mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 13:58:36 +01:00
chore(editor): cleanup comments
This commit is contained in:
@@ -1009,7 +1009,6 @@ bool editor_mode::update()
|
|||||||
// --- ImGuizmo: in-viewport transform gizmo for the selected node ---
|
// --- ImGuizmo: in-viewport transform gizmo for the selected node ---
|
||||||
render_gizmo();
|
render_gizmo();
|
||||||
|
|
||||||
// --- path editing: course and point handles of the selected path, designed route ---
|
|
||||||
if (selected_track())
|
if (selected_track())
|
||||||
draw_track_overlay();
|
draw_track_overlay();
|
||||||
if (route_active())
|
if (route_active())
|
||||||
@@ -2245,7 +2244,6 @@ void editor_mode::render_gizmo()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// route design: the gizmo moves the selected vertex of the designed alignment
|
|
||||||
if (route_active())
|
if (route_active())
|
||||||
{
|
{
|
||||||
render_route_gizmo();
|
render_route_gizmo();
|
||||||
@@ -2258,7 +2256,6 @@ void editor_mode::render_gizmo()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// paths aren't transformed as a whole node, they're edited through their source data
|
|
||||||
if (selected_track())
|
if (selected_track())
|
||||||
{
|
{
|
||||||
render_track_gizmo();
|
render_track_gizmo();
|
||||||
@@ -2690,7 +2687,6 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case GLFW_KEY_ESCAPE:
|
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)
|
if (is_press(Action) && ui()->mode() == nodebank_panel::TRACK)
|
||||||
{
|
{
|
||||||
m_track_point = {};
|
m_track_point = {};
|
||||||
@@ -2786,7 +2782,6 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// in track mode the left button selects a point handle of the selected path, or another path
|
|
||||||
if (mode == nodebank_panel::TRACK)
|
if (mode == nodebank_panel::TRACK)
|
||||||
{
|
{
|
||||||
if (false == ImGuizmo::IsOver() && false == pick_route_vertex() && false == pick_track_handle())
|
if (false == ImGuizmo::IsOver() && false == pick_route_vertex() && false == pick_track_handle())
|
||||||
|
|||||||
@@ -103,9 +103,8 @@ class editor_mode : public application_mode
|
|||||||
UID uuid; // node UUID for reference, used as fallback lookup for deleted/recreated nodes
|
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::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
|
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<std::pair<TTrack *, editor_track::state>> tracks;
|
std::vector<std::pair<TTrack *, editor_track::state>> tracks;
|
||||||
std::vector<TTrack *> created; // TrackEdit: paths added by the change
|
std::vector<TTrack *> created;
|
||||||
|
|
||||||
};
|
};
|
||||||
void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string());
|
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)
|
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
|
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;
|
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();
|
void render_track_ui();
|
||||||
// parameters and points of the selected path
|
|
||||||
void render_path_ui();
|
void render_path_ui();
|
||||||
// curve and point handles of the selected path
|
|
||||||
void draw_track_overlay() const;
|
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();
|
bool pick_track_handle();
|
||||||
// selects the path picked in the viewport
|
|
||||||
void select_track(scene::basic_node *Node);
|
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();
|
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);
|
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<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created = {});
|
void push_track_snapshot(std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> 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<EditorSnapshot> &Opposite, bool const Undo);
|
void restore_track_snapshot(EditorSnapshot const &Snapshot, std::vector<EditorSnapshot> &Opposite, bool const Undo);
|
||||||
editor_track::point_ref m_track_point; // selected point handle of the selected path
|
editor_track::point_ref m_track_point;
|
||||||
std::vector<TTrack *> m_track_drag; // paths modified by the ongoing gizmo drag
|
std::vector<TTrack *> m_track_drag;
|
||||||
std::vector<std::pair<TTrack *, editor_track::point_ref>> m_track_drag_points; // end points of neighbours dragged along
|
std::vector<std::pair<TTrack *, editor_track::point_ref>> m_track_drag_points;
|
||||||
editor_track::snap_target m_track_snap; // connection made at the end of the ongoing drag
|
editor_track::snap_target m_track_snap;
|
||||||
glm::dvec3 m_track_pivot{0.0}; // rotation pivot of the ongoing drag
|
glm::dvec3 m_track_pivot{0.0};
|
||||||
glm::mat4 m_track_gizmo{1.0f}; // gizmo matrix, kept for the duration of a drag
|
glm::mat4 m_track_gizmo{1.0f};
|
||||||
bool m_track_gizmo_using{false};
|
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;
|
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
|
editor_track::state m_track_field_before;
|
||||||
std::array<std::array<char, 256>, 3> m_track_materials{}; // material names being edited
|
std::array<std::array<char, 256>, 3> m_track_materials{};
|
||||||
// route design: re-lays a chain of paths along an alignment designed with intersection points
|
|
||||||
struct route_design
|
struct route_design
|
||||||
{
|
{
|
||||||
TTrack *from{nullptr}; // first and last path of the re-laid fragment
|
TTrack *from{nullptr};
|
||||||
TTrack *to{nullptr};
|
TTrack *to{nullptr};
|
||||||
editor_track::chain chain;
|
editor_track::chain chain;
|
||||||
std::string error;
|
std::string error;
|
||||||
std::string status; // outcome of the last application
|
std::string status;
|
||||||
alignment::design design;
|
alignment::design design;
|
||||||
alignment::result result;
|
alignment::result result;
|
||||||
int vertex{-1}; // selected vertex
|
int vertex{-1};
|
||||||
};
|
};
|
||||||
void render_route_ui();
|
void render_route_ui();
|
||||||
// finds the chain between the selected fragment ends and proposes an initial design for it
|
|
||||||
void route_reset();
|
void route_reset();
|
||||||
void route_update() { m_route.result = alignment::compute(m_route.design); }
|
void route_update() { m_route.result = alignment::compute(m_route.design); }
|
||||||
void route_apply();
|
void route_apply();
|
||||||
// sets recommended cant, transitions and vertical curve for the vertex
|
|
||||||
void route_recommend(alignment::vertex &Vertex) const;
|
void route_recommend(alignment::vertex &Vertex) const;
|
||||||
void draw_route_overlay() const;
|
void draw_route_overlay() const;
|
||||||
bool pick_route_vertex();
|
bool pick_route_vertex();
|
||||||
void render_route_gizmo();
|
void render_route_gizmo();
|
||||||
// 3d position of the vertex handle
|
|
||||||
glm::dvec3 route_vertex_position(int const Vertex) const;
|
glm::dvec3 route_vertex_position(int const Vertex) const;
|
||||||
bool route_active() const;
|
bool route_active() const;
|
||||||
route_design m_route;
|
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};
|
bool m_route_gizmo_using{false};
|
||||||
glm::mat4 m_route_gizmo{1.0f};
|
glm::mat4 m_route_gizmo{1.0f};
|
||||||
float m_track_snap_radius{1.0f};
|
float m_track_snap_radius{1.0f};
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ terms of the Mozilla Public License, v.
|
|||||||
2.0. If a copy of the MPL was not
|
2.0. If a copy of the MPL was not
|
||||||
distributed with this file, You can
|
distributed with this file, You can
|
||||||
obtain one at
|
obtain one at
|
||||||
http://mozilla.org/MPL/2.0/.
|
http:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// path (track) editing part of the editor mode
|
|
||||||
|
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "application/editormode.h"
|
#include "application/editormode.h"
|
||||||
@@ -29,7 +28,6 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
// projects world points onto the screen, with the same camera-relative view and clean perspective the gizmo uses
|
|
||||||
class screen_projection
|
class screen_projection
|
||||||
{
|
{
|
||||||
public:
|
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);
|
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
|
bool project(glm::dvec3 const &Point, ImVec2 &Screen) const
|
||||||
{
|
{
|
||||||
auto const c = clip(Point);
|
auto const c = clip(Point);
|
||||||
@@ -60,7 +57,6 @@ class screen_projection
|
|||||||
Screen = screen(c);
|
Screen = screen(c);
|
||||||
return true;
|
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
|
void line(ImDrawList *Drawlist, glm::dvec3 const &A, glm::dvec3 const &B, ImU32 const Color, float const Thickness) const
|
||||||
{
|
{
|
||||||
glm::vec4 a = clip(A);
|
glm::vec4 a = clip(A);
|
||||||
@@ -81,7 +77,6 @@ class screen_projection
|
|||||||
glm::mat4 m_viewprojection;
|
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)
|
glm::dvec3 path_point(segment_data const &Path, double const T)
|
||||||
{
|
{
|
||||||
auto const &p0 = Path.points[segment_data::point::start];
|
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};
|
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};
|
float const kHandleRadius{10.0f};
|
||||||
|
|
||||||
} // namespace
|
}
|
||||||
|
|
||||||
TTrack *editor_mode::selected_track() const
|
TTrack *editor_mode::selected_track() const
|
||||||
{
|
{
|
||||||
@@ -129,11 +123,9 @@ void editor_mode::select_track(scene::basic_node *Node)
|
|||||||
auto *track = dynamic_cast<TTrack *>(Node);
|
auto *track = dynamic_cast<TTrack *>(Node);
|
||||||
if (track == nullptr)
|
if (track == nullptr)
|
||||||
{
|
{
|
||||||
// a click beside the selected path releases its point handle
|
|
||||||
m_track_point = {};
|
m_track_point = {};
|
||||||
return;
|
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};
|
glm::dvec3 const camera{Global.pCamera.Pos};
|
||||||
if (glm::distance(glm::dvec3(track->get_nearest_point(camera)), camera) > static_cast<double>(kMaxPlacementDistance))
|
if (glm::distance(glm::dvec3(track->get_nearest_point(camera)), camera) > static_cast<double>(kMaxPlacementDistance))
|
||||||
return;
|
return;
|
||||||
@@ -164,7 +156,6 @@ bool editor_mode::pick_track_handle()
|
|||||||
continue;
|
continue;
|
||||||
float const dx = screen.x - mouse.x;
|
float const dx = screen.x - mouse.x;
|
||||||
float const dy = screen.y - mouse.y;
|
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);
|
float const distance = (dx * dx + dy * dy) * (is_end(kind) ? 0.5f : 1.0f);
|
||||||
if (distance < best)
|
if (distance < best)
|
||||||
{
|
{
|
||||||
@@ -189,7 +180,6 @@ void editor_mode::draw_track_overlay() const
|
|||||||
ImDrawList *drawlist = ImGui::GetBackgroundDrawList();
|
ImDrawList *drawlist = ImGui::GetBackgroundDrawList();
|
||||||
bool const trackmode = ui()->mode() == nodebank_panel::TRACK;
|
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);
|
ImU32 const coursecolor = IM_COL32(40, 220, 255, 220);
|
||||||
int constexpr samples{32};
|
int constexpr samples{32};
|
||||||
for (auto const &path : track->m_paths)
|
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))
|
if (false == trackmode || m_route_tab || false == editor_track::is_supported(*track))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// control vectors
|
|
||||||
ImU32 const vectorcolor = IM_COL32(200, 200, 200, 160);
|
ImU32 const vectorcolor = IM_COL32(200, 200, 200, 160);
|
||||||
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
|
for (int i = 0; i < static_cast<int>(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::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);
|
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<int>(track->m_paths.size()); ++i)
|
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
|
||||||
{
|
{
|
||||||
for (auto const kind : kPointKinds)
|
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);
|
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)
|
if (m_track_snap.track != nullptr)
|
||||||
{
|
{
|
||||||
ImVec2 screen;
|
ImVec2 screen;
|
||||||
@@ -261,14 +248,12 @@ void editor_mode::render_track_gizmo()
|
|||||||
ImGuiIO const &io = ImGui::GetIO();
|
ImGuiIO const &io = ImGui::GetIO();
|
||||||
ImGuizmo::SetRect(0.0f, 0.0f, io.DisplaySize.x, io.DisplaySize.y);
|
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::mat4 const view = GfxRenderer->Camera_View_Matrix();
|
||||||
glm::dvec3 const camerapos = GfxRenderer->Camera_Position();
|
glm::dvec3 const camerapos = GfxRenderer->Camera_Position();
|
||||||
float const fovy = glm::radians(Global.FieldOfView / Global.ZoomFactor);
|
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;
|
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);
|
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;
|
ImGuizmo::OPERATION const operation = (false == pointmode && m_gizmo_op == gizmo_operation::rotate) ? ImGuizmo::ROTATE_Y : ImGuizmo::TRANSLATE;
|
||||||
|
|
||||||
if (false == m_track_gizmo_using)
|
if (false == m_track_gizmo_using)
|
||||||
@@ -286,7 +271,6 @@ void editor_mode::render_track_gizmo()
|
|||||||
{
|
{
|
||||||
if (false == m_track_gizmo_using)
|
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_gizmo_using = true;
|
||||||
m_track_pivot = editor_track::pivot(*track);
|
m_track_pivot = editor_track::pivot(*track);
|
||||||
m_track_drag = {track};
|
m_track_drag = {track};
|
||||||
@@ -326,7 +310,6 @@ void editor_mode::render_track_gizmo()
|
|||||||
for (auto const &connection : m_track_drag_points)
|
for (auto const &connection : m_track_drag_points)
|
||||||
editor_track::move_point(*connection.first, connection.second, position);
|
editor_track::move_point(*connection.first, connection.second, position);
|
||||||
m_track_dirty = true;
|
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())
|
if (is_end(m_track_point.kind) && m_track_drag_points.empty())
|
||||||
{
|
{
|
||||||
std::vector<TTrack const *> const exclude(m_track_drag.begin(), m_track_drag.end());
|
std::vector<TTrack const *> 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)
|
else if (m_track_gizmo_using)
|
||||||
{
|
{
|
||||||
// drag end: make the offered connection and rebuild everything for good
|
|
||||||
if (m_track_snap.track != nullptr && pointmode)
|
if (m_track_snap.track != nullptr && pointmode)
|
||||||
{
|
{
|
||||||
editor_track::snap_point(*track, m_track_point, m_track_snap, m_track_align_tangent);
|
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())
|
if (false == m_track_dirty || m_track_drag.empty())
|
||||||
return;
|
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();
|
auto const now = std::chrono::steady_clock::now();
|
||||||
if (false == Force && now - m_track_last_commit < std::chrono::milliseconds(100))
|
if (false == Force && now - m_track_last_commit < std::chrono::milliseconds(100))
|
||||||
return;
|
return;
|
||||||
@@ -399,11 +380,9 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
|
|||||||
if (Snapshot.tracks.empty())
|
if (Snapshot.tracks.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// paths restored by this step; paths added by the change exist only on its redo side
|
|
||||||
std::vector<TTrack *> tracks;
|
std::vector<TTrack *> tracks;
|
||||||
for (auto const &entry : Snapshot.tracks)
|
for (auto const &entry : Snapshot.tracks)
|
||||||
tracks.emplace_back(entry.first);
|
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};
|
EditorSnapshot current{Snapshot};
|
||||||
current.tracks.clear();
|
current.tracks.clear();
|
||||||
auto involved{tracks};
|
auto involved{tracks};
|
||||||
@@ -426,7 +405,6 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
|
|||||||
for (auto const &entry : Snapshot.tracks)
|
for (auto const &entry : Snapshot.tracks)
|
||||||
editor_track::apply(*entry.first, entry.second);
|
editor_track::apply(*entry.first, entry.second);
|
||||||
editor_track::commit(tracks);
|
editor_track::commit(tracks);
|
||||||
// the designed route refers to the chain, which might have changed
|
|
||||||
if (false == m_route.chain.tracks.empty())
|
if (false == m_route.chain.tracks.empty())
|
||||||
{
|
{
|
||||||
std::string error;
|
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());
|
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f), "Geometry locked: %s", reason.c_str());
|
||||||
|
|
||||||
auto const before = editor_track::capture(*track);
|
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 };
|
enum class rebuild { none, parameters, geometry };
|
||||||
auto const finish_edit = [&](rebuild const Rebuild) {
|
auto const finish_edit = [&](rebuild const Rebuild) {
|
||||||
if (ImGui::IsItemActivated())
|
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) {
|
auto const apply_now = [&](rebuild const Rebuild) {
|
||||||
push_track_snapshot({{track, before}});
|
push_track_snapshot({{track, before}});
|
||||||
if (Rebuild == rebuild::parameters)
|
if (Rebuild == rebuild::parameters)
|
||||||
@@ -545,7 +521,6 @@ void editor_mode::render_path_ui()
|
|||||||
|
|
||||||
if (track->m_visible)
|
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};
|
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)"};
|
char const *labels[] = {"Texture 1 (rails/surface)", "Texture 2 (trackbed/side)", "Trackbed (switch)"};
|
||||||
if (false == ImGui::IsAnyItemActive())
|
if (false == ImGui::IsAnyItemActive())
|
||||||
@@ -602,7 +577,6 @@ void editor_mode::render_path_ui()
|
|||||||
ImGui::PushID(i);
|
ImGui::PushID(i);
|
||||||
if (track->m_paths.size() > 1)
|
if (track->m_paths.size() > 1)
|
||||||
ImGui::Text("Path %d", i + 1);
|
ImGui::Text("Path %d", i + 1);
|
||||||
// point selection, same as clicking the handle in the viewport
|
|
||||||
for (auto const kind : kPointKinds)
|
for (auto const kind : kPointKinds)
|
||||||
{
|
{
|
||||||
editor_track::point_ref const point{i, kind};
|
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];
|
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"))
|
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);
|
finish_edit(rebuild::geometry);
|
||||||
}
|
}
|
||||||
ImGui::InputScalarN("Control 1 (relative)", ImGuiDataType_Double, glm::value_ptr(path.points[segment_data::point::control1]), 3, nullptr, nullptr, "%.3f");
|
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
|
bool editor_mode::route_active() const
|
||||||
{
|
{
|
||||||
@@ -686,7 +659,6 @@ void editor_mode::route_reset()
|
|||||||
|
|
||||||
auto const &chain{route.chain};
|
auto const &chain{route.chain};
|
||||||
alignment::design design;
|
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.norms = route.design.norms;
|
||||||
design.shape = route.design.shape;
|
design.shape = route.design.shape;
|
||||||
design.start = chain.start;
|
design.start = chain.start;
|
||||||
@@ -702,7 +674,6 @@ void editor_mode::route_reset()
|
|||||||
design.speed = chain.velocity > 0.0 ? chain.velocity : 100.0;
|
design.speed = chain.velocity > 0.0 ? chain.velocity : 100.0;
|
||||||
route.design = design;
|
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;
|
glm::dvec2 intersection;
|
||||||
if (false == alignment::collinear(design))
|
if (false == alignment::collinear(design))
|
||||||
{
|
{
|
||||||
@@ -730,7 +701,6 @@ void editor_mode::route_apply()
|
|||||||
route.status.clear();
|
route.status.clear();
|
||||||
if (false == route.result.valid || route.chain.tracks.empty())
|
if (false == route.result.valid || route.chain.tracks.empty())
|
||||||
return;
|
return;
|
||||||
// the paths might have changed since the fragment was selected
|
|
||||||
editor_track::chain chain;
|
editor_track::chain chain;
|
||||||
if (false == editor_track::find_chain(route.from, route.to, chain, route.error))
|
if (false == editor_track::find_chain(route.from, route.to, chain, route.error))
|
||||||
return;
|
return;
|
||||||
@@ -829,7 +799,6 @@ void editor_mode::draw_route_overlay() const
|
|||||||
screen_projection const projection;
|
screen_projection const projection;
|
||||||
ImDrawList *drawlist = ImGui::GetBackgroundDrawList();
|
ImDrawList *drawlist = ImGui::GetBackgroundDrawList();
|
||||||
|
|
||||||
// polygon of tangents
|
|
||||||
std::vector<glm::dvec3> polygon{design.start};
|
std::vector<glm::dvec3> polygon{design.start};
|
||||||
for (int i = 0; i < static_cast<int>(design.vertices.size()); ++i)
|
for (int i = 0; i < static_cast<int>(design.vertices.size()); ++i)
|
||||||
polygon.push_back(route_vertex_position(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)
|
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);
|
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)
|
if (result.length > 0.0 && result.profile.size() >= 2)
|
||||||
{
|
{
|
||||||
for (auto const &e : result.elements)
|
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})
|
for (auto const atend : {false, true})
|
||||||
{
|
{
|
||||||
auto const &point{atend ? design.end : design.start};
|
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])};
|
glm::dvec3 const moved{camerapos + glm::dvec3(m_route_gizmo[3])};
|
||||||
auto &vertex{route.design.vertices[route.vertex]};
|
auto &vertex{route.design.vertices[route.vertex]};
|
||||||
glm::dvec2 const point{moved.x, moved.z};
|
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 (count >= 2)
|
||||||
{
|
{
|
||||||
if (route.vertex == 0)
|
if (route.vertex == 0)
|
||||||
@@ -1010,7 +976,6 @@ void editor_mode::render_route_ui()
|
|||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// vertices
|
|
||||||
auto &vertices{design.vertices};
|
auto &vertices{design.vertices};
|
||||||
if (ImGui::Button("Add vertex"))
|
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})};
|
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)
|
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;
|
vertices.front().offset = distance / 3.0;
|
||||||
vertex.offset = distance / 3.0;
|
vertex.offset = distance / 3.0;
|
||||||
vertices.push_back(vertex);
|
vertices.push_back(vertex);
|
||||||
@@ -1031,12 +995,10 @@ void editor_mode::render_route_ui()
|
|||||||
}
|
}
|
||||||
else
|
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<int>(vertices.size()) - 2)};
|
auto const index{std::clamp(route.vertex, 0, static_cast<int>(vertices.size()) - 2)};
|
||||||
auto const a{route_vertex_position(index)};
|
auto const a{route_vertex_position(index)};
|
||||||
auto const b{route_vertex_position(index + 1)};
|
auto const b{route_vertex_position(index + 1)};
|
||||||
vertex.position = glm::dvec2{(a.x + b.x) * 0.5, (a.z + b.z) * 0.5};
|
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<int>(vertices.size()); ++i)
|
for (int i = 0; i < static_cast<int>(vertices.size()); ++i)
|
||||||
{
|
{
|
||||||
auto const p{route_vertex_position(i)};
|
auto const p{route_vertex_position(i)};
|
||||||
@@ -1113,7 +1075,6 @@ void editor_mode::render_route_ui()
|
|||||||
if (changed)
|
if (changed)
|
||||||
route_update();
|
route_update();
|
||||||
|
|
||||||
// outcome
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
auto const &result{route.result};
|
auto const &result{route.result};
|
||||||
if (result.length > 0.0)
|
if (result.length > 0.0)
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ class editor_ui : public ui_layer
|
|||||||
{
|
{
|
||||||
m_filloptions = std::move(Renderer);
|
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<void()> Renderer)
|
void set_track_options(std::function<void()> Renderer)
|
||||||
{
|
{
|
||||||
m_trackoptions = std::move(Renderer);
|
m_trackoptions = std::move(Renderer);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terms of the Mozilla Public License, v.
|
|||||||
2.0. If a copy of the MPL was not
|
2.0. If a copy of the MPL was not
|
||||||
distributed with this file, You can
|
distributed with this file, You can
|
||||||
obtain one at
|
obtain one at
|
||||||
http://mozilla.org/MPL/2.0/.
|
http:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "stdafx.h"
|
#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;
|
return A.x * B.y - A.y * B.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
// vector rotated by 90 degrees counter-clockwise
|
|
||||||
glm::dvec2 perpendicular(glm::dvec2 const &Vector)
|
glm::dvec2 perpendicular(glm::dvec2 const &Vector)
|
||||||
{
|
{
|
||||||
return {-Vector.y, Vector.x};
|
return {-Vector.y, Vector.x};
|
||||||
@@ -63,15 +62,13 @@ std::string format(char const *Format, ...)
|
|||||||
return buffer;
|
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
|
struct local_point
|
||||||
{
|
{
|
||||||
double x{0.0};
|
double x{0.0};
|
||||||
double y{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)
|
local_point transition_point(transition_shape const Shape, double const Length, double const Radius, double const Parameter)
|
||||||
{
|
{
|
||||||
if (Length <= 0.0 || Radius <= 0.0)
|
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};
|
auto const x{Length * Parameter};
|
||||||
return {x, x * x * x / (6.0 * Radius * Length), std::atan(x * x / (2.0 * Radius * Length))};
|
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 distance{Length * Parameter};
|
||||||
auto const angle = [&](double const S) { return S * S / (2.0 * Radius * Length); };
|
auto const angle = [&](double const S) { return S * S / (2.0 * Radius * Length); };
|
||||||
int const steps{std::max(8, static_cast<int>(std::ceil(distance / 0.5)) * 2)};
|
int const steps{std::max(8, static_cast<int>(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)};
|
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)
|
void element_point(element const &Element, transition_shape const Shape, double const Distance, glm::dvec2 &Position, glm::dvec2 &Direction)
|
||||||
{
|
{
|
||||||
switch (Element.kind)
|
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);
|
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<glm::dvec2, 4> const &Points, double const A, double const B, double const C)
|
glm::dvec2 blossom(std::array<glm::dvec2, 4> const &Points, double const A, double const B, double const C)
|
||||||
{
|
{
|
||||||
glm::dvec2 r[3], s[2];
|
glm::dvec2 r[3], s[2];
|
||||||
@@ -158,7 +152,6 @@ glm::dvec2 blossom(std::array<glm::dvec2, 4> const &Points, double const A, doub
|
|||||||
return glm::mix(s[0], s[1], C);
|
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)
|
std::size_t element_pieces(element const &Element, transition_shape const Shape)
|
||||||
{
|
{
|
||||||
if (Element.length < 1e-3)
|
if (Element.length < 1e-3)
|
||||||
@@ -166,7 +159,6 @@ std::size_t element_pieces(element const &Element, transition_shape const Shape)
|
|||||||
switch (Element.kind)
|
switch (Element.kind)
|
||||||
{
|
{
|
||||||
case element_kind::arc:
|
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::size_t>(std::max(1.0, std::ceil(Element.length / Element.radius / (10.0 * kPi / 180.0) - 1e-9)));
|
return static_cast<std::size_t>(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_in:
|
||||||
case element_kind::transition_out:
|
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)
|
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};
|
double length{0.0};
|
||||||
if (Cant > 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, Speed * Cant / (3.6 * Limits.cant_rate));
|
||||||
length = std::max(length, Cant / 1000.0 * Limits.ramp_factor * Speed);
|
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))};
|
auto const unbalanced{std::max(0.0, unbalanced_acceleration(Speed, Radius, Cant, Limits))};
|
||||||
length = std::max(length, unbalanced * Speed / (3.6 * Limits.jerk));
|
length = std::max(length, unbalanced * Speed / (3.6 * Limits.jerk));
|
||||||
return length > 0.0 ? ceil_to(length, 5.0) : 0.0;
|
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 recommend(double const Speed, double const Radius, limits const &Limits)
|
||||||
{
|
{
|
||||||
recommendation result;
|
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};
|
auto const allowance{Limits.gauge * Limits.unbalanced / kGravity};
|
||||||
result.radius_min = factor * Speed * Speed / (Limits.cant_max + allowance);
|
result.radius_min = factor * Speed * Speed / (Limits.cant_max + allowance);
|
||||||
result.vertical_radius = std::max(Limits.vertical_min, Limits.vertical_factor * Speed * Speed);
|
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;
|
return result;
|
||||||
result.cant_equilibrium = factor * Speed * Speed / Radius;
|
result.cant_equilibrium = factor * Speed * Speed / Radius;
|
||||||
result.cant_min = std::max(0.0, result.cant_equilibrium - allowance);
|
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.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);
|
result.transition = transition_length(Speed, Radius, result.cant, Limits);
|
||||||
return result;
|
return result;
|
||||||
@@ -229,7 +218,6 @@ result compute(design const &Design)
|
|||||||
auto const enddirection{glm::normalize(Design.end_direction)};
|
auto const enddirection{glm::normalize(Design.end_direction)};
|
||||||
auto const count{Design.vertices.size()};
|
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);
|
r.vertices.resize(count);
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
@@ -307,7 +295,6 @@ result compute(design const &Design)
|
|||||||
auto const lead{glm::dot(position - cursor, directionin)};
|
auto const lead{glm::dot(position - cursor, directionin)};
|
||||||
if (deflection < 1e-7)
|
if (deflection < 1e-7)
|
||||||
{
|
{
|
||||||
// tangents are collinear, there's no curve to fit
|
|
||||||
if (lead < -1e-4)
|
if (lead < -1e-4)
|
||||||
r.errors.emplace_back(format("Vertex %zu lies inside the curve of the previous vertex", k + 1));
|
r.errors.emplace_back(format("Vertex %zu lies inside the curve of the previous vertex", k + 1));
|
||||||
push_straight(cursor, directionin, lead);
|
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 lengthout{std::max(0.0, vertex.transition_out)};
|
||||||
auto const endin{transition_point(Design.shape, lengthin, radius, 1.0)};
|
auto const endin{transition_point(Design.shape, lengthin, radius, 1.0)};
|
||||||
auto const endout{transition_point(Design.shape, lengthout, 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 shiftin{endin.y - radius * (1.0 - std::cos(endin.angle))};
|
||||||
auto const shiftout{endout.y - radius * (1.0 - std::cos(endout.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 middlein{endin.x - radius * std::sin(endin.angle)};
|
||||||
auto const middleout{endout.x - radius * std::sin(endout.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<double>(side)};
|
auto const normalin{perpendicular(directionin) * static_cast<double>(side)};
|
||||||
auto const normalout{perpendicular(directionout) * static_cast<double>(side)};
|
auto const normalout{perpendicular(directionout) * static_cast<double>(side)};
|
||||||
auto const a{radius + shiftin};
|
auto const a{radius + shiftin};
|
||||||
@@ -420,7 +405,6 @@ result compute(design const &Design)
|
|||||||
report.recommended = recommend(Design.speed, radius, Design.norms);
|
report.recommended = recommend(Design.speed, radius, Design.norms);
|
||||||
r.curves.push_back(report);
|
r.curves.push_back(report);
|
||||||
|
|
||||||
// verification against the limits
|
|
||||||
auto const &norms{Design.norms};
|
auto const &norms{Design.norms};
|
||||||
if (radius < report.recommended.radius_min - 1e-6)
|
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));
|
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);
|
push_straight(cursor, lastdirection, tail);
|
||||||
r.length = chainage;
|
r.length = chainage;
|
||||||
|
|
||||||
// straights between curves, measured as travel time
|
|
||||||
if (Design.norms.tangent_min_time > 0.0)
|
if (Design.norms.tangent_min_time > 0.0)
|
||||||
{
|
{
|
||||||
auto const minimum{Design.norms.tangent_min_time * Design.speed / 3.6};
|
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.vertex_chainages = vertexchainage;
|
||||||
r.profile.push_back({0.0, Design.start.y, 0.0, 0.0});
|
r.profile.push_back({0.0, Design.start.y, 0.0, 0.0});
|
||||||
for (std::size_t k = 0; k < count; ++k)
|
for (std::size_t k = 0; k < count; ++k)
|
||||||
@@ -463,7 +445,7 @@ result compute(design const &Design)
|
|||||||
r.vertex_elevations.push_back(elevation);
|
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)};
|
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)
|
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({vertexchainage[k], elevation, radius, 0.0});
|
||||||
}
|
}
|
||||||
r.profile.push_back({r.length, Design.end.y, 0.0, 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)
|
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));
|
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)
|
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));
|
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)
|
if (Design.end_radius > 0.0 && Design.end_radius < 10000.0)
|
||||||
@@ -615,7 +596,6 @@ std::vector<segment_data> pieces(result const &Result, design const &Design, std
|
|||||||
for (std::size_t j = 0; j < count; ++j)
|
for (std::size_t j = 0; j < count; ++j)
|
||||||
spans.push_back({i, e.length * j / count, e.length * (j + 1) / count});
|
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)
|
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; });
|
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<segment_data> pieces(result const &Result, design const &Design, std
|
|||||||
{
|
{
|
||||||
if (Design.shape == transition_shape::cubic_parabola)
|
if (Design.shape == transition_shape::cubic_parabola)
|
||||||
{
|
{
|
||||||
// exact: part of the cubic which describes the whole transition
|
|
||||||
std::array<glm::dvec2, 4> 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)}};
|
std::array<glm::dvec2, 4> 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 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};
|
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<segment_data> pieces(result const &Result, design const &Design, std
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// heights follow the profile, with end slopes matching its grade
|
|
||||||
auto const s0{e.chainage + piece.from};
|
auto const s0{e.chainage + piece.from};
|
||||||
auto const s3{e.chainage + piece.to};
|
auto const s3{e.chainage + piece.to};
|
||||||
auto const y0{elevation(Result, s0)};
|
auto const y0{elevation(Result, s0)};
|
||||||
@@ -693,4 +671,4 @@ std::vector<segment_data> pieces(result const &Result, design const &Design, std
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace alignment
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terms of the Mozilla Public License, v.
|
|||||||
2.0. If a copy of the MPL was not
|
2.0. If a copy of the MPL was not
|
||||||
distributed with this file, You can
|
distributed with this file, You can
|
||||||
obtain one at
|
obtain one at
|
||||||
http://mozilla.org/MPL/2.0/.
|
http:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -15,80 +15,68 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
|
|
||||||
#include "world/Segment.h"
|
#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
|
namespace alignment
|
||||||
{
|
{
|
||||||
|
|
||||||
enum class transition_shape
|
enum class transition_shape
|
||||||
{
|
{
|
||||||
cubic_parabola, // y = x^3 / (6RL), represented exactly by a single cubic Bezier
|
cubic_parabola,
|
||||||
clothoid // curvature linear along the length, approximated by cubic pieces
|
clothoid
|
||||||
};
|
};
|
||||||
|
|
||||||
// permissible values used to recommend and verify the design
|
|
||||||
struct limits
|
struct limits
|
||||||
{
|
{
|
||||||
double gauge{1500.0}; // distance between rail axes, mm
|
double gauge{1500.0};
|
||||||
double unbalanced{0.6}; // permissible unbalanced lateral acceleration, m/s2
|
double unbalanced{0.6};
|
||||||
double cant_max{150.0}; // maximum cant, mm
|
double cant_max{150.0};
|
||||||
double cant_rate{35.0}; // permissible rate of cant change along transition, mm/s
|
double cant_rate{35.0};
|
||||||
double ramp_factor{10.0}; // cant ramp inclination limited to 1 / (factor * V)
|
double ramp_factor{10.0};
|
||||||
double jerk{0.3}; // permissible rate of change of unbalanced acceleration, m/s3
|
double jerk{0.3};
|
||||||
double vertical_factor{0.4}; // minimum vertical curve radius = factor * V^2
|
double vertical_factor{0.4};
|
||||||
double vertical_min{2000.0}; // absolute minimum vertical curve radius, m
|
double vertical_min{2000.0};
|
||||||
double tangent_min_time{0.0}; // minimum length of a straight between curves expressed as travel time, s (0: not checked)
|
double tangent_min_time{0.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
// intersection point of two tangents of the horizontal alignment, with the curve fitted there
|
|
||||||
struct vertex
|
struct vertex
|
||||||
{
|
{
|
||||||
glm::dvec2 position{0.0}; // plan position of an unconstrained vertex
|
glm::dvec2 position{0.0};
|
||||||
double offset{100.0}; // first/last vertex: distance from the fixed end along its tangent
|
double offset{100.0};
|
||||||
double radius{1000.0};
|
double radius{1000.0};
|
||||||
double transition_in{0.0};
|
double transition_in{0.0};
|
||||||
double transition_out{0.0};
|
double transition_out{0.0};
|
||||||
double cant{0.0}; // mm
|
double cant{0.0};
|
||||||
bool auto_elevation{true}; // elevation interpolated between the fixed ends
|
bool auto_elevation{true};
|
||||||
double elevation{0.0}; // height of the profile at the vertex
|
double elevation{0.0};
|
||||||
double vertical_radius{0.0}; // 0: minimum radius from the limits
|
double vertical_radius{0.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct design
|
struct design
|
||||||
{
|
{
|
||||||
// fixed ends of the designed fragment, with plan directions pointing along the route
|
|
||||||
glm::dvec3 start{0.0};
|
glm::dvec3 start{0.0};
|
||||||
glm::dvec3 end{0.0};
|
glm::dvec3 end{0.0};
|
||||||
glm::dvec2 start_direction{0.0, 1.0};
|
glm::dvec2 start_direction{0.0, 1.0};
|
||||||
glm::dvec2 end_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 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};
|
double end_radius{0.0};
|
||||||
std::vector<vertex> vertices;
|
std::vector<vertex> vertices;
|
||||||
transition_shape shape{transition_shape::cubic_parabola};
|
transition_shape shape{transition_shape::cubic_parabola};
|
||||||
double speed{100.0}; // design speed, km/h
|
double speed{100.0};
|
||||||
limits norms;
|
limits norms;
|
||||||
};
|
};
|
||||||
|
|
||||||
// values derived from the design speed for a curve of given radius
|
|
||||||
struct recommendation
|
struct recommendation
|
||||||
{
|
{
|
||||||
double cant_equilibrium{0.0}; // cant compensating the lateral acceleration fully, mm
|
double cant_equilibrium{0.0};
|
||||||
double cant_min{0.0}; // smallest cant keeping the unbalanced acceleration within the limit, mm
|
double cant_min{0.0};
|
||||||
double cant{0.0}; // proposed cant, mm
|
double cant{0.0};
|
||||||
double radius_min{0.0}; // smallest radius for the design speed with maximum cant, m
|
double radius_min{0.0};
|
||||||
double transition{0.0}; // minimum transition length for the proposed cant, m
|
double transition{0.0};
|
||||||
double vertical_radius{0.0}; // minimum vertical curve radius, m
|
double vertical_radius{0.0};
|
||||||
};
|
};
|
||||||
recommendation recommend(double const Speed, double const Radius, limits const &Limits);
|
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);
|
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);
|
double unbalanced_acceleration(double const Speed, double const Radius, double const Cant, limits const &Limits);
|
||||||
|
|
||||||
enum class element_kind
|
enum class element_kind
|
||||||
@@ -99,36 +87,31 @@ enum class element_kind
|
|||||||
transition_out
|
transition_out
|
||||||
};
|
};
|
||||||
|
|
||||||
// single geometric element of the computed alignment
|
|
||||||
struct element
|
struct element
|
||||||
{
|
{
|
||||||
element_kind kind{element_kind::straight};
|
element_kind kind{element_kind::straight};
|
||||||
int vertex{-1}; // vertex the curve element belongs to
|
int vertex{-1};
|
||||||
double chainage{0.0}; // distance from the start of the fragment to the start of the element
|
double chainage{0.0};
|
||||||
double length{0.0};
|
double length{0.0};
|
||||||
double radius{0.0}; // curve radius (also the final radius of a transition)
|
double radius{0.0};
|
||||||
double transition{0.0}; // transition length
|
double transition{0.0};
|
||||||
int turn{0}; // +1/-1 direction of the curve, 0 for straights
|
int turn{0};
|
||||||
double cant{0.0}; // full cant of the curve, mm
|
double cant{0.0};
|
||||||
// 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
|
|
||||||
glm::dvec2 origin{0.0};
|
glm::dvec2 origin{0.0};
|
||||||
glm::dvec2 direction{0.0, 1.0};
|
glm::dvec2 direction{0.0, 1.0};
|
||||||
glm::dvec2 normal{0.0};
|
glm::dvec2 normal{0.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
// verification of a single curve against the limits
|
|
||||||
struct curve_report
|
struct curve_report
|
||||||
{
|
{
|
||||||
int vertex{-1};
|
int vertex{-1};
|
||||||
double deflection{0.0}; // angle between the tangents, radians
|
double deflection{0.0};
|
||||||
double arc_length{0.0};
|
double arc_length{0.0};
|
||||||
double tangent_in{0.0}; // distance from the vertex to the start of the curve
|
double tangent_in{0.0};
|
||||||
double tangent_out{0.0}; // distance from the vertex to the end of the curve
|
double tangent_out{0.0};
|
||||||
double unbalanced{0.0}; // m/s2
|
double unbalanced{0.0};
|
||||||
double cant_rate_in{0.0}, cant_rate_out{0.0}; // mm/s
|
double cant_rate_in{0.0}, cant_rate_out{0.0};
|
||||||
double jerk_in{0.0}, jerk_out{0.0}; // m/s3
|
double jerk_in{0.0}, jerk_out{0.0};
|
||||||
recommendation recommended;
|
recommendation recommended;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,8 +119,8 @@ struct profile_point
|
|||||||
{
|
{
|
||||||
double chainage{0.0};
|
double chainage{0.0};
|
||||||
double elevation{0.0};
|
double elevation{0.0};
|
||||||
double radius{0.0}; // vertical curve radius at the grade break
|
double radius{0.0};
|
||||||
double tangent{0.0}; // vertical curve tangent length
|
double tangent{0.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct result
|
struct result
|
||||||
@@ -147,9 +130,9 @@ struct result
|
|||||||
double gauge{1500.0};
|
double gauge{1500.0};
|
||||||
std::vector<std::string> errors;
|
std::vector<std::string> errors;
|
||||||
std::vector<std::string> warnings;
|
std::vector<std::string> warnings;
|
||||||
std::vector<glm::dvec2> vertices; // resolved plan positions of the vertices
|
std::vector<glm::dvec2> vertices;
|
||||||
std::vector<double> vertex_chainages; // chainage of the middle of each vertex curve
|
std::vector<double> vertex_chainages;
|
||||||
std::vector<double> vertex_elevations; // profile elevation at each vertex
|
std::vector<double> vertex_elevations;
|
||||||
std::vector<element> elements;
|
std::vector<element> elements;
|
||||||
std::vector<curve_report> curves;
|
std::vector<curve_report> curves;
|
||||||
std::vector<profile_point> profile;
|
std::vector<profile_point> profile;
|
||||||
@@ -157,12 +140,9 @@ struct result
|
|||||||
};
|
};
|
||||||
|
|
||||||
result compute(design const &Design);
|
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);
|
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);
|
bool collinear(design const &Design);
|
||||||
|
|
||||||
// position, direction of travel (unit, 3d), cant and curvature direction at specified chainage
|
|
||||||
struct station
|
struct station
|
||||||
{
|
{
|
||||||
glm::dvec3 position{0.0};
|
glm::dvec3 position{0.0};
|
||||||
@@ -171,14 +151,10 @@ struct station
|
|||||||
int turn{0};
|
int turn{0};
|
||||||
};
|
};
|
||||||
station evaluate(result const &Result, double const Chainage);
|
station evaluate(result const &Result, double const Chainage);
|
||||||
// profile height and grade at specified chainage
|
|
||||||
double elevation(result const &Result, double const Chainage);
|
double elevation(result const &Result, double const Chainage);
|
||||||
double grade(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);
|
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<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count);
|
std::vector<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count);
|
||||||
|
|
||||||
} // namespace alignment
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terms of the Mozilla Public License, v.
|
|||||||
2.0. If a copy of the MPL was not
|
2.0. If a copy of the MPL was not
|
||||||
distributed with this file, You can
|
distributed with this file, You can
|
||||||
obtain one at
|
obtain one at
|
||||||
http://mozilla.org/MPL/2.0/.
|
http:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
@@ -26,7 +26,6 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
// end points closer than this are treated as the same point
|
|
||||||
double const kSamePoint{0.05};
|
double const kSamePoint{0.05};
|
||||||
|
|
||||||
segment_data::point point_index(editor_track::point_kind const Kind)
|
segment_data::point point_index(editor_track::point_kind const Kind)
|
||||||
@@ -56,7 +55,6 @@ void add_unique(std::vector<TTrack *> &Tracks, TTrack *Track)
|
|||||||
Tracks.emplace_back(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)
|
glm::dvec3 path_tangent(segment_data const &Path, bool const Atend)
|
||||||
{
|
{
|
||||||
auto const &start{Path.points[segment_data::point::start]};
|
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);
|
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)
|
double path_radius(segment_data const &Path, double const T)
|
||||||
{
|
{
|
||||||
auto const &p0{Path.points[segment_data::point::start]};
|
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;
|
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 reversed(segment_data const &Path)
|
||||||
{
|
{
|
||||||
segment_data result{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::end] = Path.points[segment_data::point::start];
|
||||||
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
|
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
|
||||||
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
|
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]};
|
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
|
||||||
return result;
|
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)
|
bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool const Leaving, glm::dvec3 &Direction, double &Radius)
|
||||||
{
|
{
|
||||||
if (Neighbour == nullptr)
|
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]};
|
auto const &point{path.points[atend ? segment_data::point::end : segment_data::point::start]};
|
||||||
if (glm::distance(point, Joint) > kSamePoint)
|
if (glm::distance(point, Joint) > kSamePoint)
|
||||||
continue;
|
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))};
|
auto const tangent{glm::normalize(path_tangent(path, atend))};
|
||||||
Direction = (Leaving != atend) ? tangent : -tangent;
|
Direction = (Leaving != atend) ? tangent : -tangent;
|
||||||
Radius = path_radius(path, atend ? 1.0 : 0.0);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
}
|
||||||
|
|
||||||
editor_track::state editor_track::capture(TTrack const &Track)
|
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)
|
bool editor_track::is_supported(TTrack const &Track)
|
||||||
{
|
{
|
||||||
if ((Track.iCategoryFlag & 0x80) != 0)
|
if ((Track.iCategoryFlag & 0x80) != 0)
|
||||||
return false; // auto-generated helper path
|
return false;
|
||||||
if (Track.eType == tt_Normal)
|
if (Track.eType == tt_Normal)
|
||||||
return Track.Segment != nullptr;
|
return Track.Segment != nullptr;
|
||||||
if (Track.eType == tt_Switch)
|
if (Track.eType == tt_Switch)
|
||||||
@@ -209,7 +200,6 @@ void editor_track::commit(std::vector<TTrack *> const &Tracks)
|
|||||||
if (Tracks.empty())
|
if (Tracks.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// paths whose render geometry depends on the modified ones
|
|
||||||
std::vector<TTrack *> affected;
|
std::vector<TTrack *> affected;
|
||||||
for (auto *track : Tracks)
|
for (auto *track : Tracks)
|
||||||
{
|
{
|
||||||
@@ -217,27 +207,23 @@ void editor_track::commit(std::vector<TTrack *> const &Tracks)
|
|||||||
for (auto *neighbour : neighbours(*track))
|
for (auto *neighbour : neighbours(*track))
|
||||||
add_unique(affected, neighbour);
|
add_unique(affected, neighbour);
|
||||||
}
|
}
|
||||||
// take the paths out of the scene while their ends still match the old segments...
|
|
||||||
for (auto *track : Tracks)
|
for (auto *track : Tracks)
|
||||||
{
|
{
|
||||||
disconnect(*track);
|
disconnect(*track);
|
||||||
simulation::Region->erase_and_unregister(track);
|
simulation::Region->erase_and_unregister(track);
|
||||||
}
|
}
|
||||||
// ...rebuild the segments and put the paths back in their new place...
|
|
||||||
for (auto *track : Tracks)
|
for (auto *track : Tracks)
|
||||||
{
|
{
|
||||||
track->init_segments(false);
|
track->init_segments(false);
|
||||||
track->update_location();
|
track->update_location();
|
||||||
track->m_area.radius = -1.f; // forces radius recalculation
|
track->m_area.radius = -1.f;
|
||||||
simulation::Region->insert_and_register(track);
|
simulation::Region->insert_and_register(track);
|
||||||
}
|
}
|
||||||
// ...connect them with whatever they touch now...
|
|
||||||
for (auto *track : Tracks)
|
for (auto *track : Tracks)
|
||||||
join(*track);
|
join(*track);
|
||||||
for (auto *track : Tracks)
|
for (auto *track : Tracks)
|
||||||
for (auto *neighbour : neighbours(*track))
|
for (auto *neighbour : neighbours(*track))
|
||||||
add_unique(affected, neighbour);
|
add_unique(affected, neighbour);
|
||||||
// ...and update the visuals
|
|
||||||
for (auto *track : affected)
|
for (auto *track : affected)
|
||||||
update_transition(*track);
|
update_transition(*track);
|
||||||
for (auto *track : affected)
|
for (auto *track : affected)
|
||||||
@@ -249,7 +235,6 @@ void editor_track::commit(std::vector<TTrack *> const &Tracks)
|
|||||||
void editor_track::commit_parameters(TTrack &Track)
|
void editor_track::commit_parameters(TTrack &Track)
|
||||||
{
|
{
|
||||||
Track.fTrackWidth2 = Track.fTrackWidth;
|
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);
|
Track.init_segments(false);
|
||||||
|
|
||||||
std::vector<TTrack *> affected{&Track};
|
std::vector<TTrack *> 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 &point{path.points[point_index(Point.kind)]};
|
||||||
auto const oldposition{point};
|
auto const oldposition{point};
|
||||||
point = Position;
|
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)
|
for (auto &otherpath : Track.m_paths)
|
||||||
{
|
{
|
||||||
if (&otherpath == &path)
|
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 excluded = [&](TTrack const *Other) { return std::find(Exclude.begin(), Exclude.end(), Other) != Exclude.end(); };
|
||||||
auto const category{Track.iCategoryFlag & 15};
|
auto const category{Track.iCategoryFlag & 15};
|
||||||
// NOTE: copy, the region returns its scratchpad
|
|
||||||
auto const sections{simulation::Region->sections(Position, static_cast<float>(Radius))};
|
auto const sections{simulation::Region->sections(Position, static_cast<float>(Radius))};
|
||||||
for (auto *section : sections)
|
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)};
|
auto const distance{glm::distance(point, Position)};
|
||||||
if (distance > Radius || distance >= result.distance)
|
if (distance > Radius || distance >= result.distance)
|
||||||
continue;
|
continue;
|
||||||
// skip ends already connected with something which stays in place
|
|
||||||
auto const connections{connected_points(*other, point)};
|
auto const connections{connected_points(*other, point)};
|
||||||
if (std::any_of(connections.begin(), connections.end(), [&](auto const &Connection) { return false == excluded(Connection.first); }))
|
if (std::any_of(connections.begin(), connections.end(), [&](auto const &Connection) { return false == excluded(Connection.first); }))
|
||||||
continue;
|
continue;
|
||||||
@@ -466,7 +447,6 @@ void editor_track::snap_point(TTrack &Track, point_ref const &Point, snap_target
|
|||||||
if (false == Aligntangent)
|
if (false == Aligntangent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// the path continues the target, so it leaves the shared point in the opposite direction
|
|
||||||
auto &path{Track.m_paths[Point.path]};
|
auto &path{Track.m_paths[Point.path]};
|
||||||
auto const &start{path.points[segment_data::point::start]};
|
auto const &start{path.points[segment_data::point::start]};
|
||||||
auto const &end{path.points[segment_data::point::end]};
|
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)
|
if (Track.SwitchExtension && Track.eType == tt_Switch)
|
||||||
{
|
{
|
||||||
// same rules as TTrack::Switch()
|
|
||||||
Track.SwitchExtension->fVelocity = static_cast<float>(Velocity);
|
Track.SwitchExtension->fVelocity = static_cast<float>(Velocity);
|
||||||
if (Velocity <= -2.0)
|
if (Velocity <= -2.0)
|
||||||
Track.fVelocity = Track.SwitchExtension->CurrentIndex ? -Velocity : -1.0;
|
Track.fVelocity = Track.SwitchExtension->CurrentIndex ? -Velocity : -1.0;
|
||||||
@@ -594,7 +573,6 @@ void editor_track::join(TTrack &Track)
|
|||||||
}
|
}
|
||||||
case tt_Switch:
|
case tt_Switch:
|
||||||
{
|
{
|
||||||
// like during scenery load, switches are connected from the side of the regular paths
|
|
||||||
for (int i = 0; i < 2; ++i)
|
for (int i = 0; i < 2; ++i)
|
||||||
{
|
{
|
||||||
auto const &segment{Track.SwitchExtension->Segments[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))
|
if (false == has_switch_paths(*Other))
|
||||||
break;
|
break;
|
||||||
// same connections as in path_table::InitTracks(), but made without moving the switch blades
|
|
||||||
auto const path{(Endpointid - 2) / 2};
|
auto const path{(Endpointid - 2) / 2};
|
||||||
auto const atend{((Endpointid - 2) % 2) != 0};
|
auto const atend{((Endpointid - 2) % 2) != 0};
|
||||||
bind_switch_path(*Other, path);
|
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)
|
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;
|
Track.iTrapezoid &= ~2;
|
||||||
auto const *next{Track.trNext};
|
auto const *next{Track.trNext};
|
||||||
if (next == nullptr || Track.iNextDirection != 0)
|
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)
|
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 &extension{*Switch.SwitchExtension};
|
||||||
auto const path{Path & 1};
|
auto const path{Path & 1};
|
||||||
extension.pNexts[path] = Switch.trNext;
|
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;
|
extension.iPrevDirection[path] = Switch.iPrevDirection;
|
||||||
if (Switch.eType == tt_Switch)
|
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.pPrevs[path ^ 1] = Switch.trPrev;
|
||||||
extension.iPrevDirection[path ^ 1] = Switch.iPrevDirection;
|
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)
|
if (From != To)
|
||||||
{
|
{
|
||||||
bool found{false};
|
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})
|
for (auto const throughend : {true, false})
|
||||||
{
|
{
|
||||||
Chain.tracks = {From};
|
Chain.tracks = {From};
|
||||||
@@ -780,7 +753,6 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin
|
|||||||
Chain.radius = radius;
|
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 &first{From->m_paths.front()};
|
||||||
auto const firstforward{Chain.forward.front()};
|
auto const firstforward{Chain.forward.front()};
|
||||||
Chain.start = first.points[firstforward ? segment_data::point::start : segment_data::point::end];
|
Chain.start = first.points[firstforward ? segment_data::point::start : segment_data::point::end];
|
||||||
@@ -806,7 +778,6 @@ std::vector<TTrack *> editor_track::relay(chain const &Chain, std::vector<segmen
|
|||||||
if (count == 0 || Pieces.size() < count)
|
if (count == 0 || Pieces.size() < count)
|
||||||
return created;
|
return created;
|
||||||
|
|
||||||
// existing paths are spread evenly over the pieces, added paths fill the gaps between them
|
|
||||||
std::vector<TTrack *> tracks;
|
std::vector<TTrack *> tracks;
|
||||||
std::size_t previous{count};
|
std::size_t previous{count};
|
||||||
for (std::size_t i = 0; i < Pieces.size(); ++i)
|
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);
|
cParser parser(text.str(), cParser::buffer_TEXT);
|
||||||
parser.getTokens();
|
parser.getTokens();
|
||||||
std::string token;
|
std::string token;
|
||||||
parser >> token; // node type, consumed before the path is loaded
|
parser >> token;
|
||||||
|
|
||||||
scene::node_data data;
|
scene::node_data data;
|
||||||
data.type = "track";
|
data.type = "track";
|
||||||
@@ -854,7 +825,6 @@ TTrack *editor_track::clone(TTrack const &Template)
|
|||||||
track->m_rangesquaredmin = Template.m_rangesquaredmin;
|
track->m_rangesquaredmin = Template.m_rangesquaredmin;
|
||||||
track->m_rangesquaredmax = Template.m_rangesquaredmax;
|
track->m_rangesquaredmax = Template.m_rangesquaredmax;
|
||||||
track->Load(&parser, glm::dvec3{});
|
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})
|
for (auto *events : {&track->m_events0, &track->m_events1, &track->m_events2, &track->m_events0all, &track->m_events1all, &track->m_events2all})
|
||||||
events->clear();
|
events->clear();
|
||||||
track->m_events = false;
|
track->m_events = false;
|
||||||
@@ -885,6 +855,5 @@ void editor_track::revive(TTrack &Track)
|
|||||||
void editor_track::rebuild_geometry(TTrack &Track)
|
void editor_track::rebuild_geometry(TTrack &Track)
|
||||||
{
|
{
|
||||||
auto §ion{simulation::Region->section(Track.location())};
|
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{});
|
Track.rebuild_geometry(section.m_geometrycreated ? section.m_geometrybank : gfx::geometrybank_handle{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terms of the Mozilla Public License, v.
|
|||||||
2.0. If a copy of the MPL was not
|
2.0. If a copy of the MPL was not
|
||||||
distributed with this file, You can
|
distributed with this file, You can
|
||||||
obtain one at
|
obtain one at
|
||||||
http://mozilla.org/MPL/2.0/.
|
http:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -19,25 +19,18 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
|
|
||||||
class TTrack;
|
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
|
class editor_track
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// identifies an editable point of a path
|
|
||||||
enum class point_kind { start, control1, control2, end };
|
enum class point_kind { start, control1, control2, end };
|
||||||
struct point_ref
|
struct point_ref
|
||||||
{
|
{
|
||||||
int path{-1}; // index into TTrack::m_paths
|
int path{-1};
|
||||||
point_kind kind{point_kind::start};
|
point_kind kind{point_kind::start};
|
||||||
bool valid() const { return path >= 0; }
|
bool valid() const { return path >= 0; }
|
||||||
bool operator==(point_ref const &Other) const { return path == Other.path && kind == Other.kind; }
|
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
|
struct state
|
||||||
{
|
{
|
||||||
std::vector<segment_data> paths;
|
std::vector<segment_data> paths;
|
||||||
@@ -58,85 +51,57 @@ class editor_track
|
|||||||
float texslope{0.9f};
|
float texslope{0.9f};
|
||||||
};
|
};
|
||||||
static state capture(TTrack const &Track);
|
static state capture(TTrack const &Track);
|
||||||
// restores provided state; follow with commit() to rebuild the path
|
|
||||||
static void apply(TTrack &Track, state const &State);
|
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);
|
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);
|
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<TTrack *> const &Tracks);
|
static void commit(std::vector<TTrack *> const &Tracks);
|
||||||
// rebuilds the path after change of parameters which don't affect its course (width, textures, etc)
|
|
||||||
static void commit_parameters(TTrack &Track);
|
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);
|
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);
|
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);
|
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);
|
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);
|
static glm::dvec3 pivot(TTrack const &Track);
|
||||||
|
|
||||||
// lists paths currently connected to provided path
|
|
||||||
static std::vector<TTrack *> neighbours(TTrack const &Track);
|
static std::vector<TTrack *> 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<std::pair<TTrack *, point_ref>> connected_points(TTrack const &Track, glm::dvec3 const &Position);
|
static std::vector<std::pair<TTrack *, point_ref>> 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);
|
static bool is_connected(TTrack const &Track, point_ref const &Point);
|
||||||
|
|
||||||
// candidate to connect a dragged end point with
|
|
||||||
struct snap_target
|
struct snap_target
|
||||||
{
|
{
|
||||||
TTrack *track{nullptr};
|
TTrack *track{nullptr};
|
||||||
point_ref point;
|
point_ref point;
|
||||||
glm::dvec3 position{0.0};
|
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};
|
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<TTrack const *> const &Exclude);
|
static snap_target find_snap_target(TTrack const &Track, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> 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);
|
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
|
struct chain
|
||||||
{
|
{
|
||||||
std::vector<TTrack *> tracks;
|
std::vector<TTrack *> tracks;
|
||||||
std::vector<bool> forward; // the path runs in the direction of the chain (its start is closer to the chain start)
|
std::vector<bool> forward;
|
||||||
glm::dvec3 start{0.0}; // outer end of the first path
|
glm::dvec3 start{0.0};
|
||||||
glm::dvec3 end{0.0}; // outer end of the last path
|
glm::dvec3 end{0.0};
|
||||||
glm::dvec3 start_direction{0.0}; // direction of travel along the chain at its ends, from the adjoining paths if there are any
|
glm::dvec3 start_direction{0.0};
|
||||||
glm::dvec3 end_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 end_radius{0.0};
|
||||||
double length{0.0};
|
double length{0.0};
|
||||||
double velocity{-1.0}; // highest speed limit of the paths in the chain
|
double velocity{-1.0};
|
||||||
double radius{0.0}; // smallest radius of the paths in the chain, 0 if all are straight
|
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);
|
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<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces);
|
static std::vector<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces);
|
||||||
// takes a path added by the editor out of the scene, or puts it back
|
|
||||||
static void retire(TTrack &Track);
|
static void retire(TTrack &Track);
|
||||||
static void revive(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 std::string material_name(material_handle const Material);
|
||||||
static material_handle fetch_material(std::string const &Name);
|
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 float texture_height(TTrack const &Track);
|
||||||
static void texture_height(TTrack &Track, float const Height);
|
static void texture_height(TTrack &Track, float const Height);
|
||||||
static double velocity(TTrack const &Track);
|
static double velocity(TTrack const &Track);
|
||||||
@@ -144,19 +109,12 @@ class editor_track
|
|||||||
static void damage(TTrack &Track, int const Damage);
|
static void damage(TTrack &Track, int const Damage);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// unlinks the path from all its neighbours
|
|
||||||
static void disconnect(TTrack &Track);
|
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);
|
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);
|
static void connect(TTrack &Track, bool const Prevside, TTrack *Other, int const Endpointid);
|
||||||
// recalculates the trackbed transition flag
|
|
||||||
static void update_transition(TTrack &Track);
|
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 bind_switch_path(TTrack &Switch, int const Path);
|
||||||
static void store_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);
|
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);
|
static TTrack *clone(TTrack const &Template);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -416,14 +416,12 @@ basic_cell::insert( TMemCell *Memorycell ) {
|
|||||||
// NOTE: memory cells are virtual 'points' hence they don't ever expand cell range
|
// NOTE: memory cells are virtual 'points' hence they don't ever expand cell range
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes provided path from the cell
|
|
||||||
void
|
void
|
||||||
basic_cell::erase( TTrack *Path ) {
|
basic_cell::erase( TTrack *Path ) {
|
||||||
|
|
||||||
m_paths.erase(
|
m_paths.erase(
|
||||||
std::remove( std::begin( m_paths ), std::end( m_paths ), Path ),
|
std::remove( std::begin( m_paths ), std::end( m_paths ), Path ),
|
||||||
std::end( m_paths ) );
|
std::end( m_paths ) );
|
||||||
// drop the path from the animation list, the cell won't update it anymore
|
|
||||||
if( tTrackAnim == Path ) {
|
if( tTrackAnim == Path ) {
|
||||||
tTrackAnim = Path->SwitchExtension->pNextAnim;
|
tTrackAnim = Path->SwitchExtension->pNextAnim;
|
||||||
Path->SwitchExtension->pNextAnim = nullptr;
|
Path->SwitchExtension->pNextAnim = nullptr;
|
||||||
@@ -441,7 +439,6 @@ basic_cell::erase( TTrack *Path ) {
|
|||||||
// TODO: update cell bounding area
|
// TODO: update cell bounding area
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes provided path from the lookup directory of the cell
|
|
||||||
void
|
void
|
||||||
basic_cell::unregister_end( TTrack *Path ) {
|
basic_cell::unregister_end( TTrack *Path ) {
|
||||||
|
|
||||||
|
|||||||
@@ -145,10 +145,8 @@ public:
|
|||||||
// registers provided traction piece in the lookup directory of the cell
|
// registers provided traction piece in the lookup directory of the cell
|
||||||
void
|
void
|
||||||
register_end( TTraction *Traction );
|
register_end( TTraction *Traction );
|
||||||
// removes provided path from the cell
|
|
||||||
void
|
void
|
||||||
erase( TTrack *Path );
|
erase( TTrack *Path );
|
||||||
// removes provided path from the lookup directory of the cell
|
|
||||||
void
|
void
|
||||||
unregister_end( TTrack *Path );
|
unregister_end( TTrack *Path );
|
||||||
// removes provided model instance from the cell
|
// removes provided model instance from the cell
|
||||||
@@ -314,7 +312,6 @@ public:
|
|||||||
void
|
void
|
||||||
register_node( Type_ *Node, glm::dvec3 const &Point ) {
|
register_node( Type_ *Node, glm::dvec3 const &Point ) {
|
||||||
cell( Point ).register_end( Node ); }
|
cell( Point ).register_end( Node ); }
|
||||||
// removes provided node from the lookup directory of the section enclosing specified point
|
|
||||||
template <class Type_>
|
template <class Type_>
|
||||||
void
|
void
|
||||||
unregister_node( Type_ *Node, glm::dvec3 const &Point ) {
|
unregister_node( Type_ *Node, glm::dvec3 const &Point ) {
|
||||||
@@ -446,7 +443,6 @@ public:
|
|||||||
auto const location{ Node->location() };
|
auto const location{ Node->location() };
|
||||||
if( point_inside( location ) ) {
|
if( point_inside( location ) ) {
|
||||||
section( location ).erase( Node ); } }
|
section( location ).erase( Node ); } }
|
||||||
// removes specified node from the region and from the lookup directories of its ends
|
|
||||||
template <class Type_>
|
template <class Type_>
|
||||||
void
|
void
|
||||||
erase_and_unregister( Type_ *Node ) {
|
erase_and_unregister( Type_ *Node ) {
|
||||||
|
|||||||
@@ -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).
|
// 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() {
|
void TTrack::update_location() {
|
||||||
|
|
||||||
location( (
|
location( (
|
||||||
@@ -697,19 +696,14 @@ void TTrack::update_location() {
|
|||||||
/ 3.0 );
|
/ 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 ) {
|
void TTrack::init_segments( bool const Initial ) {
|
||||||
|
|
||||||
glm::dvec3 p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań
|
glm::dvec3 p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań
|
||||||
double r1, r2, r3, r4;
|
double r1, r2, r3, r4;
|
||||||
double segsize = 5.0; // długość odcinka segmentowania
|
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;
|
fTexHeight1 -= fTexHeightOffset;
|
||||||
fTexHeightOffset = 0.f;
|
fTexHeightOffset = 0.f;
|
||||||
// the roll flag is re-evaluated below, the transition flag is set again when the path gets connected
|
|
||||||
iTrapezoid = 0;
|
iTrapezoid = 0;
|
||||||
|
|
||||||
switch (eType) {
|
switch (eType) {
|
||||||
@@ -1355,9 +1349,6 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wypełnianie tablic VBO
|
// 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 ) {
|
void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||||
|
|
||||||
Geometry1.clear();
|
Geometry1.clear();
|
||||||
@@ -2358,7 +2349,6 @@ TTrack::export_as_text_( std::ostream &Output ) const {
|
|||||||
<< fTexSlope << ' ';
|
<< fTexSlope << ' ';
|
||||||
}
|
}
|
||||||
// path data
|
// path data
|
||||||
// NOTE: default stream precision would round coordinates to centimetres or worse, breaking path connections
|
|
||||||
auto const precision { Output.precision( std::numeric_limits<double>::digits10 ) };
|
auto const precision { Output.precision( std::numeric_limits<double>::digits10 ) };
|
||||||
for( auto const &path : m_paths ) {
|
for( auto const &path : m_paths ) {
|
||||||
Output
|
Output
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ public:
|
|||||||
geometryhandle_sequence Geometry2; // geometry chunks textured with texture 2
|
geometryhandle_sequence Geometry2; // geometry chunks textured with texture 2
|
||||||
|
|
||||||
std::vector<segment_data> m_paths; // source data for owned paths
|
std::vector<segment_data> 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;
|
int iterate_stamp = 0;
|
||||||
|
|
||||||
// sleepermodel optional parameter -------------------------------------------------
|
// sleepermodel optional parameter -------------------------------------------------
|
||||||
@@ -280,9 +280,7 @@ public:
|
|||||||
RouteCount() const {
|
RouteCount() const {
|
||||||
return SwitchExtension != nullptr ? SwitchExtension->iRoads - 1 : 1; }
|
return SwitchExtension != nullptr ? SwitchExtension->iRoads - 1 : 1; }
|
||||||
void Load(cParser *parser, glm::dvec3 const &pOrigin);
|
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 );
|
void init_segments( bool const Initial );
|
||||||
// calculates path location from the current segment
|
|
||||||
void update_location();
|
void update_location();
|
||||||
bool AssignEvents();
|
bool AssignEvents();
|
||||||
bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus);
|
bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus);
|
||||||
@@ -306,7 +304,6 @@ public:
|
|||||||
double ActiveLength();
|
double ActiveLength();
|
||||||
|
|
||||||
void create_geometry( gfx::geometrybank_handle const &Bank ); // wypełnianie VBO
|
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 rebuild_geometry( gfx::geometrybank_handle const &Bank );
|
||||||
void create_map_geometry(std::vector<gfx::basic_vertex> &Bank, const gfx::geometrybank_handle Extra);
|
void create_map_geometry(std::vector<gfx::basic_vertex> &Bank, const gfx::geometrybank_handle Extra);
|
||||||
void get_map_active_paths(map_colored_paths &handles);
|
void get_map_active_paths(map_colored_paths &handles);
|
||||||
|
|||||||
Reference in New Issue
Block a user