diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index 3996bee6..cafe5e2d 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Globals.h" #include "utilities/Logs.h" #include "world/Track.h" +#include "world/TrackRails.h" #include "world/MemCell.h" #include "world/Event.h" #include "vehicle/DynObj.h" @@ -45,6 +46,7 @@ http://mozilla.org/MPL/2.0/. // camera from the first point, looking at the second // select // frog [name] logs where the inner rails of each switch cross: distance on both paths, 1:n, throat, nose, check rails +// guards [name] logs each guard rail and what each of its ends and breaks is cut along; a fault when a cut is not along a rail or a glued end not on the guard rail of the other track // crossings [name] logs where rails of tracks cross rails of other tracks, checks that each acute crossing has one partner and one nose // frogkey name [on|off] sets or clears the frogs key of a switch (one undo step); logs the key, whether it is written, the frog and the crossings // geomhash [name] logs the number of vertices and a hash of the drawn geometry of each rail track (rails, trackbed, cut faces), to compare builds @@ -239,6 +241,109 @@ void editor_mode::selftest_step() track->frog().valid ? 1 : 0, track->rail_crossings().size(), track->m_profile1.first.c_str())); } } + else if (command == "guards") + { + std::string name; + words >> name; + int count{0}; + int faults{0}; + for (auto *track : simulation::Paths.sequence()) + { + if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 15) != 1) + continue; + if (false == name.empty() && track->name() != name) + continue; + auto const layout{track->plan_rails(true)}; + auto const size{track->rail_dimensions()}; + auto const guardoffset{-(track_rails::flangeway + size.head)}; + double const laterals[2]{size.low + guardoffset, track_rails::flangeway + size.head + size.low - 0.002 + guardoffset}; + for (int path = 0; path < 2; ++path) + { + for (int right = 0; right < 2; ++right) + { + auto const side{right ? 1 : -1}; + auto const length{track->eType == tt_Switch ? track->SwitchExtension->Segments[path]->GetLength() : track->Segment->GetLength()}; + auto const drawn = [&](glm::dvec2 const &Miter, double const Square) { + auto const at{Miter.y == 0.0 ? Square : Miter.x + Miter.y * 0.5 * (laterals[0] + laterals[1])}; + return at > 0.0 && at < length; + }; + for (auto const &guard : layout.guards[path][right]) + { + if (false == drawn(guard.frommiter, guard.from) && false == drawn(guard.tomiter, guard.to) && (guard.to <= 0.0 || guard.from >= length)) + continue; + ++count; + std::string text; + auto const end = [&](glm::dvec2 const &Miter, bool const Flared, bool const Glued) { + if (Miter.y == 0.0) + return std::string{Flared ? "flared" : "square"}; + if (false == drawn(Miter, 0.0)) + return std::string{"past the end of the track"}; + glm::dvec3 points[2]; + for (int i = 0; i < 2; ++i) + points[i] = track_rails::rail_point(*track, path, side, Miter.x + Miter.y * laterals[i], laterals[i]); + auto best{1e9}; + double lateral{0.0}, along{0.0}; + TTrack *against{nullptr}; + int againstpath{0}, againstside{0}; + for (auto *other : layout.crossed) + for (int otherpath = 0; otherpath < (other->eType == tt_Switch ? 2 : 1); ++otherpath) + for (auto const otherside : {-1, 1}) + { + double d[2], l[2]; + for (int i = 0; i < 2; ++i) + track_rails::rail_offset(*other, otherpath, otherside, points[i], d[i], l[i]); + if (std::abs(l[0]) > 0.5 || std::abs(l[1]) > 0.5 || std::abs(l[0] - l[1]) >= best) + continue; + best = std::abs(l[0] - l[1]); + lateral = 0.5 * (l[0] + l[1]); + along = 0.5 * (d[0] + d[1]); + against = other; + againstpath = otherpath; + againstside = otherside; + } + if (against == nullptr) + { + ++faults; + return std::string{"cut along nothing FAULT"}; + } + auto result{format("along %s/%d%+d at %.3f m, %.1f mm off its gauge line, skew %.1f mm", against->name().c_str(), againstpath, againstside, along, lateral * 1000.0, best * 1000.0)}; + auto fault{best > 0.002}; + if (Glued) + { + auto const theirsize{against->rail_dimensions()}; + auto const theiroffset{-(track_rails::flangeway + theirsize.head)}; + double const theirlaterals[2]{theirsize.low + theiroffset, track_rails::flangeway + theirsize.head + theirsize.low - 0.002 + theiroffset}; + auto const reach = [&](glm::dvec2 const &Miter, double const Square, bool const Latest) { + if (Miter.y == 0.0) + return Square; + auto const a{Miter.x + Miter.y * theirlaterals[0]}; + auto const b{Miter.x + Miter.y * theirlaterals[1]}; + return Latest ? std::max(a, b) : std::min(a, b); + }; + auto covered{false}; + for (auto const &theirs : against->plan_rails(true).guards[againstpath][againstside > 0]) + covered |= (along > reach(theirs.frommiter, theirs.from, false) - 0.002 && along < reach(theirs.tomiter, theirs.to, true) + 0.002); + result += covered ? ", on its guard rail" : ", no guard rail there"; + fault |= (false == covered || std::abs(lateral + track_rails::flangeway) > 0.002); + } + if (fault) + { + ++faults; + result += " FAULT"; + } + return result; + }; + text += "from " + end(guard.frommiter, guard.flarefrom, false == guard.flarefrom) + "; to " + end(guard.tomiter, guard.flareto, false == guard.flareto); + for (auto const &cut : guard.plan.cuts) + if (cut.end > 0.0 && cut.start < length) + text += format("; break %.3f-%.3f: ", cut.end, cut.start) + end(cut.endmiter, false, false) + " / " + end(cut.startmiter, false, false); + WriteLog(format("SELFTEST guard %s path %d side %+d %.3f-%.3f m: %s", track->name().c_str(), path, side, guard.from, guard.to, text.c_str())); + } + } + } + } + WriteLog(format("SELFTEST guards: %d guard rails, %d faults", count, faults)); + } else if (command == "crossings") { std::string name; diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index a6ddd8d0..d26864a8 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -289,6 +289,36 @@ void cut_faces( gfx::vertex_array const &Strip, std::size_t const Ring, bool con } } +glm::dvec3 rail_point( TTrack const &Track, int const Path, int const Side, double const Distance, double const Lateral ) { + + auto const line { make_gauge_line( path_segment( Track, Path ), Side * 0.5 * std::abs( Track.fTrackWidth ), 0.0 ) }; + glm::dvec3 point, tangent; + line.at( Distance, point, tangent ); + return point - line.normal( Distance, Side ) * Lateral; +} + +void rail_offset( TTrack const &Track, int const Path, int const Side, glm::dvec3 const &Point, double &Distance, double &Lateral ) { + + auto const line { make_gauge_line( path_segment( Track, Path ), Side * 0.5 * std::abs( Track.fTrackWidth ), 0.0 ) }; + auto const flat = []( glm::dvec3 const &V ) { return glm::dvec2{ V.x, V.z }; }; + Distance = line.distances.front(); + auto nearest { std::numeric_limits::max() }; + for( std::size_t i = 0; i < line.points.size(); ++i ) { + auto const gap { glm::length( flat( line.points[ i ] - Point ) ) }; + if( gap < nearest ) { + nearest = gap; + Distance = line.distances[ i ]; + } + } + glm::dvec3 point, tangent; + for( int step = 0; step < 8; ++step ) { + line.at( Distance, point, tangent ); + Distance += glm::dot( flat( Point - point ), glm::normalize( flat( tangent ) ) ); + } + line.at( Distance, point, tangent ); + Lateral = -glm::dot( flat( Point - point ), flat( line.normal( Distance, Side ) ) ); +} + } // namespace track_rails using namespace track_rails; diff --git a/world/TrackRails.h b/world/TrackRails.h index fb2f39b9..d76168ce 100644 --- a/world/TrackRails.h +++ b/world/TrackRails.h @@ -34,4 +34,7 @@ gfx::userdata_array worn_userdata( std::size_t const Count ); // the ends of rails left open: at track ends with nothing joined, blade tips, the gap of a frog void cut_faces( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::geometrybank_handle const &Bank, TTrack::geometryhandle_sequence &Geometry ); +glm::dvec3 rail_point( TTrack const &Track, int const Path, int const Side, double const Distance, double const Lateral ); +void rail_offset( TTrack const &Track, int const Path, int const Side, glm::dvec3 const &Point, double &Distance, double &Lateral ); + } // namespace track_rails