mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-08 02:58:37 +01:00
feat(editor): two free ends joined without loops or forced curves, the shape from a free end on the wheel
- the join of two free ends (a drag from one onto the other, or a click of the lay tool on it) tries in turn: a straight when the joints check can't see the kinks (half its angle), one circular arc through both ends (a piece cut out of a curve comes back as it was, its cant kept), a curve with straights where the tangents meet, a reverse curve, and only then a free Bezier curve; a curve turning more than it needs to, sharper than 10 m or longer than three chords is never taken - the transition curves eased out of a curved neighbour only when there's room for them between the ends, never crossing each other into a loop - cant and transition curves recommended for the radius the curve really gets, at the speed of the joined track or of the tool and never above what the radius allows; the circular arc keeps at least max(20 m, 0.4 V) between the transitions, the cant lowered to what the shortened transitions can ramp, transitions under 1 m left out - shapes from a free end: arc then straight, straight then arc, arc through the cursor, straight; Ctrl+wheel steps the radius, Shift+wheel the shape (at a free end: the other joins), the plain wheel still zooms; a click of the lay tool from a free end lays the shape and goes on from its end - self-test commands join, layto and joints; Polish translation
This commit is contained in:
@@ -4994,6 +4994,8 @@ void editor_mode::ortho_pan_stop()
|
|||||||
|
|
||||||
void editor_mode::on_scroll(double const Xoffset, double const Yoffset)
|
void editor_mode::on_scroll(double const Xoffset, double const Yoffset)
|
||||||
{
|
{
|
||||||
|
if (false == ImGui::GetIO().WantCaptureMouse && track_wheel(Yoffset != 0.0 ? Yoffset : Xoffset))
|
||||||
|
return;
|
||||||
if (false == Global.EditorOrtho || ImGui::GetIO().WantCaptureMouse)
|
if (false == Global.EditorOrtho || ImGui::GetIO().WantCaptureMouse)
|
||||||
return;
|
return;
|
||||||
auto const before{Global.EditorOrthoExtent};
|
auto const before{Global.EditorOrthoExtent};
|
||||||
|
|||||||
@@ -1006,7 +1006,28 @@ class editor_mode : public application_mode, private editor_track::observer
|
|||||||
// adds a straight (Side 0) or a curve to the left (1) or the right (-1) at the free end of the selected track
|
// adds a straight (Side 0) or a curve to the left (1) or the right (-1) at the free end of the selected track
|
||||||
void extend_add(int const Side);
|
void extend_add(int const Side);
|
||||||
void render_extend_ui();
|
void render_extend_ui();
|
||||||
bool m_extend_freehand{false}; // dragging the ring of a free end draws a curve through the cursor, not a straight
|
enum class track_layout
|
||||||
|
{
|
||||||
|
arc_straight,
|
||||||
|
straight_arc,
|
||||||
|
arc,
|
||||||
|
straight
|
||||||
|
};
|
||||||
|
track_layout m_track_layout{track_layout::arc_straight};
|
||||||
|
double m_join_radius{0.0};
|
||||||
|
int m_join_variant{0};
|
||||||
|
TTrack const *m_join_track{nullptr};
|
||||||
|
editor_track::point_ref m_join_point;
|
||||||
|
mutable alignment::join_result m_join_last;
|
||||||
|
bool m_shift_wheel{false};
|
||||||
|
bool track_wheel(double const Offset);
|
||||||
|
std::string track_shaping(bool const Joined) const;
|
||||||
|
void render_layout_combo();
|
||||||
|
bool shift_straight() const;
|
||||||
|
double design_speed(TTrack const *From, TTrack const *To) const;
|
||||||
|
std::vector<segment_data> layout_pieces(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, glm::dvec3 const &Mouse, double const Radius) const;
|
||||||
|
std::vector<segment_data> join_pieces(TTrack const *From, editor_track::point_ref const &Frompoint, glm::dvec3 const &Start, glm::dvec2 const &Direction, editor_track::snap_target const &End, double &Length,
|
||||||
|
std::string &Error) const;
|
||||||
double m_extend_length{25.0};
|
double m_extend_length{25.0};
|
||||||
double m_extend_radius{300.0};
|
double m_extend_radius{300.0};
|
||||||
bool extend_snap(editor_track::snap_target &Target) const;
|
bool extend_snap(editor_track::snap_target &Target) const;
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
// terrainturn <palette entry> <degrees> the textures of the palette entry turned
|
// terrainturn <palette entry> <degrees> the textures of the palette entry turned
|
||||||
// terrainwater <index> <material> [size] material and repeat of a body of water; terrainwaterdefault <material>
|
// terrainwater <index> <material> [size] material and repeat of a body of water; terrainwaterdefault <material>
|
||||||
// orthophoto <north> <east> <radius> <fit 0|1> [in scene 0|1] the imagery layer on, placed and fitted as given
|
// orthophoto <north> <east> <radius> <fit 0|1> [in scene 0|1] the imagery layer on, placed and fitted as given
|
||||||
|
// join <x> <z> <x> <z> [radius] [variant] [transitions 0|1] the free ends at the points joined, as a drag from one to the other
|
||||||
|
// layto <x> <z> <x> <z> <layout 0..3> <radius> from the free end at the first point towards the second, as a drag in the layout
|
||||||
|
// joints <x> <z>... the joints check over the scenery, the issues around the points logged
|
||||||
// screenshot
|
// screenshot
|
||||||
// save
|
// save
|
||||||
// log <text>
|
// log <text>
|
||||||
@@ -971,6 +974,99 @@ void editor_mode::selftest_step()
|
|||||||
else
|
else
|
||||||
WriteLog("SELFTEST gauge " + name + ": no such track");
|
WriteLog("SELFTEST gauge " + name + ": no such track");
|
||||||
}
|
}
|
||||||
|
else if (command == "join" || command == "layto")
|
||||||
|
{
|
||||||
|
glm::dvec3 a{0.0}, b{0.0};
|
||||||
|
words >> a.x >> a.z >> b.x >> b.z;
|
||||||
|
auto const from{editor_track::find_free_end(nullptr, 1, a, 3.0, {})};
|
||||||
|
extend_tool tool;
|
||||||
|
if (from.track == nullptr || false == free_end(*from.track, from.point.path, from.point.kind == editor_track::point_kind::end, tool))
|
||||||
|
{
|
||||||
|
WriteLog("SELFTEST " + command + ": no free end at the first point");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::vector<segment_data> pieces;
|
||||||
|
std::string error;
|
||||||
|
double length{0.0};
|
||||||
|
if (command == "join")
|
||||||
|
{
|
||||||
|
double radius{0.0};
|
||||||
|
int variant{0}, transitions{0};
|
||||||
|
words >> radius >> variant >> transitions;
|
||||||
|
auto const to{editor_track::find_free_end(nullptr, 1, b, 3.0, {from.track})};
|
||||||
|
if (to.track == nullptr)
|
||||||
|
{
|
||||||
|
WriteLog("SELFTEST join: no free end at the second point");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_join_radius = radius;
|
||||||
|
m_join_variant = variant;
|
||||||
|
m_lay.transitions = transitions != 0;
|
||||||
|
pieces = join_pieces(tool.track, from.point, tool.point, tool.direction, to, length, error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int layout{0};
|
||||||
|
words >> layout >> m_lay.radius;
|
||||||
|
m_track_layout = static_cast<track_layout>(std::clamp(layout, 0, 3));
|
||||||
|
m_extend = tool;
|
||||||
|
m_extend.active = true;
|
||||||
|
m_extend.mouse = {b.x, tool.point.y, b.z};
|
||||||
|
m_extend.snap = {};
|
||||||
|
pieces = extend_pieces();
|
||||||
|
m_extend.active = false;
|
||||||
|
}
|
||||||
|
std::vector<TTrack *> created;
|
||||||
|
for (auto const &piece : pieces)
|
||||||
|
created.push_back(editor_track::create_path(*tool.track, piece));
|
||||||
|
editor_track::commit(created);
|
||||||
|
if (false == created.empty())
|
||||||
|
push_track_snapshot({}, created);
|
||||||
|
char const *const kinds[]{"none", "straight", "arc", "curve", "reverse curve", "free curve"};
|
||||||
|
auto const &join{m_join_last};
|
||||||
|
WriteLog(format("SELFTEST %s: %s, %zu paths, R %.1f m, cant %.0f mm, transitions %.1f m, V %.0f km/h, variants %d%s", command.c_str(), kinds[static_cast<int>(join.kind)], pieces.size(), join.radius, join.cant,
|
||||||
|
join.transition, join.speed, join.variants, error.empty() ? "" : (", " + error).c_str()));
|
||||||
|
auto heading{tool.direction};
|
||||||
|
glm::dvec3 previous{tool.point};
|
||||||
|
for (auto const *track : created)
|
||||||
|
{
|
||||||
|
auto const &path{track->m_paths.front()};
|
||||||
|
geometry::bezier const curve{path};
|
||||||
|
auto const begin{glm::normalize(geometry::plan_of(curve.first(0.0)))};
|
||||||
|
auto const finish{glm::normalize(geometry::plan_of(curve.first(1.0)))};
|
||||||
|
WriteLog(format("SELFTEST %s path %s: L %.2f m, R %.1f m, rolls %.2f %.2f, kink %.4f deg, step %.4f m", command.c_str(), track->name().c_str(), curve.plan_length(), static_cast<double>(path.radius),
|
||||||
|
static_cast<double>(path.rolls[0]), static_cast<double>(path.rolls[1]), glm::degrees(std::abs(geometry::signed_angle(heading, begin))), glm::distance(previous, path.points[segment_data::point::start])));
|
||||||
|
heading = finish;
|
||||||
|
previous = path.points[segment_data::point::end];
|
||||||
|
}
|
||||||
|
if (false == created.empty())
|
||||||
|
WriteLog(format("SELFTEST %s end: %.3f m from the target", command.c_str(), geometry::plan_distance(previous, b)));
|
||||||
|
}
|
||||||
|
else if (command == "joints")
|
||||||
|
{
|
||||||
|
std::vector<glm::dvec3> points;
|
||||||
|
for (glm::dvec3 point{0.0}; words >> point.x >> point.z;)
|
||||||
|
points.push_back(point);
|
||||||
|
auto const scope{m_joints.scope};
|
||||||
|
m_joints.scope = 1;
|
||||||
|
joints_start();
|
||||||
|
while (m_joints.next < m_joints.queue.size())
|
||||||
|
joints_step();
|
||||||
|
m_joints.scope = scope;
|
||||||
|
char const *const kinds[]{"gap", "step", "kink", "cant jump", "grade break", "free end"};
|
||||||
|
int around{0};
|
||||||
|
for (auto const &issue : m_joints.issues)
|
||||||
|
{
|
||||||
|
if (issue.type == joint_issue::kind::open)
|
||||||
|
continue;
|
||||||
|
if (std::none_of(points.begin(), points.end(), [&](glm::dvec3 const &Point) { return geometry::plan_distance(Point, issue.position) < 5.0; }))
|
||||||
|
continue;
|
||||||
|
++around;
|
||||||
|
WriteLog(format("SELFTEST joints: %s %.3f at %.1f %.1f, %s / %s", kinds[static_cast<int>(issue.type)], issue.value, issue.position.x, issue.position.z, issue.track != nullptr ? issue.track->name().c_str() : "-",
|
||||||
|
issue.other != nullptr ? issue.other->name().c_str() : "-"));
|
||||||
|
}
|
||||||
|
WriteLog(format("SELFTEST joints: %d issues around the points, %zu in all", around, m_joints.issues.size()));
|
||||||
|
}
|
||||||
else if (command == "deletetrack")
|
else if (command == "deletetrack")
|
||||||
{
|
{
|
||||||
delete_selected_track();
|
delete_selected_track();
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ http:
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -182,6 +183,37 @@ segment_data turned_around(segment_data const &Path)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double joint_roll(TTrack const &Track, editor_track::point_ref const &Point)
|
||||||
|
{
|
||||||
|
if (Point.path < 0 || Point.path >= static_cast<int>(Track.m_paths.size()))
|
||||||
|
return 0.0;
|
||||||
|
auto const &path{Track.m_paths[Point.path]};
|
||||||
|
return Point.kind == editor_track::point_kind::start ? path.rolls[0] : -path.rolls[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<segment_data> straight_piece(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Length)
|
||||||
|
{
|
||||||
|
if (Length < 0.5)
|
||||||
|
return {};
|
||||||
|
segment_data path;
|
||||||
|
path.points[segment_data::point::start] = Point;
|
||||||
|
path.points[segment_data::point::end] = {Point.x + Direction.x * Length, Point.y + Grade * Length, Point.z + Direction.y * Length};
|
||||||
|
return {path};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<segment_data> arc_through(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, glm::dvec2 const &Offset)
|
||||||
|
{
|
||||||
|
auto const along{glm::dot(Offset, Direction)};
|
||||||
|
auto const lateral{cross(Direction, Offset)};
|
||||||
|
auto const chord{glm::length(Offset)};
|
||||||
|
if (along <= 0.0 || chord < 0.5)
|
||||||
|
return {};
|
||||||
|
if (std::abs(lateral) < 0.005 * chord + 0.05)
|
||||||
|
return straight_piece(Point, Direction, Grade, along);
|
||||||
|
auto const angle{2.0 * std::atan2(std::abs(lateral), along)};
|
||||||
|
return arc_pieces(Point, Direction, Grade, chord / (2.0 * std::sin(angle * 0.5)), angle, lateral > 0.0 ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
// curvature of the shape at Chainage, positive turning from x towards z
|
// curvature of the shape at Chainage, positive turning from x towards z
|
||||||
double curvature_at(alignment::result const &Shape, double const Chainage)
|
double curvature_at(alignment::result const &Shape, double const Chainage)
|
||||||
{
|
{
|
||||||
@@ -3466,7 +3498,11 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
|
|||||||
hints = {{"LMB", "start of the new track, on the ground or at a free end"}, {"Drag from a free end", "straight along it, to another free end: a curve joining them"}, {"Parallel", "first select a straight, then a second track beside it in the panel"}, {"Esc", "stop laying"}};
|
hints = {{"LMB", "start of the new track, on the ground or at a free end"}, {"Drag from a free end", "straight along it, to another free end: a curve joining them"}, {"Parallel", "first select a straight, then a second track beside it in the panel"}, {"Esc", "stop laying"}};
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hints = {{"LMB", m_lay.points.size() == 1 ? "end of the straight, or a vertex of a curve" : "next vertex of the curve"}, {"LMB on a free end", "join and lay"}, {"Enter", "lay up to the cursor"}, {"Backspace", "take back the point"}, {"Esc", "cancel"}};
|
bool const chained{m_lay.points.size() == 1 && m_lay.start.track != nullptr};
|
||||||
|
hints = {{"LMB", chained ? "lay the shape up to here and go on from its end" : m_lay.points.size() == 1 ? "end of the straight, or a vertex of a curve" : "next vertex of the curve"}, {"LMB on a free end", "join and lay"}, {"Ctrl+wheel", "radius"}};
|
||||||
|
if (chained || m_lay.mouse_snap.track != nullptr)
|
||||||
|
hints.push_back({"Shift+wheel", "arc / straight, at a free end: another shape"});
|
||||||
|
hints.insert(hints.end(), {{"Enter", "lay up to the cursor"}, {"Backspace", "take back the point"}, {"Esc", "cancel"}});
|
||||||
if (glm::dvec2 heading; lay_heading(heading))
|
if (glm::dvec2 heading; lay_heading(heading))
|
||||||
hints.insert(hints.begin() + 1, {"Shift", "straight on in the direction of the track"});
|
hints.insert(hints.begin() + 1, {"Shift", "straight on in the direction of the track"});
|
||||||
}
|
}
|
||||||
@@ -3477,10 +3513,8 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
|
|||||||
auto const step{std::min<std::size_t>(m_straights.detour.size(), 3)};
|
auto const step{std::min<std::size_t>(m_straights.detour.size(), 3)};
|
||||||
hints = {{"Shift+click", format("point %zu of 4: %s", step + 1, steps[step])}, {"Esc", "cancel"}};
|
hints = {{"Shift+click", format("point %zu of 4: %s", step + 1, steps[step])}, {"Esc", "cancel"}};
|
||||||
}
|
}
|
||||||
else if (m_extend.active && m_extend_freehand)
|
|
||||||
hints = {{"Drag", "curve through the cursor, along the end: straight"}, {"Ctrl", "straight"}, {"Release", "build"}, {"Esc", "cancel"}};
|
|
||||||
else if (m_extend.active)
|
else if (m_extend.active)
|
||||||
hints = {{"Drag", "straight along the end, to a free end: a curve joining it"}, {"Shift", "straight, joins nothing"}, {"Release", "build"}, {"Esc", "cancel"}};
|
hints = {{"Drag", "the shape chosen towards the cursor, to a free end: joins it"}, {"Ctrl+wheel", "radius"}, {"Shift+wheel", "arc / straight, at a free end: another shape"}, {"Shift", "straight, joins nothing"}, {"Release", "build"}, {"Esc", "cancel"}};
|
||||||
else if (m_switch.placing)
|
else if (m_switch.placing)
|
||||||
hints = {{"Drag along", "direction of the switch"}, {"Drag sideways", "side of the diverging track"}, {"Esc", "cancel"}};
|
hints = {{"Drag along", "direction of the switch"}, {"Drag sideways", "side of the diverging track"}, {"Esc", "cancel"}};
|
||||||
else if (m_track_tab == track_tab::lay)
|
else if (m_track_tab == track_tab::lay)
|
||||||
@@ -3605,7 +3639,7 @@ std::string editor_mode::track_readout() const
|
|||||||
auto text{radius > 0.0 ? format("L %.2f m R %.0f m", length, radius) : format("L %.2f m straight", length)};
|
auto text{radius > 0.0 ? format("L %.2f m R %.0f m", length, radius) : format("L %.2f m straight", length)};
|
||||||
if (snapped)
|
if (snapped)
|
||||||
text += "\njoins " + name(target.track);
|
text += "\njoins " + name(target.track);
|
||||||
return text;
|
return text + "\n" + track_shaping(snapped);
|
||||||
}
|
}
|
||||||
if (m_lay.active)
|
if (m_lay.active)
|
||||||
{
|
{
|
||||||
@@ -3634,6 +3668,10 @@ std::string editor_mode::track_readout() const
|
|||||||
text += "\njoins " + name(lay.mouse_snap.track);
|
text += "\njoins " + name(lay.mouse_snap.track);
|
||||||
if (false == lay.preview_error.empty())
|
if (false == lay.preview_error.empty())
|
||||||
text += "\n" + lay.preview_error;
|
text += "\n" + lay.preview_error;
|
||||||
|
if (lay.points.size() == 1 && (lay.start.track != nullptr || lay.mouse_snap.track != nullptr))
|
||||||
|
text += "\n" + track_shaping(lay.mouse_snap.track != nullptr);
|
||||||
|
else if (false == lay.points.empty())
|
||||||
|
text += "\n" + format(STR_C("R %.0f m Ctrl+wheel: radius"), lay.radius);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
auto const &state{m_straights};
|
auto const &state{m_straights};
|
||||||
@@ -3697,6 +3735,32 @@ std::string editor_mode::track_readout() const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string editor_mode::track_shaping(bool const Joined) const
|
||||||
|
{
|
||||||
|
if (false == Joined)
|
||||||
|
{
|
||||||
|
char const *const names[]{"Arc, then straight", "Straight, then arc", "Arc through the cursor", "Straight"};
|
||||||
|
auto text{std::string{STR_C(names[static_cast<int>(m_track_layout)])}};
|
||||||
|
if (m_track_layout == track_layout::arc_straight || m_track_layout == track_layout::straight_arc)
|
||||||
|
text += format(" R %.0f m", m_lay.radius);
|
||||||
|
return text + "\n" + STR("Ctrl+wheel: radius, Shift+wheel: arc / straight");
|
||||||
|
}
|
||||||
|
auto const &join{m_join_last};
|
||||||
|
char const *const kinds[]{"", "straight", "arc", "curve", "reverse curve", "free curve"};
|
||||||
|
std::string text{STR_C(kinds[static_cast<int>(join.kind)])};
|
||||||
|
if (join.radius > 0.0)
|
||||||
|
text += format(" R %.0f m", join.radius) + (m_join_radius > 0.0 ? std::string{} : std::string{" "} + STR("(auto)"));
|
||||||
|
if (join.cant > 0.0)
|
||||||
|
text += format(STR_C(" cant %.0f mm"), join.cant);
|
||||||
|
if (join.transition > 0.0)
|
||||||
|
text += format(STR_C(" transitions %.0f m"), join.transition);
|
||||||
|
if (join.speed > 0.0 && (join.cant > 0.0 || join.transition > 0.0))
|
||||||
|
text += format(" V %.0f km/h", join.speed);
|
||||||
|
if (join.variants > 1)
|
||||||
|
text += format(" %d/%d", (m_join_variant % join.variants + join.variants) % join.variants + 1, join.variants);
|
||||||
|
return text + "\n" + STR("Ctrl+wheel: radius, Shift+wheel: another shape");
|
||||||
|
}
|
||||||
|
|
||||||
void editor_mode::draw_track_hints()
|
void editor_mode::draw_track_hints()
|
||||||
{
|
{
|
||||||
ImGuiIO const &io = ImGui::GetIO();
|
ImGuiIO const &io = ImGui::GetIO();
|
||||||
@@ -3775,13 +3839,24 @@ void editor_mode::update_build_tools()
|
|||||||
auto const &io{ImGui::GetIO()};
|
auto const &io{ImGui::GetIO()};
|
||||||
glm::vec2 const cursor{io.MousePos.x, io.MousePos.y};
|
glm::vec2 const cursor{io.MousePos.x, io.MousePos.y};
|
||||||
m_straights.detour_mouse = ground;
|
m_straights.detour_mouse = ground;
|
||||||
|
if (false == io.KeyShift)
|
||||||
|
m_shift_wheel = false;
|
||||||
|
auto const joining = [&](editor_track::snap_target const &Target) {
|
||||||
|
if (Target.track == m_join_track && Target.point == m_join_point)
|
||||||
|
return;
|
||||||
|
m_join_track = Target.track;
|
||||||
|
m_join_point = Target.point;
|
||||||
|
m_join_radius = 0.0;
|
||||||
|
m_join_variant = 0;
|
||||||
|
};
|
||||||
if (m_extend.active)
|
if (m_extend.active)
|
||||||
{
|
{
|
||||||
screen_projection const projection;
|
screen_projection const projection;
|
||||||
glm::dvec3 level;
|
glm::dvec3 level;
|
||||||
m_extend.mouse = projection.on_level(io.MousePos, m_extend.point.y, level) && plan_distance(level, m_extend.point) < static_cast<double>(kMaxPlacementDistance) ? level : ground;
|
m_extend.mouse = projection.on_level(io.MousePos, m_extend.point.y, level) && plan_distance(level, m_extend.point) < static_cast<double>(kMaxPlacementDistance) ? level : ground;
|
||||||
typed_extend();
|
typed_extend();
|
||||||
m_extend.snap = m_typed.empty() && false == io.KeyShift && m_extend.track != nullptr ? snap_free_end(m_extend.mouse, cursor, m_extend.track, m_extend.track->iCategoryFlag & 15, {m_extend.track}) : editor_track::snap_target{};
|
m_extend.snap = m_typed.empty() && false == shift_straight() && m_extend.track != nullptr ? snap_free_end(m_extend.mouse, cursor, m_extend.track, m_extend.track->iCategoryFlag & 15, {m_extend.track}) : editor_track::snap_target{};
|
||||||
|
joining(m_extend.snap);
|
||||||
}
|
}
|
||||||
if (m_switch.placing)
|
if (m_switch.placing)
|
||||||
m_switch.mouse = cursor_level(m_switch.point.y);
|
m_switch.mouse = cursor_level(m_switch.point.y);
|
||||||
@@ -3800,7 +3875,7 @@ void editor_mode::update_build_tools()
|
|||||||
else if (false == lay_projection.on_level(io.MousePos, height, mouse))
|
else if (false == lay_projection.on_level(io.MousePos, height, mouse))
|
||||||
mouse = ground;
|
mouse = ground;
|
||||||
glm::dvec2 heading;
|
glm::dvec2 heading;
|
||||||
bool const straight{io.KeyShift && lay_heading(heading)};
|
bool const straight{shift_straight() && lay_heading(heading)};
|
||||||
if (straight)
|
if (straight)
|
||||||
{
|
{
|
||||||
auto const &last{lay.points.back()};
|
auto const &last{lay.points.back()};
|
||||||
@@ -3809,6 +3884,8 @@ void editor_mode::update_build_tools()
|
|||||||
}
|
}
|
||||||
lay.mouse = typed_lay(mouse);
|
lay.mouse = typed_lay(mouse);
|
||||||
lay.mouse_snap = straight ? editor_track::snap_target{} : m_typed.empty() ? snap_free_end(lay.mouse, cursor, nullptr, kRailCategory, {}) : editor_track::find_free_end(nullptr, kRailCategory, lay.mouse, kLaySnapRadius, {});
|
lay.mouse_snap = straight ? editor_track::snap_target{} : m_typed.empty() ? snap_free_end(lay.mouse, cursor, nullptr, kRailCategory, {}) : editor_track::find_free_end(nullptr, kRailCategory, lay.mouse, kLaySnapRadius, {});
|
||||||
|
if (false == m_extend.active)
|
||||||
|
joining(lay.points.empty() ? editor_track::snap_target{} : lay.mouse_snap);
|
||||||
if (lay.points.empty())
|
if (lay.points.empty())
|
||||||
return;
|
return;
|
||||||
auto points{lay.points};
|
auto points{lay.points};
|
||||||
@@ -4115,40 +4192,12 @@ std::vector<segment_data> editor_mode::extend_pieces() const
|
|||||||
editor_track::snap_target target;
|
editor_track::snap_target target;
|
||||||
if (extend_snap(target))
|
if (extend_snap(target))
|
||||||
{
|
{
|
||||||
alignment::design design;
|
double length;
|
||||||
design.norms = m_route.design.norms;
|
std::string error;
|
||||||
design.shape = m_route.design.shape;
|
return join_pieces(tool.track, {tool.path, tool.atend ? editor_track::point_kind::end : editor_track::point_kind::start}, tool.point, tool.direction, target, length, error);
|
||||||
design.arc_piece_angle = 90.0;
|
|
||||||
design.start = tool.point;
|
|
||||||
design.end = target.position;
|
|
||||||
design.start_direction = tool.direction;
|
|
||||||
design.end_direction = glm::normalize(plan_of(target.direction));
|
|
||||||
design.start_grade = tool.grade;
|
|
||||||
design.end_grade = grade_of(target.direction);
|
|
||||||
auto const speed{editor_track::velocity(*tool.track)};
|
|
||||||
design.speed = speed > 0.0 ? speed : 100.0;
|
|
||||||
if (false == alignment::collinear(design))
|
|
||||||
{
|
|
||||||
alignment::vertex vertex;
|
|
||||||
vertex.radius = 100000.0;
|
|
||||||
auto const recommended{alignment::recommend(design.speed, 1000.0, design.norms)};
|
|
||||||
vertex.transition_in = vertex.transition_out = recommended.transition;
|
|
||||||
glm::dvec2 intersection;
|
|
||||||
if (alignment::tangent_intersection(design, intersection))
|
|
||||||
{
|
|
||||||
design.vertices.push_back(vertex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
vertex.offset = plan_distance(design.start, design.end) / 3.0;
|
|
||||||
design.vertices = {vertex, vertex};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// a track which doesn't fit to the free end isn't built towards the cursor instead, it'd miss the end
|
|
||||||
auto const shape{alignment::compute(design)};
|
|
||||||
return shape.valid ? alignment::pieces(shape, design, 0) : result;
|
|
||||||
}
|
}
|
||||||
if (false == ImGui::GetIO().KeyShift && tool.track != nullptr && tool.track->eType == tt_Switch && tool.track->m_paths.size() > 1 && tool.path == 1 && tool.atend)
|
m_join_last = {};
|
||||||
|
if (false == shift_straight() && tool.track != nullptr && tool.track->eType == tt_Switch && tool.track->m_paths.size() > 1 && tool.path == 1 && tool.atend)
|
||||||
{
|
{
|
||||||
auto const &diverging{tool.track->m_paths[1]};
|
auto const &diverging{tool.track->m_paths[1]};
|
||||||
bezier const curve{diverging};
|
bezier const curve{diverging};
|
||||||
@@ -4170,26 +4219,156 @@ std::vector<segment_data> editor_mode::extend_pieces() const
|
|||||||
return pieces;
|
return pieces;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
|
if (shift_straight())
|
||||||
|
return straight_piece(tool.point, tool.direction, tool.grade, glm::dot(glm::dvec2{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z}, tool.direction));
|
||||||
|
auto radius{m_lay.radius};
|
||||||
|
if (auto const values{typed_values()}; false == values.empty() && (m_track_layout == track_layout::arc_straight || m_track_layout == track_layout::straight_arc) && std::abs(values[0]) >= 1.0)
|
||||||
|
radius = std::abs(values[0]);
|
||||||
|
return layout_pieces(tool.point, tool.direction, tool.grade, tool.mouse, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<segment_data> editor_mode::layout_pieces(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, glm::dvec3 const &Mouse, double const Radius) const
|
||||||
|
{
|
||||||
|
glm::dvec2 const offset{Mouse.x - Point.x, Mouse.z - Point.z};
|
||||||
|
auto const along{glm::dot(offset, Direction)};
|
||||||
|
auto const lateral{cross(Direction, offset)};
|
||||||
auto const chord{glm::length(offset)};
|
auto const chord{glm::length(offset)};
|
||||||
if (chord < 0.5)
|
if (chord < 0.5)
|
||||||
return result;
|
return {};
|
||||||
auto const &direction{tool.direction};
|
if (m_track_layout == track_layout::straight)
|
||||||
auto const along{glm::dot(offset, direction)};
|
return straight_piece(Point, Direction, Grade, along);
|
||||||
auto const lateral{direction.x * offset.y - direction.y * offset.x};
|
if (m_track_layout == track_layout::arc || std::abs(lateral) < 0.005 * chord + 0.05)
|
||||||
auto const height = [&](double const Distance) { return tool.point.y + tool.grade * Distance; };
|
return arc_through(Point, Direction, Grade, offset);
|
||||||
if (false == m_extend_freehand || ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeyShift || std::abs(lateral) < 0.005 * chord + 0.05)
|
int const side{lateral > 0.0 ? 1 : -1};
|
||||||
|
glm::dvec2 const normal{-Direction.y * side, Direction.x * side};
|
||||||
|
auto const radius{std::max(1.0, Radius)};
|
||||||
|
if (radius >= chord * chord / (2.0 * std::abs(lateral)))
|
||||||
|
return arc_through(Point, Direction, Grade, offset);
|
||||||
|
if (m_track_layout == track_layout::arc_straight)
|
||||||
{
|
{
|
||||||
if (along < 0.5)
|
auto const centre{plan_of(Point) + normal * radius};
|
||||||
return result;
|
auto const towards{plan_of(Mouse) - centre};
|
||||||
segment_data path;
|
auto const distance{glm::length(towards)};
|
||||||
path.points[segment_data::point::start] = tool.point;
|
auto const spread{std::acos(std::clamp(radius / distance, -1.0, 1.0))};
|
||||||
path.points[segment_data::point::end] = {tool.point.x + direction.x * along, height(along), tool.point.z + direction.y * along};
|
auto const radial0{plan_of(Point) - centre};
|
||||||
result.push_back(path);
|
double rest{-1.0};
|
||||||
return result;
|
double angle{0.0};
|
||||||
|
for (auto const sign : {1.0, -1.0})
|
||||||
|
{
|
||||||
|
auto const radial{turned(towards / distance, sign * spread) * radius};
|
||||||
|
auto turn{side * signed_angle(radial0, radial)};
|
||||||
|
if (turn < 0.0)
|
||||||
|
turn += 2.0 * glm::pi<double>();
|
||||||
|
auto const heading{turned(Direction, side * turn)};
|
||||||
|
auto const remaining{glm::dot(plan_of(Mouse) - centre - radial, heading)};
|
||||||
|
if (remaining > 0.0 && (rest < 0.0 || turn < angle))
|
||||||
|
{
|
||||||
|
rest = remaining;
|
||||||
|
angle = turn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rest < 0.0 || angle > glm::pi<double>())
|
||||||
|
return {};
|
||||||
|
auto pieces{arc_pieces(Point, Direction, Grade, radius, angle, side)};
|
||||||
|
auto const tail{straight_piece(pieces.back().points[segment_data::point::end], turned(Direction, side * angle), Grade, rest)};
|
||||||
|
pieces.insert(pieces.end(), tail.begin(), tail.end());
|
||||||
|
return pieces;
|
||||||
}
|
}
|
||||||
auto const angle{2.0 * std::atan2(std::abs(lateral), along)};
|
auto const across{radius - std::abs(lateral)};
|
||||||
return arc_pieces(tool.point, direction, tool.grade, chord / (2.0 * std::sin(angle * 0.5)), angle, lateral > 0.0 ? 1 : -1);
|
if (std::abs(lateral) > 2.0 * radius)
|
||||||
|
return arc_through(Point, Direction, Grade, offset);
|
||||||
|
auto const lead{along - std::sqrt(std::max(0.0, radius * radius - across * across))};
|
||||||
|
if (lead < 0.5)
|
||||||
|
return arc_through(Point, Direction, Grade, offset);
|
||||||
|
auto const angle{2.0 * std::atan2(std::abs(lateral), along - lead)};
|
||||||
|
if (angle > glm::pi<double>())
|
||||||
|
return {};
|
||||||
|
auto pieces{straight_piece(Point, Direction, Grade, lead)};
|
||||||
|
auto const arc{arc_pieces(pieces.back().points[segment_data::point::end], Direction, Grade, radius, angle, side)};
|
||||||
|
pieces.insert(pieces.end(), arc.begin(), arc.end());
|
||||||
|
return pieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
double editor_mode::design_speed(TTrack const *From, TTrack const *To) const
|
||||||
|
{
|
||||||
|
double speed{0.0};
|
||||||
|
for (auto const *track : {From, To})
|
||||||
|
{
|
||||||
|
if (track == nullptr)
|
||||||
|
continue;
|
||||||
|
auto const velocity{editor_track::velocity(*track)};
|
||||||
|
if (velocity > 0.0)
|
||||||
|
speed = speed > 0.0 ? std::min(speed, velocity) : velocity;
|
||||||
|
}
|
||||||
|
if (speed <= 0.0)
|
||||||
|
speed = m_lay.style.velocity > 0.0 ? m_lay.style.velocity : 100.0;
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<segment_data> editor_mode::join_pieces(TTrack const *From, editor_track::point_ref const &Frompoint, glm::dvec3 const &Start, glm::dvec2 const &Direction, editor_track::snap_target const &End, double &Length,
|
||||||
|
std::string &Error) const
|
||||||
|
{
|
||||||
|
alignment::join_request request;
|
||||||
|
request.start.position = Start;
|
||||||
|
request.start.direction = Direction;
|
||||||
|
request.start.roll = From != nullptr ? -joint_roll(*From, Frompoint) : 0.0;
|
||||||
|
request.end.position = End.position;
|
||||||
|
request.end.direction = plan_of(End.direction);
|
||||||
|
request.end.roll = End.track != nullptr ? joint_roll(*End.track, End.point) : 0.0;
|
||||||
|
request.radius = m_join_radius;
|
||||||
|
request.variant = m_join_variant;
|
||||||
|
request.transitions = m_lay.transitions;
|
||||||
|
request.speed = design_speed(From, End.track);
|
||||||
|
request.kink = m_joints.angle * 0.5;
|
||||||
|
request.shape = m_route.design.shape;
|
||||||
|
request.norms = m_route.design.norms;
|
||||||
|
auto result{alignment::join(request)};
|
||||||
|
if (m_lay.transitions && result.kind != alignment::join_kind::straight && result.kind != alignment::join_kind::arc && glm::length(request.end.direction) > 1e-9)
|
||||||
|
{
|
||||||
|
auto const grade{(End.position.y - Start.y) / std::max(1.0, plan_distance(Start, End.position))};
|
||||||
|
auto const in{From != nullptr && From->eType == tt_Normal ? ease_out_of(*From, Start, grade, request.speed, request.norms) : easing{}};
|
||||||
|
auto const out{End.track != nullptr && End.track->eType == tt_Normal ? ease_out_of(*End.track, End.position, -grade, request.speed, request.norms) : easing{}};
|
||||||
|
if (false == in.pieces.empty() || false == out.pieces.empty())
|
||||||
|
{
|
||||||
|
auto middle{request};
|
||||||
|
if (false == in.pieces.empty())
|
||||||
|
{
|
||||||
|
middle.start.position = in.end;
|
||||||
|
middle.start.direction = in.direction;
|
||||||
|
middle.start.roll = in.pieces.back().rolls[1];
|
||||||
|
}
|
||||||
|
if (false == out.pieces.empty())
|
||||||
|
{
|
||||||
|
middle.end.position = out.end;
|
||||||
|
middle.end.direction = -out.direction;
|
||||||
|
middle.end.roll = -out.pieces.back().rolls[1];
|
||||||
|
}
|
||||||
|
auto const gap{plan_of(middle.end.position) - plan_of(middle.start.position)};
|
||||||
|
if (glm::length(gap) > 5.0 && glm::dot(glm::normalize(middle.start.direction), gap) > 0.0 && glm::dot(glm::normalize(middle.end.direction), gap) > 0.0)
|
||||||
|
{
|
||||||
|
auto eased{alignment::join(middle)};
|
||||||
|
if (eased.kind != alignment::join_kind::none && eased.kind != alignment::join_kind::bezier)
|
||||||
|
{
|
||||||
|
std::vector<segment_data> pieces{in.pieces};
|
||||||
|
pieces.insert(pieces.end(), eased.pieces.begin(), eased.pieces.end());
|
||||||
|
for (auto piece{out.pieces.rbegin()}; piece != out.pieces.rend(); ++piece)
|
||||||
|
pieces.push_back(turned_around(*piece));
|
||||||
|
eased.pieces = std::move(pieces);
|
||||||
|
eased.length += (in.pieces.empty() ? 0.0 : in.length) + (out.pieces.empty() ? 0.0 : out.length);
|
||||||
|
result = std::move(eased);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Length = result.length;
|
||||||
|
Error = result.kind == alignment::join_kind::bezier ? std::string{STR_C("No curve fits between the ends, they are joined with a free curve")} : std::string{};
|
||||||
|
m_join_last = result;
|
||||||
|
return result.pieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool editor_mode::shift_straight() const
|
||||||
|
{
|
||||||
|
return ImGui::GetIO().KeyShift && false == m_shift_wheel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void editor_mode::finish_extend()
|
void editor_mode::finish_extend()
|
||||||
@@ -4299,8 +4478,70 @@ void editor_mode::render_extend_ui()
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(STR_C("Curve right##extend"), ImVec2(100.0f, 0.0f)))
|
if (ImGui::Button(STR_C("Curve right##extend"), ImVec2(100.0f, 0.0f)))
|
||||||
extend_add(-1);
|
extend_add(-1);
|
||||||
ImGui::Checkbox(STR_C("Freehand"), &m_extend_freehand);
|
ImGui::PushID("extend");
|
||||||
item_tooltip("Dragging from a free end draws a curve through the cursor; off: it draws a straight along the end");
|
render_layout_combo();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
void editor_mode::render_layout_combo()
|
||||||
|
{
|
||||||
|
char const *const names[]{"Arc, then straight", "Straight, then arc", "Arc through the cursor", "Straight"};
|
||||||
|
auto current{static_cast<int>(m_track_layout)};
|
||||||
|
ImGui::PushItemWidth(200.0f);
|
||||||
|
if (ImGui::BeginCombo(STR_C("Shape from a free end"), STR_C(names[current])))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
if (ImGui::Selectable(STR_C(names[i]), i == current))
|
||||||
|
m_track_layout = static_cast<track_layout>(i);
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
item_tooltip("What a drag or a click from a free end lays towards the cursor; Shift+wheel changes it, Ctrl+wheel the radius");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool editor_mode::track_wheel(double const Offset)
|
||||||
|
{
|
||||||
|
auto const &io{ImGui::GetIO()};
|
||||||
|
if ((false == io.KeyCtrl && false == io.KeyShift) || Offset == 0.0)
|
||||||
|
return false;
|
||||||
|
bool const laying{m_lay.active && false == m_lay.points.empty()};
|
||||||
|
if (false == m_extend.active && false == laying)
|
||||||
|
return false;
|
||||||
|
int const step{Offset > 0.0 ? 1 : -1};
|
||||||
|
editor_track::snap_target target;
|
||||||
|
bool const joined{m_extend.active ? extend_snap(target) : m_lay.mouse_snap.track != nullptr};
|
||||||
|
static double const radii[]{100.0, 150.0, 190.0, 250.0, 300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1200.0, 1500.0, 2000.0, 3000.0, 4000.0, 6000.0};
|
||||||
|
auto const next_radius = [&](double const Current) {
|
||||||
|
if (step > 0)
|
||||||
|
{
|
||||||
|
for (auto const radius : radii)
|
||||||
|
if (radius > Current * 1.001)
|
||||||
|
return radius;
|
||||||
|
return std::end(radii)[-1];
|
||||||
|
}
|
||||||
|
for (auto radius{std::rbegin(radii)}; radius != std::rend(radii); ++radius)
|
||||||
|
if (*radius < Current * 0.999)
|
||||||
|
return *radius;
|
||||||
|
return radii[0];
|
||||||
|
};
|
||||||
|
if (io.KeyCtrl)
|
||||||
|
{
|
||||||
|
if (false == joined)
|
||||||
|
m_lay.radius = next_radius(m_lay.radius);
|
||||||
|
else if (m_join_radius <= 0.0)
|
||||||
|
m_join_radius = next_radius(m_join_last.radius > 0.0 ? m_join_last.radius : 500.0);
|
||||||
|
else if (step > 0 && m_join_radius >= std::end(radii)[-1])
|
||||||
|
m_join_radius = 0.0;
|
||||||
|
else
|
||||||
|
m_join_radius = next_radius(m_join_radius);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
m_shift_wheel = true;
|
||||||
|
if (joined)
|
||||||
|
m_join_variant += step;
|
||||||
|
else
|
||||||
|
m_track_layout = static_cast<track_layout>((static_cast<int>(m_track_layout) + 4 - step) % 4);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const &Points, editor_track::snap_target const &End, double &Length, std::string &Error) const
|
std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const &Points, editor_track::snap_target const &End, double &Length, std::string &Error) const
|
||||||
@@ -4309,6 +4550,24 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
|||||||
Length = 0.0;
|
Length = 0.0;
|
||||||
if (Points.size() < 2)
|
if (Points.size() < 2)
|
||||||
return {};
|
return {};
|
||||||
|
if (Points.size() == 2 && (End.track != nullptr || tool.start.track != nullptr))
|
||||||
|
{
|
||||||
|
auto const leaving{tool.start.track != nullptr ? -plan_of(tool.start.direction) : plan_of(Points[1] - Points[0])};
|
||||||
|
if (glm::length(leaving) < 1e-6)
|
||||||
|
{
|
||||||
|
Error = STR_C("Two points lie on one another");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (End.track != nullptr)
|
||||||
|
return join_pieces(tool.start.track, tool.start.point, Points[0], glm::normalize(leaving), End, Length, Error);
|
||||||
|
m_join_last = {};
|
||||||
|
auto const pieces{layout_pieces(Points[0], glm::normalize(leaving), -grade_of(tool.start.direction), Points[1], tool.radius)};
|
||||||
|
for (auto const &piece : pieces)
|
||||||
|
Length += bezier{piece}.plan_length();
|
||||||
|
if (pieces.empty())
|
||||||
|
Error = STR_C("The track can't be laid through these points");
|
||||||
|
return pieces;
|
||||||
|
}
|
||||||
auto const startdirection{tool.start.track != nullptr ? -plan_of(tool.start.direction) : plan_of(Points[1] - Points[0])};
|
auto const startdirection{tool.start.track != nullptr ? -plan_of(tool.start.direction) : plan_of(Points[1] - Points[0])};
|
||||||
auto const enddirection{End.track != nullptr ? plan_of(End.direction) : plan_of(Points.back() - Points[Points.size() - 2])};
|
auto const enddirection{End.track != nullptr ? plan_of(End.direction) : plan_of(Points.back() - Points[Points.size() - 2])};
|
||||||
if (glm::length(startdirection) < 1e-3 || glm::length(enddirection) < 1e-3)
|
if (glm::length(startdirection) < 1e-3 || glm::length(enddirection) < 1e-3)
|
||||||
@@ -4338,9 +4597,15 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
|||||||
result.pieces.clear();
|
result.pieces.clear();
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
auto const in{eased(tool.start, 0.0, 1.0)};
|
auto const in{tool.transitions ? eased(tool.start, 0.0, 1.0) : easing{}};
|
||||||
auto const out{eased(End, shape.length, -1.0)};
|
auto const out{tool.transitions ? eased(End, shape.length, -1.0) : easing{}};
|
||||||
if (false == in.pieces.empty() || false == out.pieces.empty())
|
auto const room = [&]() {
|
||||||
|
auto const from{in.pieces.empty() ? Points.front() : in.end};
|
||||||
|
auto const to{out.pieces.empty() ? Points.back() : out.end};
|
||||||
|
auto const gap{plan_of(to) - plan_of(from)};
|
||||||
|
return glm::length(gap) > 5.0 && glm::dot(in.pieces.empty() ? design.start_direction : in.direction, gap) > 0.0 && glm::dot(out.pieces.empty() ? design.end_direction : -out.direction, gap) > 0.0;
|
||||||
|
};
|
||||||
|
if ((false == in.pieces.empty() || false == out.pieces.empty()) && room())
|
||||||
{
|
{
|
||||||
auto points{Points};
|
auto points{Points};
|
||||||
auto starting{design.start_direction};
|
auto starting{design.start_direction};
|
||||||
@@ -4380,7 +4645,7 @@ alignment::result editor_mode::lay_shape(std::vector<glm::dvec3> const &Points,
|
|||||||
design = {};
|
design = {};
|
||||||
design.norms = m_route.design.norms;
|
design.norms = m_route.design.norms;
|
||||||
design.shape = m_route.design.shape;
|
design.shape = m_route.design.shape;
|
||||||
design.speed = tool.style.velocity > 0.0 ? tool.style.velocity : 100.0;
|
design.speed = design_speed(tool.start.track, nullptr);
|
||||||
design.start = Points.front();
|
design.start = Points.front();
|
||||||
design.end = Points.back();
|
design.end = Points.back();
|
||||||
design.start_direction = Startdirection;
|
design.start_direction = Startdirection;
|
||||||
@@ -4388,12 +4653,6 @@ alignment::result editor_mode::lay_shape(std::vector<glm::dvec3> const &Points,
|
|||||||
|
|
||||||
alignment::vertex corner;
|
alignment::vertex corner;
|
||||||
corner.radius = std::max(1.0, tool.radius);
|
corner.radius = std::max(1.0, tool.radius);
|
||||||
if (tool.transitions)
|
|
||||||
{
|
|
||||||
auto const recommended{alignment::recommend(design.speed, corner.radius, design.norms)};
|
|
||||||
corner.cant = recommended.cant;
|
|
||||||
corner.transition_in = corner.transition_out = recommended.transition;
|
|
||||||
}
|
|
||||||
if (Points.size() == 2)
|
if (Points.size() == 2)
|
||||||
{
|
{
|
||||||
if (false == alignment::collinear(design))
|
if (false == alignment::collinear(design))
|
||||||
@@ -4423,7 +4682,7 @@ alignment::result editor_mode::lay_shape(std::vector<glm::dvec3> const &Points,
|
|||||||
design.vertices.back().offset = std::max(1.0, glm::dot(plan_of(Points.back() - Points[Points.size() - 2]), design.end_direction));
|
design.vertices.back().offset = std::max(1.0, glm::dot(plan_of(Points.back() - Points[Points.size() - 2]), design.end_direction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return alignment::compute(design);
|
return alignment::compute_recommended(design, tool.transitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void editor_mode::lay_click()
|
void editor_mode::lay_click()
|
||||||
@@ -4447,8 +4706,31 @@ void editor_mode::lay_click()
|
|||||||
auto const point{placement_on_ground(tool.mouse)};
|
auto const point{placement_on_ground(tool.mouse)};
|
||||||
if (plan_distance(point, tool.points.back()) < 1.0)
|
if (plan_distance(point, tool.points.back()) < 1.0)
|
||||||
return;
|
return;
|
||||||
tool.points.push_back(point);
|
|
||||||
m_typed.clear();
|
m_typed.clear();
|
||||||
|
if (tool.points.size() == 1 && tool.start.track != nullptr)
|
||||||
|
{
|
||||||
|
tool.points.push_back(tool.mouse);
|
||||||
|
lay_finish({});
|
||||||
|
if (false == tool.points.empty())
|
||||||
|
{
|
||||||
|
tool.points.pop_back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *last{dynamic_cast<TTrack *>(m_node)};
|
||||||
|
if (last == nullptr || last->m_paths.empty())
|
||||||
|
return;
|
||||||
|
auto const &path{last->m_paths.front()};
|
||||||
|
editor_track::snap_target next;
|
||||||
|
next.track = last;
|
||||||
|
next.point = {0, editor_track::point_kind::end};
|
||||||
|
next.position = path.points[segment_data::point::end];
|
||||||
|
auto const &control{path.points[segment_data::point::control2]};
|
||||||
|
next.direction = glm::normalize(control != glm::dvec3{} ? control : path.points[segment_data::point::start] - next.position);
|
||||||
|
tool.start = next;
|
||||||
|
tool.points = {next.position};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tool.points.push_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
void editor_mode::lay_finish(editor_track::snap_target const &End)
|
void editor_mode::lay_finish(editor_track::snap_target const &End)
|
||||||
@@ -4569,8 +4851,9 @@ void editor_mode::render_lay_ui()
|
|||||||
tool.radius = std::max(10.0, tool.radius);
|
tool.radius = std::max(10.0, tool.radius);
|
||||||
ImGui::DragScalar(STR_C("Speed (km/h)"), ImGuiDataType_Double, &tool.style.velocity, 1.0f, nullptr, nullptr, "%.0f");
|
ImGui::DragScalar(STR_C("Speed (km/h)"), ImGuiDataType_Double, &tool.style.velocity, 1.0f, nullptr, nullptr, "%.0f");
|
||||||
tool.style.velocity = std::clamp(tool.style.velocity, 0.0, 400.0);
|
tool.style.velocity = std::clamp(tool.style.velocity, 0.0, 400.0);
|
||||||
item_tooltip("Speed limit of the new paths, and the design speed of the transition curves and of the cant");
|
item_tooltip("Speed limit of the new paths, and the design speed of the transition curves and of the cant; the speed of the track joined comes first, and none higher than the radius allows");
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
|
render_layout_combo();
|
||||||
if (false == ImGui::TreeNode(STR_C("More settings")))
|
if (false == ImGui::TreeNode(STR_C("More settings")))
|
||||||
{
|
{
|
||||||
render_lay_status();
|
render_lay_status();
|
||||||
|
|||||||
@@ -731,7 +731,7 @@ char const *editor_mode::typed_meaning() const
|
|||||||
{
|
{
|
||||||
auto const &straights{m_straights};
|
auto const &straights{m_straights};
|
||||||
if (m_extend.active)
|
if (m_extend.active)
|
||||||
return m_extend_freehand && false == ImGui::GetIO().KeyCtrl ? "type the radius, a space, the length" : "type the length";
|
return m_track_layout == track_layout::straight || shift_straight() ? "type the length" : m_track_layout == track_layout::arc ? "type the radius, a space, the length" : "type the radius";
|
||||||
if (straights.dragging)
|
if (straights.dragging)
|
||||||
return straights.handle == 2 ? "type the shift, + to the right" : "type the length";
|
return straights.handle == 2 ? "type the shift, + to the right" : "type the length";
|
||||||
if (straights.tool == 1 && (straights.tool_mouse || straights.tool_dragging))
|
if (straights.tool == 1 && (straights.tool_mouse || straights.tool_dragging))
|
||||||
@@ -800,7 +800,9 @@ void editor_mode::typed_extend()
|
|||||||
return;
|
return;
|
||||||
auto &tool{m_extend};
|
auto &tool{m_extend};
|
||||||
auto const &direction{tool.direction};
|
auto const &direction{tool.direction};
|
||||||
if (false == m_extend_freehand || ImGui::GetIO().KeyCtrl)
|
if (m_track_layout != track_layout::straight && m_track_layout != track_layout::arc && false == shift_straight())
|
||||||
|
return;
|
||||||
|
if (m_track_layout == track_layout::straight || shift_straight())
|
||||||
{
|
{
|
||||||
auto const length{std::max(0.0, values[0])};
|
auto const length{std::max(0.0, values[0])};
|
||||||
tool.mouse = {tool.point.x + direction.x * length, tool.mouse.y, tool.point.z + direction.y * length};
|
tool.mouse = {tool.point.x + direction.x * length, tool.mouse.y, tool.point.z + direction.y * length};
|
||||||
@@ -817,7 +819,7 @@ void editor_mode::typed_extend()
|
|||||||
auto angle{values.size() > 1 ? std::abs(values[1]) / radius : side * geometry::signed_angle(radial, glm::dvec2{tool.mouse.x, tool.mouse.z} - centre)};
|
auto angle{values.size() > 1 ? std::abs(values[1]) / radius : side * geometry::signed_angle(radial, glm::dvec2{tool.mouse.x, tool.mouse.z} - centre)};
|
||||||
if (angle < 0.0)
|
if (angle < 0.0)
|
||||||
angle += 2.0 * glm::pi<double>();
|
angle += 2.0 * glm::pi<double>();
|
||||||
angle = std::clamp(angle, 1e-3, 2.0 * glm::pi<double>() - 0.1);
|
angle = std::clamp(angle, 1e-3, glm::pi<double>());
|
||||||
auto const end{centre + geometry::turned(radial, side * angle)};
|
auto const end{centre + geometry::turned(radial, side * angle)};
|
||||||
tool.mouse = {end.x, tool.mouse.y, end.y};
|
tool.mouse = {end.x, tool.mouse.y, end.y};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ http:
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace alignment
|
namespace alignment
|
||||||
{
|
{
|
||||||
@@ -378,6 +379,7 @@ result fit_between(design const &Design)
|
|||||||
double tangent_in{0.0};
|
double tangent_in{0.0};
|
||||||
double tangent_out{0.0};
|
double tangent_out{0.0};
|
||||||
double deflection{0.0};
|
double deflection{0.0};
|
||||||
|
double cant{0.0};
|
||||||
bool compound{false};
|
bool compound{false};
|
||||||
std::vector<compound_arc> arcs;
|
std::vector<compound_arc> arcs;
|
||||||
std::function<void(double, double &, double &)> tangents;
|
std::function<void(double, double &, double &)> tangents;
|
||||||
@@ -518,6 +520,44 @@ result fit_between(design const &Design)
|
|||||||
}
|
}
|
||||||
r.warnings.emplace_back(format("Vertex %zu: R reduced from %.1f to %.1f m, transitions to %.1f / %.1f m, to fit between the ends", k + 1, Design.vertices[k].radius, fit.radius, fit.transition_in, fit.transition_out));
|
r.warnings.emplace_back(format("Vertex %zu: R reduced from %.1f to %.1f m, transitions to %.1f / %.1f m, to fit between the ends", k + 1, Design.vertices[k].radius, fit.radius, fit.transition_in, fit.transition_out));
|
||||||
}
|
}
|
||||||
|
for (std::size_t k = 0; k < count; ++k)
|
||||||
|
{
|
||||||
|
auto &fit{fits[k]};
|
||||||
|
auto const &vertex{Design.vertices[k]};
|
||||||
|
fit.cant = std::max(0.0, vertex.cant);
|
||||||
|
if (fit.compound || fit.deflection < 1e-7 || fit.transition_in + fit.transition_out <= 0.0)
|
||||||
|
continue;
|
||||||
|
auto const angles = [&](double const Factor) {
|
||||||
|
return transition_point(Design.shape, fit.transition_in * Factor, fit.radius, 1.0).angle + transition_point(Design.shape, fit.transition_out * Factor, fit.radius, 1.0).angle;
|
||||||
|
};
|
||||||
|
auto const minimum{std::min(fit.radius * fit.deflection, std::max(Design.norms.arc_min, Design.norms.arc_min_factor * Design.speed))};
|
||||||
|
if (fit.radius * (fit.deflection - angles(1.0)) < minimum - 1e-6)
|
||||||
|
{
|
||||||
|
auto const target{fit.deflection - minimum / fit.radius};
|
||||||
|
double low{0.0}, high{1.0};
|
||||||
|
for (int i = 0; i < 50; ++i)
|
||||||
|
{
|
||||||
|
auto const middle{(low + high) * 0.5};
|
||||||
|
(angles(middle) > target ? high : low) = middle;
|
||||||
|
}
|
||||||
|
fit.transition_in *= low;
|
||||||
|
fit.transition_out *= low;
|
||||||
|
r.warnings.emplace_back(format("Vertex %zu: transition curves shortened to %.1f / %.1f m, the circular arc keeps %.0f m", k + 1, fit.transition_in, fit.transition_out, minimum));
|
||||||
|
}
|
||||||
|
if (fit.transition_in < 1.0)
|
||||||
|
fit.transition_in = 0.0;
|
||||||
|
if (fit.transition_out < 1.0)
|
||||||
|
fit.transition_out = 0.0;
|
||||||
|
auto const shortest{std::min(fit.transition_in, fit.transition_out)};
|
||||||
|
if (fit.cant <= 0.0 || (fit.transition_in >= vertex.transition_in - 1e-6 && fit.transition_out >= vertex.transition_out - 1e-6))
|
||||||
|
continue;
|
||||||
|
auto const speed{std::max(1.0, Design.speed)};
|
||||||
|
auto const ramp{std::min(shortest * 1000.0 / (Design.norms.ramp_factor * speed), Design.norms.cant_rate * 3.6 * shortest / speed)};
|
||||||
|
if (ramp >= fit.cant)
|
||||||
|
continue;
|
||||||
|
fit.cant = ramp < 5.0 ? 0.0 : std::floor(ramp / 5.0) * 5.0;
|
||||||
|
r.warnings.emplace_back(format("Vertex %zu: cant lowered to %.0f mm, the shortened transition curves can't ramp more", k + 1, fit.cant));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<double> vertexchainage(count, 0.0);
|
std::vector<double> vertexchainage(count, 0.0);
|
||||||
glm::dvec2 cursor{start};
|
glm::dvec2 cursor{start};
|
||||||
@@ -650,7 +690,7 @@ result fit_between(design const &Design)
|
|||||||
curve.vertex = static_cast<int>(k);
|
curve.vertex = static_cast<int>(k);
|
||||||
curve.radius = radius;
|
curve.radius = radius;
|
||||||
curve.turn = side;
|
curve.turn = side;
|
||||||
curve.cant = std::max(0.0, vertex.cant);
|
curve.cant = fit.cant;
|
||||||
if (lengthin > 0.0)
|
if (lengthin > 0.0)
|
||||||
{
|
{
|
||||||
curve.kind = element_kind::transition_in;
|
curve.kind = element_kind::transition_in;
|
||||||
@@ -804,6 +844,227 @@ result compute(design const &Design)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double speed_limit(double const Radius, limits const &Limits)
|
||||||
|
{
|
||||||
|
if (Radius <= 0.0)
|
||||||
|
return 0.0;
|
||||||
|
auto const factor{Limits.gauge / (kGravity * 12.96)};
|
||||||
|
auto const allowance{Limits.gauge * Limits.unbalanced / kGravity};
|
||||||
|
return std::sqrt(Radius * (Limits.cant_max + allowance) / factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
result compute_recommended(design &Design, bool const Transitions)
|
||||||
|
{
|
||||||
|
for (auto &vertex : Design.vertices)
|
||||||
|
{
|
||||||
|
vertex.cant = 0.0;
|
||||||
|
vertex.transition_in = vertex.transition_out = 0.0;
|
||||||
|
}
|
||||||
|
auto r{compute(Design)};
|
||||||
|
if (false == Transitions || false == r.valid)
|
||||||
|
return r;
|
||||||
|
auto const speed{Design.speed};
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
bool accepted{false};
|
||||||
|
for (auto const share : {1.0, 0.75, 0.5, 0.35, 0.25})
|
||||||
|
{
|
||||||
|
auto attempt{Design};
|
||||||
|
auto slowest{speed};
|
||||||
|
for (auto const &curve : r.curves)
|
||||||
|
{
|
||||||
|
if (curve.vertex < 0 || curve.vertex >= static_cast<int>(attempt.vertices.size()))
|
||||||
|
continue;
|
||||||
|
auto const allowed{std::max(1.0, std::min(speed, speed_limit(curve.radius, attempt.norms)))};
|
||||||
|
auto const recommended{recommend(allowed, curve.radius, attempt.norms)};
|
||||||
|
auto const length{recommended.transition * share};
|
||||||
|
auto &vertex{attempt.vertices[curve.vertex]};
|
||||||
|
vertex.transition_in = vertex.transition_out = length;
|
||||||
|
vertex.cant = std::min({recommended.cant, length * 1000.0 / (attempt.norms.ramp_factor * allowed), attempt.norms.cant_rate * 3.6 * length / allowed});
|
||||||
|
vertex.cant = vertex.cant < 5.0 ? 0.0 : std::floor(vertex.cant / 5.0) * 5.0;
|
||||||
|
slowest = std::min(slowest, allowed);
|
||||||
|
}
|
||||||
|
attempt.speed = slowest;
|
||||||
|
auto next{compute(attempt)};
|
||||||
|
if (false == next.valid)
|
||||||
|
continue;
|
||||||
|
auto const kept{std::all_of(next.curves.begin(), next.curves.end(), [&](curve_report const &Curve) {
|
||||||
|
auto const &vertex{attempt.vertices[Curve.vertex]};
|
||||||
|
return Curve.transition_in >= 0.95 * vertex.transition_in && Curve.transition_out >= 0.95 * vertex.transition_out;
|
||||||
|
})};
|
||||||
|
if (false == kept && share > 0.25)
|
||||||
|
continue;
|
||||||
|
Design = attempt;
|
||||||
|
r = next;
|
||||||
|
accepted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (false == accepted)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
double turning(result const &Result)
|
||||||
|
{
|
||||||
|
double total{0.0};
|
||||||
|
for (auto const &e : Result.elements)
|
||||||
|
{
|
||||||
|
if (e.radius <= 0.0)
|
||||||
|
continue;
|
||||||
|
if (e.kind == element_kind::arc)
|
||||||
|
total += e.length / e.radius;
|
||||||
|
else if (e.kind == element_kind::transition_in || e.kind == element_kind::transition_out)
|
||||||
|
total += e.length / (2.0 * e.radius);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spread_rolls(std::vector<segment_data> &Pieces, double const Start, double const End)
|
||||||
|
{
|
||||||
|
auto const count{static_cast<double>(Pieces.size())};
|
||||||
|
for (std::size_t i = 0; i < Pieces.size(); ++i)
|
||||||
|
Pieces[i].rolls = {static_cast<float>(Start + (End - Start) * i / count), static_cast<float>(Start + (End - Start) * (i + 1) / count)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
join_result join(join_request const &Request)
|
||||||
|
{
|
||||||
|
join_result chosen;
|
||||||
|
auto const &a{Request.start};
|
||||||
|
auto const &b{Request.end};
|
||||||
|
auto const chord{plan_of(b.position) - plan_of(a.position)};
|
||||||
|
auto const distance{glm::length(chord)};
|
||||||
|
if (distance < 0.05 || glm::length(a.direction) < 1e-9 || glm::length(b.direction) < 1e-9)
|
||||||
|
return chosen;
|
||||||
|
auto const startdirection{glm::normalize(a.direction)};
|
||||||
|
auto const enddirection{glm::normalize(b.direction)};
|
||||||
|
auto const tolerance{glm::radians(Request.kink)};
|
||||||
|
auto const heading{std::abs(geometry::signed_angle(startdirection, enddirection))};
|
||||||
|
auto const rise{b.position.y - a.position.y};
|
||||||
|
std::vector<join_result> candidates;
|
||||||
|
|
||||||
|
if (std::abs(geometry::signed_angle(startdirection, chord)) <= tolerance && std::abs(geometry::signed_angle(chord, enddirection)) <= tolerance)
|
||||||
|
{
|
||||||
|
join_result straight;
|
||||||
|
straight.kind = join_kind::straight;
|
||||||
|
segment_data path;
|
||||||
|
path.points[segment_data::point::start] = a.position;
|
||||||
|
path.points[segment_data::point::end] = b.position;
|
||||||
|
straight.pieces = {path};
|
||||||
|
spread_rolls(straight.pieces, a.roll, b.roll);
|
||||||
|
straight.length = glm::distance(a.position, b.position);
|
||||||
|
candidates.push_back(straight);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const lateral{cross(startdirection, chord)};
|
||||||
|
auto const along{glm::dot(startdirection, chord)};
|
||||||
|
if (std::abs(lateral) > 1e-6)
|
||||||
|
{
|
||||||
|
auto const angle{2.0 * std::atan2(std::abs(lateral), along)};
|
||||||
|
int const side{lateral > 0.0 ? 1 : -1};
|
||||||
|
auto const radius{distance * distance / (2.0 * std::abs(lateral))};
|
||||||
|
if (angle < kPi - 1e-3 && std::abs(geometry::signed_angle(turned(startdirection, side * angle), enddirection)) <= tolerance)
|
||||||
|
{
|
||||||
|
join_result arc;
|
||||||
|
arc.kind = join_kind::arc;
|
||||||
|
arc.pieces = geometry::arc_pieces(a.position, startdirection, rise / (radius * angle), radius, angle, side);
|
||||||
|
arc.pieces.back().points[segment_data::point::end] = b.position;
|
||||||
|
spread_rolls(arc.pieces, a.roll, b.roll);
|
||||||
|
arc.radius = radius;
|
||||||
|
arc.length = radius * angle;
|
||||||
|
candidates.push_back(arc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const fitted = [&](join_kind const Kind) {
|
||||||
|
join_result curve;
|
||||||
|
design shape;
|
||||||
|
shape.norms = Request.norms;
|
||||||
|
shape.shape = Request.shape;
|
||||||
|
shape.speed = Request.speed;
|
||||||
|
shape.arc_piece_angle = 90.0;
|
||||||
|
shape.start = a.position;
|
||||||
|
shape.end = b.position;
|
||||||
|
shape.start_direction = startdirection;
|
||||||
|
shape.end_direction = enddirection;
|
||||||
|
shape.start_grade = shape.end_grade = rise / distance;
|
||||||
|
vertex corner;
|
||||||
|
corner.radius = Request.radius > 0.0 ? Request.radius : 100000.0;
|
||||||
|
if (Kind == join_kind::curve)
|
||||||
|
shape.vertices = {corner};
|
||||||
|
else
|
||||||
|
{
|
||||||
|
corner.offset = distance / 3.0;
|
||||||
|
shape.vertices = {corner, corner};
|
||||||
|
}
|
||||||
|
auto const r{compute_recommended(shape, Request.transitions)};
|
||||||
|
auto const sharp{std::any_of(r.curves.begin(), r.curves.end(), [](curve_report const &Report) { return Report.radius < 10.0; })};
|
||||||
|
if (false == r.valid || sharp || turning(r) > heading + kPi + 1e-6 || r.length > 3.0 * distance + 1.0)
|
||||||
|
return curve;
|
||||||
|
curve.kind = Kind;
|
||||||
|
curve.pieces = pieces(r, shape, 0);
|
||||||
|
if (curve.pieces.empty())
|
||||||
|
return curve;
|
||||||
|
curve.pieces.front().rolls[0] = static_cast<float>(a.roll);
|
||||||
|
curve.pieces.back().rolls[1] = static_cast<float>(b.roll);
|
||||||
|
curve.length = r.length;
|
||||||
|
curve.speed = shape.speed;
|
||||||
|
curve.warnings = r.warnings;
|
||||||
|
curve.radius = std::numeric_limits<double>::max();
|
||||||
|
for (auto const &report : r.curves)
|
||||||
|
{
|
||||||
|
curve.radius = std::min(curve.radius, report.radius);
|
||||||
|
curve.transition = std::max({curve.transition, report.transition_in, report.transition_out});
|
||||||
|
}
|
||||||
|
for (auto const &e : r.elements)
|
||||||
|
curve.cant = std::max(curve.cant, e.cant);
|
||||||
|
if (r.curves.empty())
|
||||||
|
curve.radius = 0.0;
|
||||||
|
return curve;
|
||||||
|
};
|
||||||
|
design probe;
|
||||||
|
probe.start = a.position;
|
||||||
|
probe.end = b.position;
|
||||||
|
probe.start_direction = startdirection;
|
||||||
|
probe.end_direction = enddirection;
|
||||||
|
glm::dvec2 intersection;
|
||||||
|
if (tangent_intersection(probe, intersection))
|
||||||
|
{
|
||||||
|
if (auto curve{fitted(join_kind::curve)}; curve.kind != join_kind::none)
|
||||||
|
candidates.push_back(curve);
|
||||||
|
}
|
||||||
|
else if (auto curve{fitted(join_kind::reverse_curve)}; curve.kind != join_kind::none)
|
||||||
|
candidates.push_back(curve);
|
||||||
|
|
||||||
|
if (candidates.empty())
|
||||||
|
{
|
||||||
|
join_result fallback;
|
||||||
|
fallback.kind = join_kind::bezier;
|
||||||
|
auto const grade{rise / distance};
|
||||||
|
auto const handle{distance / 3.0};
|
||||||
|
segment_data path;
|
||||||
|
path.points[segment_data::point::start] = a.position;
|
||||||
|
path.points[segment_data::point::end] = b.position;
|
||||||
|
path.points[segment_data::point::control1] = glm::dvec3{startdirection.x, grade, startdirection.y} * handle;
|
||||||
|
path.points[segment_data::point::control2] = -glm::dvec3{enddirection.x, grade, enddirection.y} * handle;
|
||||||
|
fallback.pieces = {path};
|
||||||
|
spread_rolls(fallback.pieces, a.roll, b.roll);
|
||||||
|
fallback.length = distance;
|
||||||
|
candidates.push_back(fallback);
|
||||||
|
}
|
||||||
|
auto const count{static_cast<int>(candidates.size())};
|
||||||
|
chosen = candidates[((Request.variant % count) + count) % count];
|
||||||
|
chosen.variants = count;
|
||||||
|
if (chosen.speed <= 0.0)
|
||||||
|
chosen.speed = Request.speed;
|
||||||
|
return chosen;
|
||||||
|
}
|
||||||
|
|
||||||
bool tangent_intersection(design const &Design, glm::dvec2 &Point)
|
bool tangent_intersection(design const &Design, glm::dvec2 &Point)
|
||||||
{
|
{
|
||||||
auto const start{plan_of(Design.start)};
|
auto const start{plan_of(Design.start)};
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ struct limits
|
|||||||
double vertical_factor{0.4};
|
double vertical_factor{0.4};
|
||||||
double vertical_min{2000.0};
|
double vertical_min{2000.0};
|
||||||
double tangent_min_time{0.0};
|
double tangent_min_time{0.0};
|
||||||
|
double arc_min{20.0};
|
||||||
|
double arc_min_factor{0.4};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct compound_arc
|
struct compound_arc
|
||||||
@@ -164,6 +166,8 @@ struct result
|
|||||||
};
|
};
|
||||||
|
|
||||||
result compute(design const &Design);
|
result compute(design const &Design);
|
||||||
|
double speed_limit(double const Radius, limits const &Limits);
|
||||||
|
result compute_recommended(design &Design, bool const Transitions);
|
||||||
bool tangent_intersection(design const &Design, glm::dvec2 &Point);
|
bool tangent_intersection(design const &Design, glm::dvec2 &Point);
|
||||||
bool collinear(design const &Design);
|
bool collinear(design const &Design);
|
||||||
|
|
||||||
@@ -184,4 +188,49 @@ std::size_t minimum_pieces(result const &Result, design const &Design);
|
|||||||
std::vector<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count, std::vector<double> const &Breaks = {}, std::vector<std::size_t> const &Counts = {},
|
std::vector<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count, std::vector<double> const &Breaks = {}, std::vector<std::size_t> const &Counts = {},
|
||||||
std::vector<std::size_t> *Intervals = nullptr);
|
std::vector<std::size_t> *Intervals = nullptr);
|
||||||
|
|
||||||
|
struct join_end
|
||||||
|
{
|
||||||
|
glm::dvec3 position{0.0};
|
||||||
|
glm::dvec2 direction{0.0, 1.0};
|
||||||
|
double roll{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class join_kind
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
straight,
|
||||||
|
arc,
|
||||||
|
curve,
|
||||||
|
reverse_curve,
|
||||||
|
bezier
|
||||||
|
};
|
||||||
|
|
||||||
|
struct join_request
|
||||||
|
{
|
||||||
|
join_end start;
|
||||||
|
join_end end;
|
||||||
|
double radius{0.0};
|
||||||
|
int variant{0};
|
||||||
|
bool transitions{false};
|
||||||
|
double speed{100.0};
|
||||||
|
double kink{0.1};
|
||||||
|
transition_shape shape{transition_shape::cubic_parabola};
|
||||||
|
limits norms;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct join_result
|
||||||
|
{
|
||||||
|
join_kind kind{join_kind::none};
|
||||||
|
std::vector<segment_data> pieces;
|
||||||
|
double length{0.0};
|
||||||
|
double radius{0.0};
|
||||||
|
double cant{0.0};
|
||||||
|
double transition{0.0};
|
||||||
|
double speed{0.0};
|
||||||
|
int variants{0};
|
||||||
|
std::vector<std::string> warnings;
|
||||||
|
};
|
||||||
|
|
||||||
|
join_result join(join_request const &Request);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5213,3 +5213,73 @@ msgstr "Punkt znikania"
|
|||||||
|
|
||||||
msgid "Pedestrian crossing"
|
msgid "Pedestrian crossing"
|
||||||
msgstr "Przejście dla pieszych"
|
msgstr "Przejście dla pieszych"
|
||||||
|
|
||||||
|
# scenery editor: joining free ends, the shape from a free end on the wheel
|
||||||
|
msgid "No curve fits between the ends, they are joined with a free curve"
|
||||||
|
msgstr "Między końcami nie mieści się żaden łuk, połączono je krzywą swobodną"
|
||||||
|
|
||||||
|
msgid "Arc, then straight"
|
||||||
|
msgstr "Łuk, potem prosta"
|
||||||
|
|
||||||
|
msgid "Straight, then arc"
|
||||||
|
msgstr "Prosta, potem łuk"
|
||||||
|
|
||||||
|
msgid "Arc through the cursor"
|
||||||
|
msgstr "Łuk przez kursor"
|
||||||
|
|
||||||
|
msgid "Shape from a free end"
|
||||||
|
msgstr "Kształt z wolnego końca"
|
||||||
|
|
||||||
|
msgid "What a drag or a click from a free end lays towards the cursor; Shift+wheel changes it, Ctrl+wheel the radius"
|
||||||
|
msgstr "Co przeciągnięcie lub kliknięcie z wolnego końca kładzie w stronę kursora; Shift+kółko zmienia kształt, Ctrl+kółko promień"
|
||||||
|
|
||||||
|
msgid "Speed limit of the new paths, and the design speed of the transition curves and of the cant; the speed of the track joined comes first, and none higher than the radius allows"
|
||||||
|
msgstr "Prędkość nowych torów i prędkość projektowa krzywych przejściowych oraz przechyłki; pierwszeństwo ma prędkość toru, do którego się dołącza, i nigdy nie wyższa, niż pozwala promień"
|
||||||
|
|
||||||
|
msgid "R %.0f m Ctrl+wheel: radius"
|
||||||
|
msgstr "R %.0f m Ctrl+kółko: promień"
|
||||||
|
|
||||||
|
msgid "Ctrl+wheel: radius, Shift+wheel: arc / straight"
|
||||||
|
msgstr "Ctrl+kółko: promień, Shift+kółko: łuk / prosta"
|
||||||
|
|
||||||
|
msgid "Ctrl+wheel: radius, Shift+wheel: another shape"
|
||||||
|
msgstr "Ctrl+kółko: promień, Shift+kółko: inny kształt"
|
||||||
|
|
||||||
|
msgid "arc"
|
||||||
|
msgstr "łuk"
|
||||||
|
|
||||||
|
msgid "curve"
|
||||||
|
msgstr "łuk z prostymi"
|
||||||
|
|
||||||
|
msgid "reverse curve"
|
||||||
|
msgstr "łuk odwrotny (S)"
|
||||||
|
|
||||||
|
msgid "free curve"
|
||||||
|
msgstr "krzywa swobodna"
|
||||||
|
|
||||||
|
msgid "(auto)"
|
||||||
|
msgstr "(auto)"
|
||||||
|
|
||||||
|
msgid " cant %.0f mm"
|
||||||
|
msgstr " przechyłka %.0f mm"
|
||||||
|
|
||||||
|
msgid " transitions %.0f m"
|
||||||
|
msgstr " krzywe przejściowe %.0f m"
|
||||||
|
|
||||||
|
msgid "the shape chosen towards the cursor, to a free end: joins it"
|
||||||
|
msgstr "wybrany kształt w stronę kursora, na wolny koniec: łączy z nim"
|
||||||
|
|
||||||
|
msgid "Ctrl+wheel"
|
||||||
|
msgstr "Ctrl+kółko"
|
||||||
|
|
||||||
|
msgid "Shift+wheel"
|
||||||
|
msgstr "Shift+kółko"
|
||||||
|
|
||||||
|
msgid "arc / straight, at a free end: another shape"
|
||||||
|
msgstr "łuk / prosta, na wolnym końcu: inny kształt"
|
||||||
|
|
||||||
|
msgid "lay the shape up to here and go on from its end"
|
||||||
|
msgstr "kładzie kształt dotąd i idzie dalej od jego końca"
|
||||||
|
|
||||||
|
msgid "type the radius"
|
||||||
|
msgstr "wpisz promień"
|
||||||
|
|||||||
Reference in New Issue
Block a user