17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 13:57:28 +01:00

feat(editor): the drawn tracks kept in the scenery as //$h lines as they are drawn: laying, pulling out of a free end, joining two ends and the parallel track record the whole line they belong to, its straights, arcs and transition curves, with the radii from the chord and the turn of the arcs and the transitions from the turn they make, in the same undo step; the curves read in the route window and the Curve tab are what the paths are, no transitions or cant made up for them; a curve ends at the point of reverse; a parallel arc is concentric with its source; the paths laid in pieces of up to 90 degrees match the kept design within their own approximation; 'Keep the line as it lies' for lines drawn before; self-test commands digitize and parallel; Polish translation

This commit is contained in:
maj00r
2026-10-11 17:31:23 +02:00
parent 4ea2f4f13f
commit 11aa67378d
9 changed files with 287 additions and 22 deletions

View File

@@ -809,6 +809,8 @@ class editor_mode : public application_mode, private editor_track::observer
void record_slip(std::vector<TTrack *> const &Parts, std::string const &Kind, std::string const &Type, double const Radius, double const Angle, std::string const &Base);
void render_switch_record(TTrack &Track);
void route_store();
bool digitize_drawn(std::vector<TTrack *> const &Created, bool const Alone = true);
bool digitize_chain(editor_track::chain const &Chain);
void render_route_stored();
void route_update() { m_route.result = alignment::compute(m_route.design); }
void route_apply();

View File

