From 476909581fb0f862c4b46968b666d99ccdae0193 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 26 Oct 2026 20:40:51 +0100 Subject: [PATCH] fix(track): two switches laid right after one another, with no plain track between them, are joined, the vehicles no longer derail there: only the plain tracks looked for their neighbours, so the end of the main track of one switch at the start of the next one was joined to nothing, and an axle getting to it ran onto a track made to derail it. TTrack::join_switches joins the free ends of a switch to the ends of other switches in the same points, on both sides, with the codes of the ends the axles go on by: the end of either path of one at the start of the other, two facing at their starts, two meeting at their ends; done once the plain tracks are joined as the scenery is loaded, the old sceneries too, and as the editor joins a switch laid. Selftest pathwalk walks the tracks as an axle does: P2 to P1, P4 to P1, start to start and end to end go through either way --- application/editormodeselftestswitch.cpp | 40 ++++++++++++++++ editor/editorTrack.cpp | 2 + world/Track.cpp | 60 ++++++++++++++++++++++++ world/Track.h | 3 ++ 4 files changed, 105 insertions(+) diff --git a/application/editormodeselftestswitch.cpp b/application/editormodeselftestswitch.cpp index 0f2d5c99..31f4ce77 100644 --- a/application/editormodeselftestswitch.cpp +++ b/application/editormodeselftestswitch.cpp @@ -337,6 +337,46 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com WriteLog("SELFTEST conecheck " + name + ":" + text); return selftest_result::handled; } + // a walk along the tracks from the plain one nearest the point as an axle runs: along it (1) or against it (-1), on the path of a switch + // it's set to, or the one come onto from its end; each track, the path of a switch and the way, END where no track goes on + if (command == "pathwalk") + { + glm::dvec3 point{0.0}; + double direction{1.0}; + int steps{8}; + words >> point.x >> point.z >> direction >> steps; + TTrack *track{nullptr}; + double nearest{1e9}; + for (auto *candidate : simulation::Paths.sequence()) + { + if (candidate == nullptr || candidate->m_editorremoved || candidate->eType != tt_Normal || candidate->m_paths.empty()) + continue; + if (auto const d{geometry::plan_distance(editor_track::point_at(*candidate, editor_track::nearest_parameter(*candidate, point)), point)}; d < nearest) + { + nearest = d; + track = candidate; + } + } + std::string text{"SELFTEST pathwalk:"}; + int path{-1}; + for (int step = 0; step < steps && track != nullptr; ++step) + { + text += " " + track->name() + (path >= 0 ? "[" + std::to_string(path) + "]" : std::string{}) + (direction > 0 ? ">" : "<"); + auto const side{direction > 0 ? 1 : -1}; + auto const switched{track->eType == tt_Switch && path >= 0}; + auto const joined{switched ? (side > 0 ? track->SwitchExtension->iNextDirection[path] : track->SwitchExtension->iPrevDirection[path]) : (side > 0 ? track->iNextDirection : track->iPrevDirection)}; + auto *next{track->Connected(side, direction, switched ? path : -1)}; + if (next == nullptr) + { + text += " END"; + break; + } + path = next->eType == tt_Switch ? ((joined & 1) ? joined >> 1 : next->SwitchExtension->CurrentIndex) : -1; + track = next; + } + WriteLog(text); + return selftest_result::handled; + } if (command == "throw") { std::string name; diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 0e1a44c2..11150cb6 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -1143,6 +1143,8 @@ void editor_track::join(TTrack &Track) join(*other); } } + // and a switch right after another one + Track.join_switches(); break; } default: diff --git a/world/Track.cpp b/world/Track.cpp index f13e6f97..a54dcd9f 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -2016,6 +2016,59 @@ bool TTrack::SetConnections(int i) return false; } +void TTrack::join_switches() +{ + if( eType != tt_Switch || SwitchExtension == nullptr || simulation::Region == nullptr ) { + return; + } + // the ends of the paths as the codes of the track joined to them have them: 0 the start of a switch, where both paths begin, 1 the end of + // its first path, 3 the end of its second one; a vehicle going on onto a start keeps its direction, onto an end it turns + auto const code = []( int const Endpoint ) { return Endpoint == 3 ? 1 : Endpoint == 5 ? 3 : 0; }; + // the paths of the switch joined to its neighbours as it's set, as Switch() takes them + auto const bind = []( TTrack &Switch ) { + auto const &extension { *Switch.SwitchExtension }; + auto const state { extension.CurrentIndex & 1 }; + Switch.trNext = extension.pNexts[ state ]; + Switch.trPrev = extension.pPrevs[ state ]; + Switch.iNextDirection = extension.iNextDirection[ state ]; + Switch.iPrevDirection = extension.iPrevDirection[ state ]; + }; + auto &own { *SwitchExtension }; + for( int path = 0; path < 2; ++path ) { + for( int end = 0; end < 2; ++end ) { + if( ( end ? own.pNexts[ path ] : own.pPrevs[ path ] ) != nullptr || own.Segments[ path ] == nullptr ) { + continue; + } + auto const point { end ? own.Segments[ path ]->FastGetPoint_1() : own.Segments[ path ]->FastGetPoint_0() }; + auto const [ other, endpoint ] { simulation::Region->find_path( point, this ) }; + if( other == nullptr || other == this || other->eType != tt_Switch || other->SwitchExtension == nullptr || endpoint < 2 || endpoint > 5 ) { + continue; + } + auto &theirs { *other->SwitchExtension }; + if( end == 0 ) { + own.pPrevs[ 0 ] = own.pPrevs[ 1 ] = other; + own.iPrevDirection[ 0 ] = own.iPrevDirection[ 1 ] = code( endpoint ); + } + else { + own.pNexts[ path ] = other; + own.iNextDirection[ path ] = code( endpoint ); + } + auto const ours { end == 0 ? 0 : path ? 3 : 1 }; + if( endpoint == 2 || endpoint == 4 ) { + theirs.pPrevs[ 0 ] = theirs.pPrevs[ 1 ] = this; + theirs.iPrevDirection[ 0 ] = theirs.iPrevDirection[ 1 ] = ours; + } + else { + auto const theirpath { endpoint == 5 ? 1 : 0 }; + theirs.pNexts[ theirpath ] = this; + theirs.iNextDirection[ theirpath ] = ours; + } + bind( *other ); + } + } + bind( *this ); +} + bool TTrack::Switch(int i, float const t, float const d) { // przełączenie torów z uruchomieniem animacji if (SwitchExtension) // tory przełączalne mają doklejkę @@ -3947,6 +4000,13 @@ path_table::InitTracks() { } } + // the plain tracks are joined to the switches above, a switch right after another one only here, once they are + for( auto *track : m_items ) { + if( track->eType == tt_Switch ) { + track->join_switches(); + } + } + if( Global.CreateSwitchTrackbeds ) { // do this after all connections are established, otherwise missing switch connections // may prevent us from obtaining texture data for basic track from 'across' a switch diff --git a/world/Track.h b/world/Track.h index ed1225c5..bfd32136 100644 --- a/world/Track.h +++ b/world/Track.h @@ -315,6 +315,9 @@ public: material_handle rail_material( int const Path ) const; glm::vec2 tread_band() const; bool SetConnections(int i); + // joins the free ends of the switch to the free ends of other switches in the same points, which the plain tracks joined to switches + // leave out: a switch right after another one, at the end of either path of it, two facing at their starts or meeting at their ends + void join_switches(); bool Switch(int i, float const t = -1.f, float const d = -1.f); bool SwitchForced(int i, TDynamicObject *o); int CrossSegment(int from, int into);