17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-07 07:38:06 +01:00

fix(editor): Ctrl+Z after a crossover laid along a curve leaves nothing behind, and such a crossover reaches from a transition onto the straight: the pieces the cut of the curve split off and then took out under a switch in the same step went into the history both as made and as taken out, so an undo retired them and brought them back, loose on top of the tracks put back (the curve seen chopped along its old path); now such a piece is neither, and push_track_snapshot drops a path that is both for every tool. The design of the grade line of the outer track, raised with the crossover, goes back with the undo as well (the step keeps the library of the designs), also when a crossover is moved or taken out for the route laid again. The frame a crossover along a curve is planned in goes on 250 m along the straights past the ends of the curve, and the release may fall on the straight of the other track: pressed near the end of the last transition and dragged towards the straight, its first switch ran past the end of the curve and the crossover was refused ("A switch of the connection runs off the end of its straight"); the first switch is drawn along that frame as it's dragged. The insert takes the style and the name of the track pressed on

This commit is contained in:
maj00r
2026-10-25 17:26:05 +01:00
parent f83a1a0cd2
commit aca659754b
7 changed files with 182 additions and 15 deletions

View File

@@ -1554,7 +1554,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void insert_curved_switch(int const Direction, int const Side);
// a crossover between two tracks running side by side along a curve (editormodeconnectioncurve.cpp)
struct curve_mapping;
static bool curve_of(TTrack &Track, editor_track::straight_tolerance const &Tolerance, double const Gauge, std::vector<curve_sample> &Frame, std::vector<TTrack *> &Tracks);
// Beyond: the straights past the ends of the curve go with it up to that far, and a straight track that near the curve is taken
// as its part
static bool curve_of(TTrack &Track, editor_track::straight_tolerance const &Tolerance, double const Gauge, std::vector<curve_sample> &Frame, std::vector<TTrack *> &Tracks, double const Beyond = 0.0);
static void cut_curve(std::vector<TTrack *> &Candidates, std::vector<curve_sample> const &Frame, glm::dvec3 const &First, glm::dvec3 const &Second, std::vector<std::pair<TTrack *, editor_track::state>> &States, std::vector<TTrack *> &Created, std::vector<TTrack *> &Removed);
void insert_curved_connection();
struct curved_crossover
@@ -2453,6 +2455,10 @@ class editor_mode : public application_mode, private editor_track::observer, pri
bool m_profiles_loaded{false};
void profile_library_load();
void profile_library_mark(std::vector<scene::layer_handle> Layers);
// the last step takes the kept grade lines back to the library given, as they were before it changed them
void profiles_to_history(std::vector<stored_profile> Library);
// the kept grade lines back to the library given, the step changing them given up before it went to the history
void profiles_restore(std::vector<stored_profile> Library);
// the tracks whose grade line was fitted, by name, with the shape they had then, kept as "//$f" comment lines: a track
// changed since, by hand or taken back, isn't fitted any more
struct fitted_mark

View File