@@ -23,6 +23,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/translation.h"
#include <algorithm>
#include <cmath>
#include <optional>
namespace
{
@@ -30,6 +31,7 @@ namespace
using geometry::bezier;
using geometry::cross;
using geometry::plan_of;
using geometry::signed_angle;
double const kParallelReach{5000.0}; // m each way along the line
double const kSpacings[] = {3.5, 4.0, 4.5, 4.75, 5.0, 6.0};
@@ -76,6 +78,23 @@ double curvature(segment_data const &Path, bool const Atend)
return speed > 1e-9 ? cross(first, second) / (speed * speed * speed) : 0.0;
}
std::optional<double> arc_curvature(segment_data const &Path)
{
auto const [control1, control2] = controls(Path);
auto const along{plan_of(control1)};
auto const back{-plan_of(control2)};
if (glm::length(along) < 1e-9 || glm::length(back) < 1e-9)
return std::nullopt;
auto const turn{signed_angle(glm::normalize(along), glm::normalize(back))};
if (std::abs(turn) < 1e-6)
return std::nullopt;
auto const radius{glm::length(plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start])) / (2.0 * std::sin(std::abs(turn) * 0.5))};
auto const control{4.0 / 3.0 * std::tan(std::abs(turn) * 0.25) * radius};
if (std::abs(glm::length(along) - control) > 1e-4 * control || std::abs(glm::length(back) - control) > 1e-4 * control)
return std::nullopt;
return std::copysign(1.0 / radius, turn);
}
glm::dvec2 direction(segment_data const &Path, bool const Atend)
{
auto const [control1, control2] = controls(Path);
@@ -188,8 +207,9 @@ void editor_mode::parallel_update()
{
auto const &source{sources[i].second};
auto const [control1, control2] = controls(source);
auto const startscale{1.0 - curvature(source, false) * offset};
auto const endscale{1.0 - curvature(source, true) * offset};
auto const arc{arc_curvature(source)};
auto const startscale{1.0 - (arc.has_value() ? *arc : curvature(source, false)) * offset};
auto const endscale{1.0 - (arc.has_value() ? *arc : curvature(source, true)) * offset};
if (startscale < 0.05 || endscale < 0.05)
{
tool.pieces.clear();
@@ -205,7 +225,7 @@ void editor_mode::parallel_update()
piece.rolls = source.rolls;
auto const middle{0.5 * (curvature(source, false) + curvature(source, true))};
if (source.radius != 0.0f && std::abs(middle) > 1e-7)
piece.radius = static_cast<float>(std::copysign(std::abs((1.0 - middle * offset) / middle), static_cast<double>(source.radius)));
piece.radius = static_cast<float>(std::copysign(std::abs(std::abs(static_cast<double>(source.radius)) - std::copysign(offset, middle)), static_cast<double>(source.radius)));
tool.length += bezier{piece}.plan_length();
tool.pieces.push_back(piece);
tool.styles.push_back(sources[i].first);
@@ -222,6 +242,7 @@ void editor_mode::parallel_build()
for (std::size_t i = 0; i < tool.pieces.size(); ++i)
created.push_back(editor_track::create_path(*tool.styles[i], tool.pieces[i]));
editor_track::commit(created);
digitize_drawn(created);
push_track_snapshot({}, created);
tool.status = format(STR_C("Parallel track of %zu paths, %.1f m, %.2f m away; Ctrl+Z takes it back"), created.size(), tool.length, tool.spacing);
if (scene::Layers.active() == null_handle)

View File

@@ -1373,6 +1373,7 @@ void editor_mode::selftest_step()
for (auto const &piece : pieces)
created.push_back(editor_track::create_path(*tool.track, piece));
editor_track::commit(created);
digitize_drawn(created);
if (false == created.empty())
push_track_snapshot({}, created);
char const *const kinds[]{"none", "straight", "arc", "curve", "reverse curve", "free curve"};
@@ -1395,6 +1396,34 @@ void editor_mode::selftest_step()
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 == "digitize")
{
glm::dvec3 point{0.0};
words >> point.x >> point.z;
TTrack *nearest{nullptr};
double distance{1e9};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
continue;
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
if (d < distance)
{
distance = d;
nearest = track;
}
}
auto const kept{nearest != nullptr && digitize_drawn({nearest}, false)};
WriteLog(format("SELFTEST digitize %s: %d", nearest != nullptr ? nearest->name().c_str() : "-", kept ? 1 : 0));
}
else if (command == "parallel")
{
words >> m_parallel.spacing >> m_parallel.side;
m_parallel.scope = 1;
m_parallel.built_for = nullptr;
parallel_build();
WriteLog("SELFTEST parallel: " + m_parallel.status + " " + m_parallel.error);
}
else if (command == "layclicks")
{
int layout{0};

View File

@@ -37,6 +37,7 @@ http:
#include <cstring>
#include <functional>
#include <iterator>
#include <set>
namespace
{
@@ -94,6 +95,7 @@ float const kSnapPixels{16.0f};
float const kDragPixels{5.0f};
double const kSwitchReach{6.0}; // a press farther from the selected track selects another one instead of placing the switch
double const kLineReach{2000.0}; // how far each way the Curve tab follows the line
double const kDrawnReach{100000.0};
double const kJointRange{400.0}; // the ends of the paths are drawn for the tracks this close to the camera
ImU32 const kJointColor{IM_COL32(255, 255, 255, 200)};
int const kRailCategory{1};
@@ -2608,14 +2610,10 @@ alignment::vertex editor_mode::route_vertex_of(editor_track::curve const &Curve)
if (Curve.designed.has_value())
return *Curve.designed;
alignment::vertex vertex;
vertex.radius = Curve.radius > 0.0 ? std::max(10.0, std::round(Curve.radius)) : 1000.0;
route_recommend(vertex);
if (Curve.transition_in > 0.0)
vertex.transition_in = std::round(Curve.transition_in);
if (Curve.transition_out > 0.0)
vertex.transition_out = std::round(Curve.transition_out);
if (Curve.cant > 0.0)
vertex.cant = std::round(Curve.cant);
vertex.radius = Curve.radius > 0.0 ? std::max(1.0, Curve.radius) : 1000.0;
vertex.transition_in = std::max(0.0, Curve.transition_in);
vertex.transition_out = std::max(0.0, Curve.transition_out);
vertex.cant = std::max(0.0, Curve.cant);
vertex.reverse_turn = std::abs(Curve.turn) > glm::pi<double>();
compound_from(Curve, vertex);
return vertex;
@@ -2707,6 +2705,19 @@ void editor_mode::render_route_stored()
route.origin = km * 1000.0;
ImGui::PopItemWidth();
item_tooltip("Chainage of the start of the fragment, kept with the design in the scenery; the vertical profile of these paths counts from it");
if (route.designed == 0 && route.departed < 0 && false == route.chain.tracks.empty())
{
if (ImGui::SmallButton(STR_C("Keep the line as it lies")))
{
auto *track{route.chain.tracks[route.chain.tracks.size() / 2]};
auto const kept{digitize_drawn({track}, false)};
alignment_store::settle();
route_load(*track);
route.status += std::string{"\n"} + (kept ? STR_C("The line is kept in the scenery with its straights, arcs and transition curves") : STR_C("The paths don't make a line of straights, arcs and transition curves, nothing kept"));
return;
}
item_tooltip("Keeps the line through the selected path in the scenery as its paths lie, the way the drawn tracks are kept");
}
auto const &records{alignment_store::records()};
if (route.departed < 0 || route.departed >= static_cast<int>(records.size()))
return;
@@ -2739,6 +2750,83 @@ void editor_mode::route_store()
route.departure = 0.0;
}
bool editor_mode::digitize_drawn(std::vector<TTrack *> const &Created, bool const Alone)
{
std::vector<TTrack *> drawn;
for (auto *track : Created)
if (track != nullptr && track->eType == tt_Normal && false == track->m_editorremoved && false == track->m_paths.empty())
drawn.push_back(track);
if (drawn.empty() || scene::Layers.empty())
return false;
editor_track::chain chain;
std::string error;
if (editor_track::find_run(*drawn.front(), kDrawnReach, chain, error) && digitize_chain(chain))
return true;
if (Alone && editor_track::find_chain(drawn.front(), drawn.back(), chain, error) && chain.tracks.size() == drawn.size() && digitize_chain(chain))
return true;
WriteLog("Editor: the drawn paths don't make a line of straights, arcs and transition curves, no design kept for them", logtype::generic);
return false;
}
bool editor_mode::digitize_chain(editor_track::chain const &Chain)
{
if (Chain.tracks.empty())
return false;
alignment::design design;
design.norms = m_route.design.norms;
design.shape = m_route.design.shape;
design.transition_pieces = m_route.design.transition_pieces;
design.arc_piece_angle = m_route.design.arc_piece_angle;
design.speed = Chain.velocity > 0.0 ? Chain.velocity : 100.0;
design.start = Chain.start;
design.end = Chain.end;
if (glm::length(plan_of(Chain.start_direction)) < 1e-9 || glm::length(plan_of(Chain.end_direction)) < 1e-9)
return false;
design.start_direction = glm::normalize(plan_of(Chain.start_direction));
design.end_direction = glm::normalize(plan_of(Chain.end_direction));
design.start_grade = grade_of(Chain.start_direction);
design.end_grade = grade_of(Chain.end_direction);
design.start_radius = Chain.start_radius;
design.end_radius = Chain.end_radius;
for (auto const &entry : editor_track::recognize_line(Chain, m_straights.tolerance, design.norms.gauge, design.shape))
{
if (entry.kink)
return false;
auto vertex{route_vertex_of(entry.shape)};
vertex.position = entry.position;
design.vertices.push_back(vertex);
}
if (design.vertices.size() >= 2)
{
design.vertices.front().offset = std::max(0.0, glm::dot(design.vertices.front().position - plan_of(design.start), design.start_direction));
design.vertices.back().offset = std::max(0.0, glm::dot(plan_of(design.end) - design.vertices.back().position, design.end_direction));
}
auto const result{alignment::compute(design)};
if (false == alignment_store::follows(result, Chain.tracks))
return false;
auto const &records{alignment_store::records()};
std::set<int> covered;
for (auto *track : Chain.tracks)
if (auto const found{alignment_store::on_track(*track)})
covered.insert(found->record);
double origin{0.0};
for (auto const index : covered)
{
auto const &old{records[index]};
auto const start{alignment_store::chainage_on(result, old.design.start)};
auto const end{alignment_store::chainage_on(result, old.design.end)};
if (false == start.has_value() || false == end.has_value())
return false;
if (index == *covered.begin() && *end > *start)
origin = old.origin - *start;
}
auto const layer{covered.empty() ? record_layer(*Chain.tracks.front()) : records[*covered.begin()].layer};
for (auto index{covered.rbegin()}; index != covered.rend(); ++index)
alignment_store::forget(*index);
alignment_store::store(design, result, origin, layer, "drawn");
return true;
}
bool editor_mode::route_from_line(TTrack &Track)
{
auto &route{m_route};
@@ -2751,7 +2839,7 @@ bool editor_mode::route_from_line(TTrack &Track)
if (route.chain.tracks.empty())
return false;
auto &design{route.design};
auto const found{editor_track::recognize_line(route.chain, m_straights.tolerance, design.norms.gauge)};
auto const found{editor_track::recognize_line(route.chain, m_straights.tolerance, design.norms.gauge, design.shape)};
route.designed = 0;
if (false == found.empty())
{
@@ -4325,6 +4413,12 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
states.push_back(entry);
}
created.insert(created.begin(), splitcreated.begin(), splitcreated.end());
{
auto changed{created};
for (auto const &entry : states)
changed.push_back(entry.first);
digitize_drawn(changed);
}
push_track_snapshot(std::move(states), std::move(created));
std::string error;
editor_track::find_chain(m_route.from, m_route.to, m_route.chain, error, true);
@@ -5701,6 +5795,7 @@ void editor_mode::finish_extend()
for (auto const &piece : pieces)
created.push_back(editor_track::create_path(*style, piece));
editor_track::commit(created);
digitize_drawn(created);
push_track_snapshot({}, created);
m_node = created.back();
ui()->set_node(m_node);
@@ -5730,6 +5825,7 @@ void editor_mode::extend_add(int const Side)
for (auto const &piece : pieces)
created.push_back(editor_track::create_path(*tool.track, piece));
editor_track::commit(created);
digitize_drawn(created);
push_track_snapshot({}, created);
m_node = created.back();
ui()->set_node(m_node);
@@ -6055,6 +6151,7 @@ void editor_mode::lay_finish(editor_track::snap_target const &End)
for (auto const &piece : pieces)
created.push_back(style != nullptr ? editor_track::create_path(*style, piece) : editor_track::create_path(tool.style, piece));
editor_track::commit(created);
digitize_drawn(created);
push_track_snapshot({}, created);
tool.status = format(STR_C("Laid %zu paths, %.1f m"), created.size(), length);
if (false == error.empty())