mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 08:39:19 +02:00
feat(editor): laid track stands on the ground with the foot of its ballast
- each click of the lay tool and the end of a drag out of a free end take the height of the ground under the lower foot of the ballast plus its depth, the rails never under the ground at the axis - heights between the clicks follow a curve which keeps the grade of the track laid from at the joint and leaves with the grade of the chord, onto a free end with the grade of that track; no grade limits - the cursor follows the ground along its ray instead of the level of the last point - self-test layclicks: heights of the ground and the track, grades and grade breaks at the joints
This commit is contained in:
@@ -1144,6 +1144,9 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
bool switch_reach(glm::dvec3 const &Ground, glm::dvec3 &Point);
|
||||
glm::dvec3 cursor_ground() const;
|
||||
glm::dvec3 cursor_level(double const Height) const;
|
||||
glm::dvec3 cursor_terrain(double const Height);
|
||||
double bed_height(glm::dvec3 const &Point, glm::dvec2 const &Direction, TTrack const *Style);
|
||||
glm::dvec3 lay_bed(glm::dvec3 const &Point);
|
||||
std::optional<glm::dvec3> m_cursor_override;
|
||||
bool sweep_grabs(glm::dvec3 const &Point);
|
||||
// menu of the path a click of the right mouse button lands on
|
||||
|
||||
@@ -1054,9 +1054,13 @@ void editor_mode::selftest_step()
|
||||
auto const before{m_history.size()};
|
||||
for (glm::dvec3 point{0.0}; words >> point.x >> point.z;)
|
||||
{
|
||||
point.y = m_lay.points.empty() ? 0.0 : m_lay.points.back().y;
|
||||
m_lay.mouse = point;
|
||||
point.y = 5000.0;
|
||||
std::vector<char> found;
|
||||
auto const ground{ground_heights({point}, true, &found)};
|
||||
point.y = found[0] != 0 ? ground[0] : m_lay.points.empty() ? 0.0 : m_lay.points.back().y;
|
||||
m_lay.mouse_snap = editor_track::find_free_end(nullptr, 1, point, 3.0, {});
|
||||
m_lay.mouse = m_lay.mouse_snap.track != nullptr ? point : lay_bed(point);
|
||||
WriteLog(format("SELFTEST layclicks ground %.3f, track %.3f", point.y, m_lay.mouse.y));
|
||||
lay_click();
|
||||
WriteLog(format("SELFTEST layclicks %.1f %.1f: %s", point.x, point.z, m_lay.status.c_str()));
|
||||
}
|
||||
@@ -1064,6 +1068,7 @@ void editor_mode::selftest_step()
|
||||
m_lay.active = active;
|
||||
glm::dvec2 heading{0.0};
|
||||
glm::dvec3 previous{0.0};
|
||||
double slope{0.0};
|
||||
bool first{true};
|
||||
for (auto step{before}; step < m_history.size(); ++step)
|
||||
for (auto const *track : m_history[step].created)
|
||||
@@ -1073,8 +1078,11 @@ void editor_mode::selftest_step()
|
||||
auto const begin{glm::normalize(geometry::plan_of(curve.first(0.0)))};
|
||||
auto const &a{path.points[segment_data::point::start]};
|
||||
auto const &b{path.points[segment_data::point::end]};
|
||||
WriteLog(format("SELFTEST layclicks path %s: %.2f %.2f -> %.2f %.2f, L %.2f m, R %.1f m, kink %.4f deg, step %.4f m", track->name().c_str(), a.x, a.z, b.x, b.z, curve.plan_length(), static_cast<double>(path.radius),
|
||||
first ? 0.0 : glm::degrees(std::abs(geometry::signed_angle(heading, begin))), first ? 0.0 : glm::distance(previous, a)));
|
||||
auto const rise = [](glm::dvec3 const &Tangent) { return Tangent.y / std::max(1e-9, glm::length(geometry::plan_of(Tangent))); };
|
||||
WriteLog(format("SELFTEST layclicks path %s: %.2f %.2f -> %.2f %.2f, L %.2f m, R %.1f m, kink %.4f deg, step %.4f m, y %.3f -> %.3f, grade %.2f -> %.2f permille, grade break %.3f permille", track->name().c_str(), a.x, a.z, b.x, b.z, curve.plan_length(), static_cast<double>(path.radius),
|
||||
first ? 0.0 : glm::degrees(std::abs(geometry::signed_angle(heading, begin))), first ? 0.0 : glm::distance(previous, a), a.y, b.y, 1000.0 * rise(curve.first(0.0)), 1000.0 * rise(curve.first(1.0)),
|
||||
first ? 0.0 : 1000.0 * std::abs(rise(curve.first(0.0)) - slope)));
|
||||
slope = rise(curve.first(1.0));
|
||||
heading = glm::normalize(geometry::plan_of(curve.first(1.0)));
|
||||
previous = b;
|
||||
first = false;
|
||||
|
||||
@@ -201,6 +201,56 @@ std::vector<segment_data> straight_piece(glm::dvec3 const &Point, glm::dvec2 con
|
||||
return {path};
|
||||
}
|
||||
|
||||
void follow_heights(std::vector<segment_data> &Pieces, double const Height0, double Grade0, double const Height1, double Grade1)
|
||||
{
|
||||
std::vector<double> lengths;
|
||||
double total{0.0};
|
||||
for (auto const &piece : Pieces)
|
||||
total += lengths.emplace_back(bezier{piece}.plan_length());
|
||||
if (total < 1e-3)
|
||||
return;
|
||||
auto const chord{(Height1 - Height0) / total};
|
||||
if (std::isnan(Grade0))
|
||||
Grade0 = chord;
|
||||
if (std::isnan(Grade1))
|
||||
Grade1 = chord;
|
||||
auto const height = [&](double const Chainage) {
|
||||
auto const t{Chainage / total};
|
||||
return (2.0 * t * t * t - 3.0 * t * t + 1.0) * Height0 + (t * t * t - 2.0 * t * t + t) * total * Grade0 + (3.0 * t * t - 2.0 * t * t * t) * Height1 + (t * t * t - t * t) * total * Grade1;
|
||||
};
|
||||
auto const grade = [&](double const Chainage) {
|
||||
auto const t{Chainage / total};
|
||||
return (6.0 * t * t - 6.0 * t) * (Height0 - Height1) / total + (3.0 * t * t - 4.0 * t + 1.0) * Grade0 + (3.0 * t * t - 2.0 * t) * Grade1;
|
||||
};
|
||||
double chainage{0.0};
|
||||
for (std::size_t i = 0; i < Pieces.size(); ++i)
|
||||
{
|
||||
auto &points{Pieces[i].points};
|
||||
auto const length{lengths[i]};
|
||||
auto const from{grade(chainage)};
|
||||
auto const to{grade(chainage + length)};
|
||||
auto &start{points[segment_data::point::start]};
|
||||
auto &end{points[segment_data::point::end]};
|
||||
auto &control1{points[segment_data::point::control1]};
|
||||
auto &control2{points[segment_data::point::control2]};
|
||||
start.y = height(chainage);
|
||||
end.y = height(chainage + length);
|
||||
chainage += length;
|
||||
if (control1 != glm::dvec3{} || control2 != glm::dvec3{})
|
||||
{
|
||||
control1.y = from * glm::length(plan_of(control1));
|
||||
control2.y = -to * glm::length(plan_of(control2));
|
||||
continue;
|
||||
}
|
||||
auto const linear{(end.y - start.y) / std::max(length, 1e-6)};
|
||||
if (length < 1e-3 || (std::abs(from - linear) < 1e-6 && std::abs(to - linear) < 1e-6))
|
||||
continue;
|
||||
auto const along{glm::normalize(plan_of(end - start)) * (length / 3.0)};
|
||||
control1 = {along.x, from * length / 3.0, along.y};
|
||||
control2 = {-along.x, -to * length / 3.0, -along.y};
|
||||
}
|
||||
}
|
||||
|
||||
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)};
|
||||
@@ -3845,11 +3895,11 @@ void editor_mode::update_build_tools()
|
||||
};
|
||||
if (m_extend.active)
|
||||
{
|
||||
screen_projection const projection;
|
||||
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 = cursor_terrain(m_extend.point.y);
|
||||
typed_extend();
|
||||
m_extend.snap = m_typed.empty() && 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{};
|
||||
if (m_extend.snap.track == nullptr)
|
||||
m_extend.mouse.y = bed_height(m_extend.mouse, plan_of(m_extend.mouse - m_extend.point), m_extend.track);
|
||||
joining(m_extend.snap);
|
||||
}
|
||||
if (m_switch.placing)
|
||||
@@ -3861,15 +3911,11 @@ void editor_mode::update_build_tools()
|
||||
if (false == lay.active)
|
||||
return;
|
||||
// geometric ray, same as extend: Mouse_Position() is a delayed depth pick and lags the cursor
|
||||
screen_projection const lay_projection;
|
||||
double const height{false == lay.points.empty() ? lay.points.back().y : lay.start.track != nullptr ? lay.start.position.y : 0.0};
|
||||
glm::dvec3 mouse{ground};
|
||||
if (m_cursor_override.has_value())
|
||||
mouse = glm::dvec3{m_cursor_override->x, height, m_cursor_override->z};
|
||||
else if (false == lay_projection.on_level(io.MousePos, height, mouse))
|
||||
mouse = ground;
|
||||
lay.mouse = typed_lay(mouse);
|
||||
double const height{false == lay.points.empty() ? lay.points.back().y : lay.start.track != nullptr ? lay.start.position.y : ground.y};
|
||||
lay.mouse = typed_lay(cursor_terrain(height));
|
||||
lay.mouse_snap = m_typed.empty() ? snap_free_end(lay.mouse, cursor, nullptr, kRailCategory, {}) : editor_track::find_free_end(nullptr, kRailCategory, lay.mouse, kLaySnapRadius, {});
|
||||
if (lay.mouse_snap.track == nullptr)
|
||||
lay.mouse = lay_bed(lay.mouse);
|
||||
if (false == m_extend.active)
|
||||
joining(lay.points.empty() ? editor_track::snap_target{} : lay.mouse_snap);
|
||||
if (lay.points.empty())
|
||||
@@ -3966,6 +4012,52 @@ glm::dvec3 editor_mode::cursor_ground() const
|
||||
return {ground.x, 0.0, ground.z};
|
||||
}
|
||||
|
||||
glm::dvec3 editor_mode::cursor_terrain(double const Height)
|
||||
{
|
||||
auto point{cursor_level(Height)};
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
std::vector<char> found;
|
||||
auto const heights{ground_heights({point}, true, &found)};
|
||||
if (found[0] == 0)
|
||||
return point;
|
||||
auto const next{cursor_level(heights[0])};
|
||||
auto const settled{plan_distance(next, point) < 0.01};
|
||||
point = {next.x, heights[0], next.z};
|
||||
if (settled)
|
||||
break;
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
double editor_mode::bed_height(glm::dvec3 const &Point, glm::dvec2 const &Direction, TTrack const *Style)
|
||||
{
|
||||
auto const [depth, half]{Style != nullptr ? profile_ballast(*Style) : std::pair<double, double>{0.2, 0.5 * 1.435 + 0.5 + 1.1}};
|
||||
std::vector<glm::dvec3> points{Point};
|
||||
if (glm::length(Direction) > 1e-6)
|
||||
{
|
||||
auto const side{glm::normalize(glm::dvec2{-Direction.y, Direction.x}) * half};
|
||||
points.push_back(Point + glm::dvec3{side.x, 0.0, side.y});
|
||||
points.push_back(Point - glm::dvec3{side.x, 0.0, side.y});
|
||||
}
|
||||
std::vector<char> found;
|
||||
auto const heights{ground_heights(points, true, &found)};
|
||||
auto toe{std::numeric_limits<double>::max()};
|
||||
for (std::size_t i = 1; i < points.size(); ++i)
|
||||
if (found[i] != 0)
|
||||
toe = std::min(toe, heights[i]);
|
||||
if (found[0] == 0)
|
||||
return toe < std::numeric_limits<double>::max() ? toe + depth : Point.y;
|
||||
return std::max(toe < std::numeric_limits<double>::max() ? toe + depth : heights[0] + depth, heights[0]);
|
||||
}
|
||||
|
||||
glm::dvec3 editor_mode::lay_bed(glm::dvec3 const &Point)
|
||||
{
|
||||
auto const &tool{m_lay};
|
||||
TTrack const *style{tool.start.track != nullptr ? tool.start.track : tool.copy_selected ? selected_track() : nullptr};
|
||||
return {Point.x, bed_height(Point, tool.points.empty() ? glm::dvec2{0.0} : plan_of(Point - tool.points.back()), style), Point.z};
|
||||
}
|
||||
|
||||
glm::dvec3 editor_mode::cursor_level(double const Height) const
|
||||
{
|
||||
if (m_cursor_override.has_value())
|
||||
@@ -4202,13 +4294,16 @@ std::vector<segment_data> editor_mode::extend_pieces() const
|
||||
auto pieces{arc_pieces(tool.point, direction, tool.grade, radius, std::min(angle, glm::pi<double>()), side)};
|
||||
for (auto &piece : pieces)
|
||||
piece.rolls = {diverging.rolls[1], diverging.rolls[1]};
|
||||
follow_heights(pieces, tool.point.y, tool.grade, tool.mouse.y, std::numeric_limits<double>::quiet_NaN());
|
||||
return pieces;
|
||||
}
|
||||
}
|
||||
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);
|
||||
auto pieces{layout_pieces(tool.point, tool.direction, tool.grade, tool.mouse, radius)};
|
||||
follow_heights(pieces, tool.point.y, tool.grade, tool.mouse.y, std::numeric_limits<double>::quiet_NaN());
|
||||
return pieces;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -4541,7 +4636,11 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
||||
m_join_last = {};
|
||||
std::vector<segment_data> pieces;
|
||||
if (tool.start.track != nullptr)
|
||||
pieces = layout_pieces(Points[0], glm::normalize(leaving), -grade_of(tool.start.direction), Points[1], tool.radius);
|
||||
{
|
||||
auto const grade{-grade_of(tool.start.direction)};
|
||||
pieces = layout_pieces(Points[0], glm::normalize(leaving), grade, Points[1], tool.radius);
|
||||
follow_heights(pieces, Points[0].y, grade, Points[1].y, std::numeric_limits<double>::quiet_NaN());
|
||||
}
|
||||
else if (End.track == nullptr)
|
||||
{
|
||||
auto const length{glm::length(leaving)};
|
||||
@@ -4552,6 +4651,7 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
||||
auto const back{layout_pieces(End.position, glm::normalize(-plan_of(End.direction)), -grade_of(End.direction), Points[0], tool.radius)};
|
||||
for (auto piece{back.rbegin()}; piece != back.rend(); ++piece)
|
||||
pieces.push_back(turned_around(*piece));
|
||||
follow_heights(pieces, Points[0].y, std::numeric_limits<double>::quiet_NaN(), End.position.y, grade_of(End.direction));
|
||||
}
|
||||
for (auto const &piece : pieces)
|
||||
Length += bezier{piece}.plan_length();
|
||||
@@ -4691,10 +4791,10 @@ void editor_mode::lay_click()
|
||||
if (tool.points.empty())
|
||||
{
|
||||
tool.start = snap;
|
||||
tool.points.push_back(snap.track != nullptr ? snap.position : placement_on_ground(tool.mouse));
|
||||
tool.points.push_back(snap.track != nullptr ? snap.position : tool.mouse);
|
||||
return;
|
||||
}
|
||||
auto const point{placement_on_ground(tool.mouse)};
|
||||
auto const &point{tool.mouse};
|
||||
if (plan_distance(point, tool.points.back()) < 1.0)
|
||||
return;
|
||||
m_typed.clear();
|
||||
|
||||
Reference in New Issue
Block a user