@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
#include "application/editormode.h"
#include "application/editoruilayer.h"
#include "application/editormodeshared.h"
#include "application/editormodetrackshared.h"
#include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp"
#include "editor/editorSwitchStore.hpp"
@@ -36,6 +37,37 @@ namespace
// vertical curve: the ramps break the grade line without one
double constexpr kRampShare{0.9};
double constexpr kOtherReach{10.0}; // m, from the release to the other track
double constexpr kStraightReach{track_mode::kCrossoverStraights};
// the first curved track a straight track leads to along its straight, either way, within the reach
TTrack *curve_ahead(TTrack &Track, editor_track::straight_tolerance const &Tolerance, double const Reach)
{
TTrack *found{nullptr};
auto nearest{Reach};
for (auto const throughend : {true, false})
{
TTrack const *previous{&Track};
TTrack *current{throughend ? Track.trNext : Track.trPrev};
double distance{0.0};
for (int count = 0; current != nullptr && count < 1000 && distance < nearest; ++count)
{
if (current->eType != tt_Normal || current->m_editorremoved || current->m_paths.empty())
break;
if (false == editor_track::is_straight(*current, Tolerance))
{
found = current;
nearest = distance;
break;
}
auto const &path{current->m_paths.front()};
distance += plan_distance(path.points[segment_data::point::start], path.points[segment_data::point::end]);
auto *next{current->trPrev == previous ? current->trNext : current->trPrev};
previous = current;
current = next;
}
}
return found;
}
} // namespace
@@ -111,19 +143,55 @@ struct editor_mode::curve_mapping
};
// the curve the track is a part of, as a frame of samples, and its tracks
bool editor_mode::curve_of(TTrack &Track, editor_track::straight_tolerance const &Tolerance, double const Gauge, std::vector<curve_sample> &Frame, std::vector<TTrack *> &Tracks)
bool editor_mode::curve_of(TTrack &Track, editor_track::straight_tolerance const &Tolerance, double const Gauge, std::vector<curve_sample> &Frame, std::vector<TTrack *> &Tracks, double const Beyond)
{
auto *base{&Track};
if (Beyond > 0.0 && Track.eType == tt_Normal && editor_track::is_straight(Track, Tolerance))
base = curve_ahead(Track, Tolerance, Beyond);
if (base == nullptr)
return false;
editor_track::curve curve;
editor_track::chain chain;
std::string error;
if (false == editor_track::find_curve(Track, Tolerance, Gauge, curve) || false == editor_track::find_chain(curve.from, curve.to, chain, error, true))
if (false == editor_track::find_curve(*base, Tolerance, Gauge, curve) || false == editor_track::find_chain(curve.from, curve.to, chain, error, true))
return false;
for (auto const &part : editor_track::chain_parts(chain))
if (std::find(part.tracks.begin(), part.tracks.end(), &Track) != part.tracks.end())
if (std::find(part.tracks.begin(), part.tracks.end(), base) != part.tracks.end())
{
chain = part;
break;
}
if (Beyond > 0.0)
{
// the straights past both ends, as far as the reach, taken along the curve: the one before it from its far end
auto const taken = [&](editor_track::straight_run const &Run) {
std::size_t count{0};
for (double reached{0.0}; count < Run.tracks.size() && reached < Beyond; ++count)
{
auto const &path{Run.tracks[count]->m_paths.front()};
reached += plan_distance(path.points[segment_data::point::start], path.points[segment_data::point::end]);
}
return count;
};
auto const before{editor_track::straight_beyond(chain, false, Tolerance)};
auto const after{editor_track::straight_beyond(chain, true, Tolerance)};
editor_track::chain extended;
for (auto i{taken(before)}; i-- > 0;)
{
extended.tracks.push_back(before.tracks[i]);
extended.forward.push_back(false == before.outward[i]);
}
extended.tracks.insert(extended.tracks.end(), chain.tracks.begin(), chain.tracks.end());
extended.forward.insert(extended.forward.end(), chain.forward.begin(), chain.forward.end());
for (std::size_t i = 0, count = taken(after); i < count; ++i)
{
extended.tracks.push_back(after.tracks[i]);
extended.forward.push_back(after.outward[i]);
}
chain = std::move(extended);
}
if (std::find(chain.tracks.begin(), chain.tracks.end(), &Track) == chain.tracks.end())
return false;
Frame = sample_chain(chain);
Tracks = chain.tracks;
return Frame.size() >= 2;
@@ -166,7 +234,12 @@ void editor_mode::cut_curve(std::vector<TTrack *> &Candidates, std::vector<curve
if (where > From + 0.01 && where < To - 0.01)
{
editor_track::retire(*candidate);
Removed.push_back(candidate);
// a piece split off in this step goes as if it had never been made: kept among the ones made and the ones taken out
// too, an undo would retire it and then bring it back
if (auto const made{std::find(Created.begin(), Created.end(), candidate)}; made != Created.end())
Created.erase(made);
else
Removed.push_back(candidate);
}
}
}
@@ -181,12 +254,33 @@ void editor_mode::insert_curved_connection()
};
if (tool.connection != static_cast<int>(connection::kind::crossover))
return fail(STR("Along a curve only a crossover can be laid"));
// the curve pressed on with the straights past its ends: from a transition the crossover goes on onto the straight
auto pressedframe{tool.frame};
auto pressedtracks{tool.frame_tracks};
auto *pressed{tool.frame_tracks.front()};
{
auto nearest{std::numeric_limits<double>::max()};
for (auto *track : tool.frame_tracks)
if (false == track->m_editorremoved)
if (auto const distance{plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, tool.point)), tool.point)}; distance < nearest)
{
nearest = distance;
pressed = track;
}
std::vector<curve_sample> frame;
std::vector<TTrack *> tracks;
if (curve_of(*pressed, m_straights.tolerance, m_route.design.norms.gauge, frame, tracks, kStraightReach))
{
pressedframe = std::move(frame);
pressedtracks = std::move(tracks);
}
}
// the other track: one beside this curve near the release, running along with it
TTrack *other{nullptr};
auto best{kOtherReach};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty() || std::find(tool.frame_tracks.begin(), tool.frame_tracks.end(), track) != tool.frame_tracks.end())
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty() || std::find(pressedtracks.begin(), pressedtracks.end(), track) != pressedtracks.end())
continue;
auto const distance{plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, tool.mouse)), tool.mouse)};
if (distance < best)
@@ -199,12 +293,12 @@ void editor_mode::insert_curved_connection()
return fail(STR("Drag onto the other track"));
std::vector<curve_sample> otherframe;
std::vector<TTrack *> othertracks;
if (false == curve_of(*other, m_straights.tolerance, m_route.design.norms.gauge, otherframe, othertracks))
if (false == curve_of(*other, m_straights.tolerance, m_route.design.norms.gauge, otherframe, othertracks, kStraightReach))
return fail(STR("The other track isn't a curve running along this one"));
// the inner track is the frame: the plane of its cant carries the crossover, the outer track goes up onto it
auto innerframe{tool.frame};
auto innertracks{tool.frame_tracks};
auto station{tool.along};
auto innerframe{pressedframe};
auto innertracks{pressedtracks};
auto station{nearest_station(innerframe, tool.point)};
auto const at{sample_at(innerframe, station)};
auto const across{sample_at(otherframe, nearest_station(otherframe, at.position))};
glm::dvec2 const left{-at.tangent.y, at.tangent.x};
@@ -271,7 +365,9 @@ void editor_mode::insert_curved_connection()
std::vector<std::pair<TTrack *, editor_track::state>> states;
std::vector<TTrack *> created;
std::vector<TTrack *> removed;
auto *style{tool.frame_tracks.front()};
auto *style{pressed};
// the kept grade lines before the outer track went up, the design of which goes up with it
std::optional<std::vector<stored_profile>> library;
auto const undo_all = [&]() {
for (auto *track : created)
editor_track::retire(*track);
@@ -285,6 +381,8 @@ void editor_mode::insert_curved_connection()
editor_track::revive(*track);
editor_track::commit({track});
}
if (library.has_value())
profiles_restore(std::move(*library));
straight_refresh();
};
@@ -298,7 +396,7 @@ void editor_mode::insert_curved_connection()
low = std::min(low, path.points[end].z);
high = std::max(high, path.points[end].z);
}
auto *outertrack{swapped ? tool.frame_tracks.front() : other};
auto *outertrack{swapped ? pressed : other};
for (auto const &entry : plan.switches)
WriteLog(format("Editor: curved crossover - planned switch on %d: %.3f %.3f -> %.3f %.3f; extent %.3f..%.3f", entry.line, entry.paths.front().points[segment_data::point::start].x, entry.paths.front().points[segment_data::point::start].z, entry.paths.front().points[segment_data::point::end].x, entry.paths.front().points[segment_data::point::end].z, low, high), logtype::generic);
std::size_t raised{0};
@@ -360,6 +458,8 @@ void editor_mode::insert_curved_connection()
// the design of the outer track before, kept or read from the track, to go up with it
auto *first{route.spans.front().track};
auto *last{route.spans.back().track};
profile_library_load();
library = m_profiles;
profile_take(route, first, last);
auto const before{m_profile.line};
std::vector<std::pair<TTrack *, editor_track::state>> raisestates;
@@ -431,7 +531,7 @@ void editor_mode::insert_curved_connection()
closest = track;
}
}
if (closest == nullptr || false == curve_of(*closest, m_straights.tolerance, m_route.design.norms.gauge, outerframe, outertracks))
if (closest == nullptr || false == curve_of(*closest, m_straights.tolerance, m_route.design.norms.gauge, outerframe, outertracks, kStraightReach))
{
undo_all();
return fail(STR("The outer track can't be followed after it went up"));
@@ -439,7 +539,7 @@ void editor_mode::insert_curved_connection()
}
// the inner curve again: the outer track may be split where the ramps end
if (false == swapped)
innertracks = tool.frame_tracks;
innertracks = pressedtracks;
std::vector<TTrack *> switches;
for (auto const &entry : plan.switches)
@@ -532,6 +632,9 @@ void editor_mode::insert_curved_connection()
switch_store::put(entry, entry.layer);
}
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
// the design of the outer track goes back down with it
if (library.has_value())
profiles_to_history(std::move(*library));
straight_refresh();
first_step_done(first_step::turnout);
m_node = switches.front();
@@ -723,13 +826,16 @@ bool editor_mode::slide_curved_connection(TTrack &Switch, double const Distance)
std::vector<TTrack *> removed;
TTrack *first{nullptr};
TTrack *second{nullptr};
// the design of the outer track comes down with the lift taken back, an undo puts both back
profile_library_load();
auto library{m_profiles};
if (false == take_out_crossover(*crossover, states, created, removed, first, second))
return false;
// the new places of the press and of the release, along the two curves, or the two straights
std::vector<curve_sample> firstframe, secondframe;
std::vector<TTrack *> firsttracks, secondtracks;
glm::dvec3 press, release;
if (curve_of(*first, m_straights.tolerance, m_route.design.norms.gauge, firstframe, firsttracks) && curve_of(*second, m_straights.tolerance, m_route.design.norms.gauge, secondframe, secondtracks))
if (curve_of(*first, m_straights.tolerance, m_route.design.norms.gauge, firstframe, firsttracks, kStraightReach) && curve_of(*second, m_straights.tolerance, m_route.design.norms.gauge, secondframe, secondtracks, kStraightReach))
{
auto const towards{nearest_station(firstframe, firstend) >= nearest_station(firstframe, firststart) ? 1.0 : -1.0};
press = sample_at(firstframe, nearest_station(firstframe, firststart) + towards * Distance).position;
@@ -747,6 +853,8 @@ bool editor_mode::slide_curved_connection(TTrack &Switch, double const Distance)
switch_store::follow();
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
records_to_history();
if (std::abs(crossover->record.lift) > 0.002)
profiles_to_history(std::move(library));
// laid again
if (false == lay_crossover(shape, press, release, 3.0, names))
{

View File

@@ -249,6 +249,24 @@ void editor_mode::profile_library_mark(std::vector<scene::layer_handle> Layers)
}
}
void editor_mode::profiles_to_history(std::vector<stored_profile> Library)
{
if (m_history.empty() || m_history.back().action != EditorSnapshot::Action::TrackEdit || m_history.back().profiles)
return;
m_history.back().profiles = true;
m_history.back().profile_library = std::move(Library);
}
void editor_mode::profiles_restore(std::vector<stored_profile> Library)
{
std::vector<scene::layer_handle> layers;
for (auto const *library : {&std::as_const(m_profiles), &std::as_const(Library)})
for (auto const &entry : *library)
layers.push_back(entry.layer);
m_profiles = std::move(Library);
profile_library_mark(std::move(layers));
}
std::uint64_t editor_mode::fitted_shape(TTrack const &Track)
{
// the points of the paths to a centimetre: what's read back from the files is the same shape

View File

@@ -667,16 +667,23 @@ void editor_mode::route_lay()
std::vector<std::pair<TTrack *, editor_track::state>> states;
std::vector<TTrack *> created;
std::vector<TTrack *> removed;
// the designs of the outer tracks come down with the lifts taken back, an undo puts them back
profile_library_load();
auto library{m_profiles};
bool lifted{false};
for (auto *switchtrack : switches)
if (auto const crossover{crossover_of(*switchtrack)})
{
TTrack *first{nullptr};
TTrack *second{nullptr};
lifted = lifted || std::abs(crossover->record.lift) > 0.002;
take_out_crossover(*crossover, states, created, removed, first, second);
}
switch_store::follow();
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
records_to_history();
if (lifted)
profiles_to_history(std::move(library));
show_track_tab(tab);
// the line between the same points again: taking the lift back may have cut its paths
auto const end_at = [&](glm::dvec3 const &Point) -> TTrack * {

View File

@@ -110,6 +110,17 @@ bool editor_mode::start_switch_placement()
}
tool.frame = sample_chain(chain);
tool.frame_tracks = chain.tracks;
// a crossover along the curve goes on onto the straights past its ends, its first switch drawn so as it's dragged
if (tool.connection >= 0)
{
std::vector<curve_sample> frame;
std::vector<TTrack *> tracks;
if (curve_of(*track, m_straights.tolerance, m_route.design.norms.gauge, frame, tracks, kCrossoverStraights))
{
tool.frame = std::move(frame);
tool.frame_tracks = std::move(tracks);
}
}
if (tool.frame.size() < 2)
return false;
tool.curved = true;

View File

@@ -280,6 +280,20 @@ void editor_mode::trim_history()
void editor_mode::push_track_snapshot(std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::vector<TTrack *> Removed)
{
// a path made and taken out again within the step was never there for an undo or a redo: an undo retiring the paths made and
// then reviving the ones taken out would bring it back
for (auto made{Created.begin()}; made != Created.end();)
{
auto const gone{std::find(Removed.begin(), Removed.end(), *made)};
if (gone == Removed.end())
{
++made;
continue;
}
Removed.erase(gone);
States.erase(std::remove_if(States.begin(), States.end(), [&](auto const &Entry) { return Entry.first == *made; }), States.end());
made = Created.erase(made);
}
if (States.empty() && Created.empty() && Removed.empty())
return;

View File

@@ -208,6 +208,9 @@ double const kLaySnapRadius{5.0};
float const kSnapPixels{16.0f};
double const kSwitchReach{6.0}; // a press farther from the selected track selects another one instead of placing the switch
double const kSlipReach{12.0}; // m; a slip goes to the crossing nearest the cursor, if it's this close
// m, of the straights past the ends of a curve a crossover laid along it reaches onto: from a transition it goes on onto the
// straight after it
double const kCrossoverStraights{250.0};
double const kSkewSeat{2.0}; // m; the first point laid with Alt this close to a straight track goes onto its axis
double const kSkewReach{30.0}; // m; Alt while laying turns the straight to a straight track this close to its start, or one it crosses
double const kSkewRatios[]{4.8, 6.6, 7.5, 9.0, 12.0, 14.0, 18.5, 26.5};