From fa62a6138957ae4d928992a405f8f6f8193b1261 Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 23 Oct 2026 18:25:00 +0200 Subject: [PATCH] fix(sleepers): no rail left lying on a timber without a fastening: the stock rails on a plate as wide as their foot between the brace and the slide plate, the stock rails back to back, as the wing rails of the slips with the blades outside of the diamond, on one plate under both; three rails or more close beside one another under fastenings of two rails in turn; a plate lying over the fastening of the rail beside cut short at its edge, as on one plate with it, a rail with its check rail on the fastening of the rail beside in such a plate, not left out; selftests checkseats and the bare rails in fastencheck --- application/editormodeselftestsleepers.cpp | 29 +- world/SleeperFastenings.cpp | 331 ++++++++++++++++++++- world/SleeperFastenings.h | 27 +- world/SleeperRules.h | 2 + 4 files changed, 369 insertions(+), 20 deletions(-) diff --git a/application/editormodeselftestsleepers.cpp b/application/editormodeselftestsleepers.cpp index 930bc134..63972320 100644 --- a/application/editormodeselftestsleepers.cpp +++ b/application/editormodeselftestsleepers.cpp @@ -188,27 +188,46 @@ editor_mode::selftest_result editor_mode::selftest_sleepers(std::string const &c auto const verbose{mode == "all"}; auto const start{std::chrono::steady_clock::now()}; auto const rows{track_sleepers::check_fastenings()}; - std::size_t beams{0}, overlaps{0}, tracks{0}; + std::size_t beams{0}, overlaps{0}, tracks{0}, bare{0}; std::array parts{}; for (auto const &row : rows) { beams += row.beams; overlaps += row.overlaps; + bare += row.bares.size(); tracks += (row.overlaps > 0 ? 1 : 0); for (int part = 0; part < track_sleepers::part_count; ++part) parts[part] += row.parts[part]; - WriteLog(format("SELFTEST fastencheck %s: %zu timbers, %zu plates, %zu chairs, %zu checks, %zu overlaps%s", - row.track.c_str(), row.beams, row.parts[track_sleepers::part_plate], row.parts[track_sleepers::part_chair], row.parts[track_sleepers::part_check], row.overlaps, + WriteLog(format("SELFTEST fastencheck %s: %zu timbers, %zu plates, %zu chairs, %zu checks, %zu bare rails, %zu overlaps%s", + row.track.c_str(), row.beams, row.parts[track_sleepers::part_plate], row.parts[track_sleepers::part_chair], row.parts[track_sleepers::part_check], row.bares.size(), row.overlaps, row.overlaps > 0 ? format(", worst %.3f m %s at %.2f %.2f", row.worst, row.worstparts.c_str(), row.worstat.x, row.worstat.z).c_str() : "")); + if (verbose) + for (auto const &at : row.bares) + WriteLog(format("SELFTEST fastencheck bare %s at %.3f %.3f", row.track.c_str(), at.x, at.y)); if (verbose) for (auto const &overlap : row.cases) WriteLog(format("SELFTEST fastencheck overlap %s %d %d apart %.3f over %.3f at %.2f %.2f", row.track.c_str(), overlap.parts[0], overlap.parts[1], overlap.apart, overlap.over, overlap.at.x, overlap.at.z)); } - WriteLog(format("SELFTEST fastencheck: %zu tracks, %zu timbers, %zu plates, %zu chairs, %zu checks, %zu overlaps on %zu tracks; in %.1f ms", - rows.size(), beams, parts[track_sleepers::part_plate], parts[track_sleepers::part_chair], parts[track_sleepers::part_check], overlaps, tracks, + WriteLog(format("SELFTEST fastencheck: %zu tracks, %zu timbers, %zu plates, %zu chairs, %zu checks, %zu bare rails, %zu overlaps on %zu tracks; in %.1f ms", + rows.size(), beams, parts[track_sleepers::part_plate], parts[track_sleepers::part_chair], parts[track_sleepers::part_check], bare, overlaps, tracks, std::chrono::duration(std::chrono::steady_clock::now() - start).count())); return selftest_result::handled; } + if (command == "checkseats") + { + // the check rails with timbers under them and no fastening seating them, along how much of them + auto const gaps{track_sleepers::check_rail_seats()}; + double bare{0.0}; + std::size_t unsupported{0}; + for (auto const &gap : gaps) + { + bare += gap.bare; + unsupported += gap.unsupported; + WriteLog(format("SELFTEST checkseats %s path %d side %+d %.2f-%.2f: %.0f of %zu timbers bare, %zu with nothing under, first at %.2f %.2f", gap.track.c_str(), gap.path, gap.side, gap.from, gap.to, gap.bare, gap.timbers, gap.unsupported, gap.at.x, gap.at.y)); + } + WriteLog(format("SELFTEST checkseats: %zu check rails with %.0f timbers bare, %zu with nothing under", gaps.size(), bare, unsupported)); + return selftest_result::handled; + } if (command == "sleepercheck") { auto const start{std::chrono::steady_clock::now()}; diff --git a/world/SleeperFastenings.cpp b/world/SleeperFastenings.cpp index c9cd5945..b18ac69b 100644 --- a/world/SleeperFastenings.cpp +++ b/world/SleeperFastenings.cpp @@ -106,13 +106,12 @@ fastened_rails::fastened_rails( TTrack &Track ) { } } -std::vector fastened_rails::on( beam const &Beam, beam_model const &Model ) const { +std::vector fastened_rails::rails_on( beam const &Beam ) const { - std::vector result; auto const span { Beam.to - Beam.from }; auto const length { glm::length( plan( span ) ) }; if( length < 0.1 ) { - return result; + return {}; } auto const start { plan( Beam.from ) }; auto const axis { plan( span ) / length }; @@ -173,12 +172,71 @@ std::vector fastened_rails::on( beam const &Beam, beam_model const &M } index = next; } + return rails; +} + +std::vector fastened_rails::on( beam const &Beam, beam_model const &Model ) const { + + std::vector result; + auto const span { Beam.to - Beam.from }; + auto const length { glm::length( plan( span ) ) }; + if( length < 0.1 ) { + return result; + } + auto const axis { plan( span ) / length }; + auto const rails { rails_on( Beam ) }; auto const point = [&]( double const At ) { return Beam.from + span * ( At / length ); }; auto const has = [&]( int const Part ) { return Model.parts[ Part ] != nullptr; }; + // the rails from the first to the last under fastenings of two rails in turn, each with its seats under two rails side by side + // and over as many of the rails after them as it reaches + auto const crowd = [&]( std::size_t First, std::size_t const Last ) { + auto const &reach { Model.spans[ part_check ] }; + while( First <= Last ) { + std::size_t covered { 0 }; + auto origin { 0.0 }; + auto turn { 1.0 }; + for( auto seat { First }; seat < Last; ++seat ) { + for( auto const direction : { 1.0, -1.0 } ) { + auto const at { 0.5 * ( rails[ seat ].at + rails[ seat + 1 ].at - direction * check_seats ) }; + auto const from { at + std::min( direction * reach.x, direction * reach.y ) }; + auto const to { at + std::max( direction * reach.x, direction * reach.y ) }; + if( rails[ First ].at < from ) { + continue; + } + std::size_t count { 0 }; + while( First + count <= Last && rails[ First + count ].at <= to ) { + ++count; + } + if( count > covered ) { + covered = count; + origin = at; + turn = direction; + } + } + } + if( covered < 2 ) { + result.push_back( { part_plate, point( rails[ First ].at ), rails[ First ].inward } ); + ++First; + continue; + } + result.push_back( { part_check, point( origin ), axis * turn, 1.f, static_cast( 0.5 * check_seats ), 0.f, true } ); + First += covered; + } + }; for( std::size_t index = 0; index < rails.size(); ++index ) { auto const &rail { rails[ index ] }; + // three rails or more close beside one another, as at the crossings of the slips, none a stock rail or with a check rail + auto last { index }; + while( last + 1 < rails.size() && rails[ last + 1 ].at - rails[ last ].at < fastening_pair && rails[ last ].kind == role::plain && rails[ last + 1 ].kind == role::plain ) { + ++last; + } + if( last >= index + 2 && has( part_check ) ) { + crowd( index, last ); + index = last; + continue; + } if( index + 1 < rails.size() && rails[ index + 1 ].at - rail.at < fastening_pair && rail.kind != role::stock && rails[ index + 1 ].kind != role::stock ) { auto const &other { rails[ index + 1 ] }; // a rail of the two with its check rail on the side away from the other one in the fastening with the check rail, @@ -202,7 +260,7 @@ std::vector fastened_rails::on( beam const &Beam, beam_model const &M result.push_back( { part_plate, point( 0.5 * ( rail.at + other.at ) ), rail.inward } ); } else if( has( part_check ) ) { - result.push_back( { part_check, point( 0.5 * ( rail.at + other.at - check_seats ) ), axis, 1.f, static_cast( 0.5 * check_seats ) } ); + result.push_back( { part_check, point( 0.5 * ( rail.at + other.at - check_seats ) ), axis, 1.f, static_cast( 0.5 * check_seats ), 0.f, true } ); } else { result.push_back( { part_plate, point( rail.at ), rail.inward } ); @@ -214,6 +272,14 @@ std::vector fastened_rails::on( beam const &Beam, beam_model const &M switch( rail.kind ) { case role::stock: { result.push_back( { has( part_chair ) ? part_chair : part_plate, point( rail.at ), -rail.inward } ); + if( has( part_chair ) ) { + // the rail on a plate as wide as its foot, between the brace and the slide plate + auto const &plate { Model.spans[ part_plate ] }; + auto const low { -rail.foot }; + auto const high { std::min( rail.foot, Model.spans[ part_chair ].x ) }; + auto const scale { ( high - low ) / ( plate.y - plate.x ) }; + result.push_back( { part_plate, point( rail.at ), -rail.inward, static_cast( scale ), 0.f, static_cast( low - scale * plate.x ) } ); + } if( has( part_slide ) ) { // the slide plate from the edge of the foot of the stock rail on, not under it auto const &slide { Model.spans[ part_slide ] }; @@ -236,6 +302,25 @@ std::vector fastened_rails::on( beam const &Beam, beam_model const &M return result; } +std::vector fastened_rails::bare( beam const &Beam, beam_model const &Model, std::vector const &Fastenings ) const { + + auto const span { plan( Beam.to - Beam.from ) }; + auto const length { glm::length( span ) }; + std::vector result; + for( auto const &rail : rails_on( Beam ) ) { + auto const at { plan( Beam.from ) + span * ( rail.at / length ) }; + auto const under { std::any_of( Fastenings.begin(), Fastenings.end(), [&]( fastening const &Fastening ) { + auto const along { glm::dot( at - plan( Fastening.point ), Fastening.across ) }; + auto const a { Fastening.placed( Model.spans[ Fastening.part ].x, Fastening.scale ) }; + auto const b { Fastening.placed( Model.spans[ Fastening.part ].y, Fastening.scale ) }; + return along >= std::min( a, b ) - 0.01 && along <= std::max( a, b ) + 0.01; } ) }; + if( false == under ) { + result.push_back( at ); + } + } + return result; +} + void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, beam_model const &Model ) { auto const start { plan( Beam.from ) }; @@ -324,6 +409,22 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, } } return false; }; + auto const checks = [&]( std::pair const &Rail ) { + for( auto member { Rail.first }; member < Rail.second; ++member ) { + auto const &fastening { Fastenings[ order[ member ] ] }; + if( fastening.part == part_check && false == fastening.both ) { + return true; + } + } + return false; }; + auto const faces = [&]( std::pair const &Rail, double const Toward ) { + for( auto member { Rail.first }; member < Rail.second; ++member ) { + auto const &fastening { Fastenings[ order[ member ] ] }; + if( fastening.part == part_check && false == fastening.both && glm::dot( fastening.across, axis ) * Toward > 0.0 ) { + return true; + } + } + return false; }; auto const plates = [&]( std::pair const &Rail ) { for( auto member { Rail.first }; member < Rail.second; ++member ) { if( Fastenings[ order[ member ] ].part != part_plate ) { @@ -356,6 +457,37 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, for( auto member { Rail.first }; member < Rail.second; ++member ) { dropped[ order[ member ] ] = true; } }; + // the plates of the rail with their end toward the rail beside Room from it, the other end kept where it is + auto const clip = [&]( std::pair const &Rail, double const Toward, double const Room ) { + if( Room < 0.0 ) { + return false; + } + for( auto member { Rail.first }; member < Rail.second; ++member ) { + auto &fastening { Fastenings[ order[ member ] ] }; + auto const &span { Model.spans[ fastening.part ] }; + auto const turn { glm::dot( fastening.across, axis ) }; + auto const toward { turn * Toward > 0.0 ? span.y : span.x }; + auto const away { turn * Toward > 0.0 ? span.x : span.y }; + auto const stays { fastening.placed( away, 1.0 ) }; + auto const scale { ( Toward * turn * Room - stays ) / ( toward - away ) }; + if( scale < clip_least ) { + return false; + } + fastening.pivot = static_cast( away ); + fastening.shift = static_cast( stays - away ); + fastening.scale = static_cast( scale ); + } + return true; }; + // the fastenings of the rail made plates of their own + auto const replate = [&]( std::pair const &Rail ) -> std::pair const & { + for( auto member { Rail.first }; member < Rail.second; ++member ) { + auto &fastening { Fastenings[ order[ member ] ] }; + fastening.part = part_plate; + fastening.scale = 1.f; + fastening.pivot = 0.f; + fastening.shift = 0.f; + } + return Rail; }; // the brace of a chair where the rail beside comes too close for it, its slide plate kept auto const brace = [&]( std::pair const &Rail, int const Side ) { for( auto member { Rail.first }; member < Rail.second; ++member ) { @@ -366,11 +498,28 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, } } return false; }; - if( false == chair( second ) && apart <= firstreach.y ) { + // of two rails with check rails, the one with its check rail toward the other one, lying on the fastening of the other + // one then, goes in a plate cut short at that fastening, or without its own, the other one keeping its check rail seated + if( checks( first ) && checks( second ) && faces( first, 1.0 ) != faces( second, -1.0 ) ) { + if( faces( first, 1.0 ) && apart - check_seats <= secondreach.x ) { + if( false == clip( replate( first ), 1.0, apart - secondreach.x - fastening_gap ) ) { + drop( first ); + } + kept = index; + continue; + } + if( faces( second, -1.0 ) && apart - check_seats <= firstreach.y ) { + if( false == clip( replate( second ), -1.0, apart - firstreach.y - fastening_gap ) ) { + drop( second ); + } + continue; + } + } + if( false == chair( second ) && apart <= firstreach.y && ( false == checks( second ) || checks( first ) ) ) { drop( second ); continue; } - if( false == chair( first ) && apart <= secondreach.x ) { + if( false == chair( first ) && apart <= secondreach.x && ( false == checks( first ) || checks( second ) ) ) { drop( first ); } else { @@ -379,14 +528,44 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, brace( second, 0 ); } } - // a rail in a plate still lying over the fastening of a rail beside held with more than a plate goes without its own + // a rail in a plate still lying over the fastening of a rail beside held with more than a plate gets its plate cut short + // at the edge of that fastening, as on one plate with it, or goes without its own if the plate would no longer be under it if( overlaps() ) { if( plates( second ) && false == plates( first ) ) { - drop( second ); - continue; + if( false == clip( second, -1.0, apart - extent( first ).y - fastening_gap ) ) { + drop( second ); + continue; + } } - if( plates( first ) && false == plates( second ) ) { - drop( first ); + else if( plates( first ) && false == plates( second ) ) { + if( false == clip( first, 1.0, apart - extent( second ).x - fastening_gap ) ) { + drop( first ); + } + } + } + // the plates of two rails still lying over each other, as of the stock rails back to back, one plate under both + auto const plate = [&]( std::pair const &Rail ) { + for( auto member { Rail.first }; member < Rail.second; ++member ) { + if( Fastenings[ order[ member ] ].part == part_plate && false == dropped[ order[ member ] ] ) { + return order[ member ]; + } + } + return Fastenings.size(); }; + auto const firstplate { plate( first ) }; + auto const secondplate { plate( second ) }; + if( firstplate < Fastenings.size() && secondplate < Fastenings.size() ) { + auto &one { Fastenings[ firstplate ] }; + auto const &other { Fastenings[ secondplate ] }; + auto const from { -reach( one, one.scale ).x }; + auto const to { apart + reach( other, other.scale ).y }; + if( reach( one, one.scale ).y + reach( other, other.scale ).x > apart ) { + auto const &span { Model.spans[ part_plate ] }; + auto const turn { glm::dot( one.across, axis ) }; + auto const scale { ( to - from ) / ( span.y - span.x ) }; + one.pivot = 0.f; + one.scale = static_cast( scale ); + one.shift = static_cast( ( turn > 0.0 ? from : -to ) - scale * span.x ); + dropped[ secondplate ] = true; } } kept = index; @@ -431,7 +610,11 @@ std::vector check_fastenings() { glm::dvec3 point; }; std::vector footprints; - for( auto const &fastening : rails->on( beam, *model ) ) { + auto const fastenings { rails->on( beam, *model ) }; + for( auto const &at : rails->bare( beam, *model, fastenings ) ) { + row.bares.push_back( at ); + } + for( auto const &fastening : fastenings ) { ++row.parts[ fastening.part ]; auto const at { glm::dot( plan( fastening.point ) - start, axis ) }; auto const turn { glm::dot( fastening.across, axis ) }; @@ -461,4 +644,128 @@ std::vector check_fastenings() { return result; } +std::vector check_rail_seats() { + + // the check rails, by points along their middle + struct check_line { + check_rail_gap gap; + std::vector points; + glm::dvec2 low { std::numeric_limits::max() }; + glm::dvec2 high { std::numeric_limits::lowest() }; + }; + std::vector lines; + for( auto *track : simulation::Paths.sequence() ) { + if( track == nullptr || track->m_editorremoved || false == bedded( *track ) || false == track_rails::cut_rails( *track ) ) { + continue; + } + auto const size { track->rail_dimensions() }; + auto const middle { 0.5 * std::abs( track->fTrackWidth ) + size.web }; + auto const &checks { track_rails::rail_checks( *track ) }; + for( int path = 0; path < path_count( *track ); ++path ) { + auto const *segment { path_segment( *track, path ) }; + if( segment == nullptr ) { + continue; + } + auto const length { segment->GetLength() }; + for( auto const side : { -1, 1 } ) { + for( auto const &check : checks[ path ][ side > 0 ? 1 : 0 ] ) { + check_line line { { track->name(), path, side, check.from, check.to } }; + auto const from { std::max( 0.0, check.from ) }; + auto const to { std::min( length, check.to ) }; + auto const count { static_cast( std::ceil( ( to - from ) / fastening_step ) ) }; + for( int step = 0; step <= count && to > from; ++step ) { + glm::dvec3 point, across; + segment->plan_frame( from + ( to - from ) * step / std::max( 1, count ), point, across ); + auto const flat { plan( across ) }; + if( glm::length( flat ) < 1e-9 ) { + continue; + } + auto const inward { glm::normalize( flat ) * static_cast( -side ) }; + auto const position { plan( point ) - inward * ( middle - check_seats ) }; + line.points.push_back( position ); + line.low = glm::min( line.low, position ); + line.high = glm::max( line.high, position ); + } + if( line.points.size() > 1 ) { + lines.push_back( std::move( line ) ); + } + } + } + } + } + // where each timber with the fastenings crosses a check rail, a seat of a fastening of the timber there + std::vector timbers( lines.size(), 0 ); + for( auto *track : simulation::Paths.sequence() ) { + if( track == nullptr || track->m_editorremoved || false == track->m_sleepers3d.active ) { + continue; + } + auto const *model { timber_model( *track ) }; + if( model == nullptr || false == model->fastened() ) { + continue; + } + std::optional rails; + for( auto const &beam : place( *track ) ) { + if( beam.plain ) { + continue; + } + if( false == rails.has_value() ) { + rails.emplace( *track ); + } + auto const start { plan( beam.from ) }; + auto const span { plan( beam.to - beam.from ) }; + auto const low { glm::min( start, start + span ) }; + auto const high { glm::max( start, start + span ) }; + std::vector seats; + auto const fastenings { rails->on( beam, *model ) }; + auto const under = [&]( glm::dvec2 const &Point ) { + return std::any_of( fastenings.begin(), fastenings.end(), [&]( fastening const &Fastening ) { + auto const along { glm::dot( Point - plan( Fastening.point ), Fastening.across ) }; + auto const a { Fastening.placed( model->spans[ Fastening.part ].x, Fastening.scale ) }; + auto const b { Fastening.placed( model->spans[ Fastening.part ].y, Fastening.scale ) }; + return along >= std::min( a, b ) && along <= std::max( a, b ); } ); }; + for( auto const &fastening : fastenings ) { + seats.push_back( plan( fastening.point ) + fastening.across * fastening.placed( 0.0, fastening.scale ) ); + if( fastening.part == part_check ) { + seats.push_back( plan( fastening.point ) + fastening.across * fastening.placed( check_seats, fastening.scale ) ); + } + } + for( std::size_t index = 0; index < lines.size(); ++index ) { + auto &line { lines[ index ] }; + if( line.high.x < low.x || line.low.x > high.x || line.high.y < low.y || line.low.y > high.y ) { + continue; + } + for( std::size_t point = 1; point < line.points.size(); ++point ) { + auto const chord { line.points[ point ] - line.points[ point - 1 ] }; + double u, v; + if( false == track_rails::meet( start, span, line.points[ point - 1 ], chord, 1e-12, u, v ) || u < 0.0 || u > 1.0 || v < 0.0 || v >= 1.0 ) { + continue; + } + auto const at { start + span * u }; + ++timbers[ index ]; + auto const seated { std::any_of( seats.begin(), seats.end(), [&]( glm::dvec2 const &Seat ) { + return glm::length( Seat - at ) < 0.03; } ) }; + if( false == seated ) { + if( line.gap.bare == 0.0 ) { + line.gap.at = at; + } + line.gap.bare += 1.0; + if( false == under( at ) ) { + ++line.gap.unsupported; + } + } + break; + } + } + } + } + std::vector result; + for( std::size_t index = 0; index < lines.size(); ++index ) { + if( lines[ index ].gap.bare > 0.0 ) { + lines[ index ].gap.timbers = timbers[ index ]; + result.push_back( lines[ index ].gap ); + } + } + return result; +} + } // namespace track_sleepers diff --git a/world/SleeperFastenings.h b/world/SleeperFastenings.h index c4f36a82..98a52fc4 100644 --- a/world/SleeperFastenings.h +++ b/world/SleeperFastenings.h @@ -27,9 +27,10 @@ struct fastening { int part { part_plate }; glm::dvec3 point { 0.0 }; // under the middle of the rail, on the top of the timber glm::dvec2 across { 1.0, 0.0 }; // unit vector in plan square to the rail, the way the +x of the part goes - float scale { 1.f }; // along its x, a plate or a slide plate narrowed where it'd lie over the fastenings of rails close beside + float scale { 1.f }; // along its x, a plate or a slide plate narrowed where it'd lie over the fastenings of rails close beside, a plate stretched under two float pivot { 0.f }; // the x of the part kept in place as it's narrowed float shift { 0.f }; // along its x, how far the pivot is moved + bool both { false }; // the fastening of two rails close beside each other, neither with a check rail of its own seated // where an x of the part lands along its across, at the scale double placed( double const X, double const Scale ) const { return pivot + shift + Scale * ( X - pivot ); } @@ -42,6 +43,8 @@ public: explicit fastened_rails( TTrack &Track ); // the fastenings of the rails on the timber, of the parts the model has std::vector on( beam const &Beam, beam_model const &Model ) const; + // where the rails on the timber lie over none of the fastenings + std::vector bare( beam const &Beam, beam_model const &Model, std::vector const &Fastenings ) const; private: // what a rail is where it lies on a timber @@ -77,8 +80,10 @@ private: double foot { 0.0 }; }; - // the fastenings of rails close by fitted to the room between them: narrowed across, and a rail lying on the fastening of the - // rail beside it left without one of its own + // the rails lying on the timber, in the order along it, one where several lie on one another, but the blades + std::vector rails_on( beam const &Beam ) const; + // the fastenings of rails close by fitted to the room between them: narrowed across, a plate cut short at the fastening of the + // rail beside it or left out, the plates of two rails lying over each other made one static void fit( std::vector &Fastenings, beam const &Beam, beam_model const &Model ); std::vector m_rails; @@ -90,6 +95,7 @@ struct fastening_row { std::size_t beams { 0 }; std::array parts {}; std::size_t overlaps { 0 }; // fastenings lying over one another on a timber, along it + std::vector bares; // where rails lie on a timber over no fastening double worst { 0.0 }; glm::dvec3 worstat { 0.0 }; std::string worstparts; @@ -104,4 +110,19 @@ struct fastening_row { // those of all the tracks laying timbers with fastenings std::vector check_fastenings(); +// a check rail of a track with timbers under it and no fastening seating it there, along some of its length +struct check_rail_gap { + std::string track; + int path { 0 }; + int side { 1 }; + double from { 0.0 }; // the check rail, along the path + double to { 0.0 }; + std::size_t timbers { 0 }; // with the fastenings, under it + double bare { 0.0 }; // of those, with no seat under it + std::size_t unsupported { 0 }; // of those, with no fastening under it at all + glm::dvec2 at { 0.0 }; // where the first of those crosses it +}; +// those of all the tracks with fastenings in the model of their timbers +std::vector check_rail_seats(); + } // namespace track_sleepers diff --git a/world/SleeperRules.h b/world/SleeperRules.h index d4573e9d..b1e3062b 100644 --- a/world/SleeperRules.h +++ b/world/SleeperRules.h @@ -62,6 +62,8 @@ double constexpr check_seats { 0.113 }; // the room left between the fastenings of rails close by, and how narrow their plates get to keep it double constexpr fastening_gap { 0.01 }; double constexpr plate_least { 0.6 }; +// a plate cut short at the fastening of the rail beside, as on one plate with it, no narrower than this +double constexpr clip_least { 0.4 }; double constexpr slide_least { 0.25 }; // a fastening of two rails narrowed no more than this, the rails lying a little off its seats then double constexpr check_least { 0.8 };