From b512460fdfa345d36aff62b0a057e2af3029726c Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 23 Oct 2026 17:25:02 +0200 Subject: [PATCH] feat(sleepers): the fastenings fitted on the switches crossing and the double ones: the chair of the model split into its brace and its slide plate, the slide plate from the edge of the foot of the stock rail on, not under it; the plates and the slide plates narrowed where the rails lie close, the fastening of two rails centred between them, a rail lying on the fastening of the rail beside it left without one of its own, the brace dropped where the rail beside comes too close; selftest fastencheck --- application/editormodeselftestsleepers.cpp | 32 ++- world/SleeperFastenings.cpp | 231 ++++++++++++++++++++- world/SleeperFastenings.h | 34 +++ world/SleeperModels.cpp | 27 ++- world/SleeperRules.h | 12 +- world/TrackSleepers.cpp | 13 +- world/TrackSleepers.h | 6 +- 7 files changed, 335 insertions(+), 20 deletions(-) diff --git a/application/editormodeselftestsleepers.cpp b/application/editormodeselftestsleepers.cpp index 97969ae9..930bc134 100644 --- a/application/editormodeselftestsleepers.cpp +++ b/application/editormodeselftestsleepers.cpp @@ -162,7 +162,6 @@ editor_mode::selftest_result editor_mode::selftest_sleepers(std::string const &c WriteLog("SELFTEST fastenings: no track " + name + " with the fastenings in the model of its timbers"); return selftest_result::handled; } - std::array const names{"begin", "main", "end", "plate", "chair", "check"}; track_sleepers::fastened_rails const rails{*track}; std::array counts{}; for (auto const &beam : track_sleepers::place(*track)) @@ -173,7 +172,7 @@ editor_mode::selftest_result editor_mode::selftest_sleepers(std::string const &c std::string text{format("SELFTEST fastenings %s at %.2f %.2f:", track->name().c_str(), middle.x, middle.z)}; for (auto const &fastening : rails.on(beam, *model)) { - text += format(" %s %.3f %.3f %+.0f", names[fastening.part], fastening.point.x, fastening.point.z, glm::degrees(std::atan2(fastening.across.y, fastening.across.x))); + text += format(" %s %.3f %.3f %+.0f", track_sleepers::part_names[fastening.part], fastening.point.x, fastening.point.z, glm::degrees(std::atan2(fastening.across.y, fastening.across.x))); ++counts[fastening.part]; } WriteLog(text); @@ -181,6 +180,35 @@ editor_mode::selftest_result editor_mode::selftest_sleepers(std::string const &c WriteLog(format("SELFTEST fastenings %s: %d plates, %d chairs, %d checks", track->name().c_str(), counts[track_sleepers::part_plate], counts[track_sleepers::part_chair], counts[track_sleepers::part_check])); return selftest_result::handled; } + if (command == "fastencheck") + { + // the fastenings of all the tracks laying timbers with them: of each kind, and those over one another on a timber + std::string mode; + words >> mode; + 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::array parts{}; + for (auto const &row : rows) + { + beams += row.beams; + overlaps += row.overlaps; + 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, + 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 &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, + std::chrono::duration(std::chrono::steady_clock::now() - start).count())); + 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 641a187f..72b084c7 100644 --- a/world/SleeperFastenings.cpp +++ b/world/SleeperFastenings.cpp @@ -15,6 +15,11 @@ http://mozilla.org/MPL/2.0/. #include "world/SleeperRules.h" #include "world/TrackRails.h" #include "world/Track.h" +#include "simulation/simulation.h" + +#include +#include +#include namespace track_sleepers { @@ -42,7 +47,8 @@ fastened_rails::fastened_rails( TTrack &Track ) { tracks.push_back( Other ); } } ); for( auto *track : tracks ) { - auto const middle { 0.5 * std::abs( track->fTrackWidth ) + track->rail_dimensions().web }; + auto const size { track->rail_dimensions() }; + auto const middle { 0.5 * std::abs( track->fTrackWidth ) + size.web }; auto const cut { track_rails::cut_rails( *track ) }; auto const switched { track->eType == tt_Switch && track->SwitchExtension != nullptr }; // the side of the blade of the path, and the stretch along it from the tip to the heel @@ -63,6 +69,7 @@ fastened_rails::fastened_rails( TTrack &Track ) { line.track = track; line.path = path; line.side = side; + line.foot = size.web - size.low; line.low = glm::dvec2 { std::numeric_limits::max() }; line.high = glm::dvec2 { std::numeric_limits::lowest() }; for( int step = 0; step <= count; ++step ) { @@ -138,7 +145,8 @@ std::vector fastened_rails::on( beam const &Beam, beam_model const &M hits.push_back( { u * length, line.at( glm::mix( line.distances[ index - 1 ], line.distances[ index ], v ) ), - glm::normalize( glm::mix( line.inward[ index - 1 ], line.inward[ index ], v ) ) } ); + glm::normalize( glm::mix( line.inward[ index - 1 ], line.inward[ index ], v ) ), + line.foot } ); } } std::sort( hits.begin(), hits.end(), []( hit const &Left, hit const &Right ) { return Left.at < Right.at; } ); @@ -177,7 +185,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( rail.at ), axis } ); + result.push_back( { part_check, point( 0.5 * ( rail.at + other.at - check_seats ) ), axis, 1.f, static_cast( 0.5 * check_seats ) } ); } else { result.push_back( { part_plate, point( rail.at ), rail.inward } ); @@ -189,10 +197,16 @@ 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_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 ] }; + auto const edge { std::min( slide.y, -rail.foot ) }; + result.push_back( { part_slide, point( rail.at ), -rail.inward, static_cast( ( edge - slide.x ) / ( slide.y - slide.x ) ), slide.y, static_cast( edge - slide.y ) } ); + } break; } case role::checked: { - result.push_back( { has( part_check ) ? part_check : part_plate, point( rail.at ), rail.inward } ); + result.push_back( { has( part_check ) ? part_check : part_plate, point( rail.at ), rail.inward, 1.f, has( part_check ) ? static_cast( 0.5 * check_seats ) : 0.f } ); break; } default: { @@ -201,6 +215,215 @@ std::vector fastened_rails::on( beam const &Beam, beam_model const &M } } } + fit( result, Beam, Model ); + return result; +} + +void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, beam_model const &Model ) { + + auto const start { plan( Beam.from ) }; + auto const axis { glm::normalize( plan( Beam.to - Beam.from ) ) }; + auto const at = [&]( fastening const &Fastening ) { + return glm::dot( plan( Fastening.point ) - start, axis ); }; + // how far it reaches along the timber before and after where it is, at the scale + auto const reach = [&]( fastening const &Fastening, double const Scale ) { + auto const &span { Model.spans[ Fastening.part ] }; + auto const turn { glm::dot( Fastening.across, axis ) }; + auto const a { turn * Fastening.placed( span.x, Scale ) }; + auto const b { turn * Fastening.placed( span.y, Scale ) }; + return glm::dvec2 { -std::min( a, b ), std::max( a, b ) }; }; + auto const least = [&]( fastening const &Fastening ) { + switch( Fastening.part ) { + case part_plate: { return plate_least; } + case part_slide: { return slide_least; } + case part_check: { return check_least; } + default: { return 1.0; } + } }; + // those of one rail together, the rails in the order along the timber + std::vector order( Fastenings.size() ); + std::iota( order.begin(), order.end(), std::size_t { 0 } ); + std::stable_sort( order.begin(), order.end(), [&]( std::size_t const Left, std::size_t const Right ) { + return at( Fastenings[ Left ] ) < at( Fastenings[ Right ] ) - 1e-6; } ); + std::vector> rails; + for( std::size_t index = 0; index < order.size(); ) { + auto next { index + 1 }; + while( next < order.size() && at( Fastenings[ order[ next ] ] ) - at( Fastenings[ order[ index ] ] ) < 1e-6 ) { + ++next; + } + rails.emplace_back( index, next ); + index = next; + } + for( std::size_t index = 1; index < rails.size(); ++index ) { + auto const [ firstfrom, firstto ] { rails[ index - 1 ] }; + auto const [ secondfrom, secondto ] { rails[ index ] }; + auto const room { at( Fastenings[ order[ secondfrom ] ] ) - at( Fastenings[ order[ firstfrom ] ] ) - fastening_gap }; + // the ones of both reaching less toward the other as they're narrowed; with them narrowed by the share, how far the two + // lie over each other + std::vector narrowed; + for( auto member { firstfrom }; member < secondto; ++member ) { + auto const &fastening { Fastenings[ order[ member ] ] }; + auto const side { member < firstto ? 1 : 0 }; + if( reach( fastening, std::min( fastening.scale, least( fastening ) ) )[ side ] < reach( fastening, fastening.scale )[ side ] - 1e-6 ) { + narrowed.push_back( order[ member ] ); + } + } + auto const over = [&]( double const Share ) { + auto after { 0.0 }; + auto before { 0.0 }; + for( auto member { firstfrom }; member < secondto; ++member ) { + auto const &fastening { Fastenings[ order[ member ] ] }; + auto const shrinks { std::find( narrowed.begin(), narrowed.end(), order[ member ] ) != narrowed.end() }; + auto const scale { shrinks ? glm::mix( fastening.scale, least( fastening ), Share ) : fastening.scale }; + if( member < firstto ) { + after = std::max( after, reach( fastening, scale ).y ); + } + else { + before = std::max( before, reach( fastening, scale ).x ); + } + } + return after + before - room; }; + if( narrowed.empty() || over( 0.0 ) <= 0.0 ) { + continue; + } + auto share { 1.0 }; + if( over( 1.0 ) < 0.0 ) { + auto low { 0.0 }; + for( int step = 0; step < 20; ++step ) { + auto const middle { 0.5 * ( low + share ) }; + ( over( middle ) > 0.0 ? low : share ) = middle; + } + } + for( auto const index : narrowed ) { + auto &fastening { Fastenings[ index ] }; + fastening.scale = static_cast( glm::mix( fastening.scale, least( fastening ), share ) ); + } + } + // a rail still lying on the fastening of the rail beside it goes without one of its own, unless it's a chair + std::vector dropped( Fastenings.size(), false ); + auto const chair = [&]( std::pair const &Rail ) { + for( auto member { Rail.first }; member < Rail.second; ++member ) { + if( Fastenings[ order[ member ] ].part == part_chair || Fastenings[ order[ member ] ].part == part_slide ) { + return true; + } + } + return false; }; + auto const extent = [&]( std::pair const &Rail ) { + glm::dvec2 result { 0.0 }; + for( auto member { Rail.first }; member < Rail.second; ++member ) { + if( false == dropped[ order[ member ] ] ) { + result = glm::max( result, reach( Fastenings[ order[ member ] ], Fastenings[ order[ member ] ].scale ) ); + } + } + return result; }; + auto kept { std::size_t { 0 } }; + for( std::size_t index = 1; index < rails.size(); ++index ) { + auto const &first { rails[ kept ] }; + auto const &second { rails[ index ] }; + auto const apart { at( Fastenings[ order[ second.first ] ] ) - at( Fastenings[ order[ first.first ] ] ) }; + auto const overlaps = [&]() { + return extent( first ).y + extent( second ).x - apart > fastening_gap; }; + if( false == overlaps() ) { + kept = index; + continue; + } + auto const firstreach { extent( first ) }; + auto const secondreach { extent( second ) }; + auto const drop = [&]( std::pair const &Rail ) { + for( auto member { Rail.first }; member < Rail.second; ++member ) { + dropped[ order[ member ] ] = true; + } }; + // 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 ) { + auto const &fastening { Fastenings[ order[ member ] ] }; + if( fastening.part == part_chair && reach( fastening, fastening.scale )[ Side ] > fastening_gap ) { + dropped[ order[ member ] ] = true; + return true; + } + } + return false; }; + if( false == chair( second ) && apart <= firstreach.y ) { + drop( second ); + continue; + } + if( false == chair( first ) && apart <= secondreach.x ) { + drop( first ); + } + else { + brace( first, 1 ); + if( overlaps() ) { + brace( second, 0 ); + } + } + kept = index; + } + std::vector fitted; + for( std::size_t index = 0; index < Fastenings.size(); ++index ) { + if( false == dropped[ index ] ) { + fitted.push_back( Fastenings[ index ] ); + } + } + Fastenings = std::move( fitted ); +} + +std::vector check_fastenings() { + + std::vector result; + 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; + } + fastening_row row; + row.track = track->name(); + std::optional rails; + for( auto const &beam : place( *track ) ) { + if( beam.plain ) { + continue; + } + if( false == rails.has_value() ) { + rails.emplace( *track ); + } + ++row.beams; + auto const start { plan( beam.from ) }; + auto const axis { glm::normalize( plan( beam.to - beam.from ) ) }; + struct footprint { + double from; + double to; + int part; + glm::dvec3 point; + }; + std::vector footprints; + for( auto const &fastening : rails->on( beam, *model ) ) { + ++row.parts[ fastening.part ]; + auto const at { glm::dot( plan( fastening.point ) - start, axis ) }; + auto const turn { glm::dot( fastening.across, axis ) }; + auto const a { at + turn * fastening.placed( model->spans[ fastening.part ].x, fastening.scale ) }; + auto const b { at + turn * fastening.placed( model->spans[ fastening.part ].y, fastening.scale ) }; + footprints.push_back( { std::min( a, b ), std::max( a, b ), fastening.part, fastening.point } ); + } + std::sort( footprints.begin(), footprints.end(), []( footprint const &Left, footprint const &Right ) { return Left.from < Right.from; } ); + for( std::size_t index = 1; index < footprints.size(); ++index ) { + auto const over { footprints[ index - 1 ].to - footprints[ index ].from }; + if( over <= fastening_overlap ) { + continue; + } + ++row.overlaps; + row.cases.push_back( { { footprints[ index - 1 ].part, footprints[ index ].part }, glm::length( plan( footprints[ index ].point - footprints[ index - 1 ].point ) ), over, footprints[ index ].point } ); + if( over > row.worst ) { + row.worst = over; + row.worstat = footprints[ index ].point; + row.worstparts = std::string { part_names[ footprints[ index - 1 ].part ] } + "/" + part_names[ footprints[ index ].part ]; + } + } + } + if( row.beams > 0 ) { + result.push_back( std::move( row ) ); + } + } return result; } diff --git a/world/SleeperFastenings.h b/world/SleeperFastenings.h index 1d2f876a..c4f36a82 100644 --- a/world/SleeperFastenings.h +++ b/world/SleeperFastenings.h @@ -9,6 +9,8 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include +#include #include #include @@ -25,6 +27,12 @@ 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 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 + // 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 ); } }; // the rails of the tracks around a track, which lie on its timbers @@ -54,6 +62,7 @@ private: std::vector distances; glm::dvec2 low { 0.0 }; glm::dvec2 high { 0.0 }; + double foot { 0.0 }; // from the middle of the rail to the edge of its foot toward the middle of the path // the stretches along the path the rail is a blade, a stock rail with a chair, checked along glm::dvec2 blade { -1.0 }; glm::dvec2 stock { -1.0 }; @@ -65,9 +74,34 @@ private: double at { 0.0 }; // along the timber, from its from end role kind { role::plain }; glm::dvec2 inward { 1.0, 0.0 }; + 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 + static void fit( std::vector &Fastenings, beam const &Beam, beam_model const &Model ); + std::vector m_rails; }; +// the fastenings of a track with them in the model of its timbers, as laid on the timbers laid with it +struct fastening_row { + std::string track; + std::size_t beams { 0 }; + std::array parts {}; + std::size_t overlaps { 0 }; // fastenings lying over one another on a timber, along it + double worst { 0.0 }; + glm::dvec3 worstat { 0.0 }; + std::string worstparts; + struct overlap { + int parts[ 2 ]; + double apart; // of the rails the two lie under + double over; + glm::dvec3 at; + }; + std::vector cases; +}; +// those of all the tracks laying timbers with fastenings +std::vector check_fastenings(); + } // namespace track_sleepers diff --git a/world/SleeperModels.cpp b/world/SleeperModels.cpp index 90e0a5d8..1dc13567 100644 --- a/world/SleeperModels.cpp +++ b/world/SleeperModels.cpp @@ -109,22 +109,29 @@ beam_model const *find_model( std::string const &Name, std::string const &Skinna model.skin = GfxRenderer->Fetch_Material( Skin ); } // a timber put together from its parts, with a part between the ends stretched and repeated to the length - std::array const names { "begin", "main", "end", "plate", "chair", "check" }; model.stretchable = true; for( int part = 0; part < part_count; ++part ) { - model.parts[ part ] = model.model->GetFromName( names[ part ] ); + model.parts[ part ] = model.model->GetFromName( part_names[ part ] ); model.stretchable = model.stretchable && ( part >= timber_parts || model.parts[ part ] != nullptr ); } auto top { std::numeric_limits::lowest() }; model.span = { std::numeric_limits::max(), std::numeric_limits::lowest() }; if( model.stretchable ) { - for( int part = 0; part < timber_parts; ++part ) { + for( int part = 0; part < part_count; ++part ) { + if( model.parts[ part ] == nullptr ) { + continue; + } glm::vec2 span { std::numeric_limits::max(), std::numeric_limits::lowest() }; + auto const timber { part < timber_parts }; vertices_of( model.parts[ part ], [&]( glm::vec3 const &Position ) { span = { std::min( span.x, Position.x ), std::max( span.y, Position.x ) }; - top = std::max( top, Position.y ); } ); + if( timber ) { + top = std::max( top, Position.y ); + } } ); model.spans[ part ] = span; - model.span = { std::min( model.span.x, span.x ), std::max( model.span.y, span.y ) }; + if( timber ) { + model.span = { std::min( model.span.x, span.x ), std::max( model.span.y, span.y ) }; + } } model.stretchable = model.spans[ part_main ].y > model.spans[ part_main ].x; } @@ -152,8 +159,12 @@ beam_model const *find_model( std::string const &Name, std::string const &Skinna if( Seat < 0.f && model.fastened() ) { model.seat = std::max( top, surface_at( model.parts[ part_plate ], 0.f, 0.f ).value_or( top ) ); } - if( model.fastened() && model.parts[ part_chair ] != nullptr ) { - model.slide = std::max( 0.f, surface_at( model.parts[ part_chair ], static_cast( -slide_probe ), 0.f ).value_or( model.seat ) - model.seat ); + if( model.fastened() ) { + for( auto const part : { part_chair, part_slide } ) { + if( model.parts[ part ] != nullptr ) { + model.slide = std::max( model.slide, surface_at( model.parts[ part ], static_cast( -slide_probe ), 0.f ).value_or( model.seat ) - model.seat ); + } + } } if( model.length() < 0.5f ) { ErrorLog( "Bad sleepers3d: model \"" + Name + "\" has no beam along x" ); @@ -167,7 +178,7 @@ beam_model const *find_model( std::string const &Name, std::string const &Skinna ", main " + to_string( model.spans[ part_main ].x, 3 ) + ".." + to_string( model.spans[ part_main ].y, 3 ) : std::string {} ) + ( model.fastened() ? - std::string { ", fastenings plate" } + ( model.parts[ part_chair ] != nullptr ? " chair" : "" ) + ( model.parts[ part_check ] != nullptr ? " check" : "" ) : + std::string { ", fastenings plate" } + ( model.parts[ part_chair ] != nullptr ? " chair" : "" ) + ( model.parts[ part_check ] != nullptr ? " check" : "" ) + ( model.parts[ part_slide ] != nullptr ? " slide" : "" ) : std::string {} ) + ", seat " + to_string( model.seat, 3 ) + ( model.slide > 0.f ? ", slide " + to_string( model.slide, 3 ) : std::string {} ) ); diff --git a/world/SleeperRules.h b/world/SleeperRules.h index 3fb63044..d4573e9d 100644 --- a/world/SleeperRules.h +++ b/world/SleeperRules.h @@ -55,12 +55,22 @@ double constexpr fastening_step { 0.25 }; double constexpr fastening_merge { 0.03 }; // two rails closer than this lie in one fastening of both, as a rail of the frog with the one beside it; closer than the second // one, as the legs of the nose where they're joined, in one plate between them -double constexpr fastening_pair { 0.24 }; +double constexpr fastening_pair { 0.2 }; double constexpr fastening_nose { 0.05 }; +// how far apart the rails lie in the fastening of both, the check part of the model +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 }; +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 }; // how far before the tip of the blade the slide chairs of its stock rail begin double constexpr chair_lead { 0.3 }; // how far from the stock rail toward its blade the slide plate of a chair is measured double constexpr slide_probe { 0.12 }; +// how far two fastenings on a timber may reach over one another before the check counts them +double constexpr fastening_overlap { 0.01 }; // how far from the camera the fastenings are drawn at most double constexpr fastening_range { 120.0 }; diff --git a/world/TrackSleepers.cpp b/world/TrackSleepers.cpp index e23fa7db..94f239d5 100644 --- a/world/TrackSleepers.cpp +++ b/world/TrackSleepers.cpp @@ -141,8 +141,8 @@ void lay( TTrack &Track ) { // the layout of the cluster isn't needed by this track any more, the instances below are what's drawn forget_layout( Track ); - // the rails around, where they lie on the timbers, for the fastenings of the model - std::optional const rails { parts.fastened() ? std::make_optional( Track ) : std::nullopt }; + // the rails around, where they lie on the timbers, for the fastenings of the model; found at the first timber, none for plain sleepers + std::optional rails; auto const partlength = [&]( int const Part ) { return static_cast( parts.spans[ Part ].y - parts.spans[ Part ].x ); }; @@ -242,7 +242,10 @@ void lay( TTrack &Track ) { put( part_end, scale, parts.spans[ part_end ].x, -half ); put( part_begin, scale, parts.spans[ part_begin ].y, half ); } - if( rails && false == ( beam.plain && laid.plains != nullptr ) ) { + if( parts.fastened() && false == ( beam.plain && laid.plains != nullptr ) ) { + if( false == rails.has_value() ) { + rails.emplace( Track ); + } // each square to its rail on the top of the timber, the plate under the foot of the rail for( auto const &fastening : rails->on( beam, parts ) ) { auto across { glm::dvec3 { fastening.across.x, 0.0, fastening.across.y } }; @@ -252,7 +255,9 @@ void lay( TTrack &Track ) { place[ 1 ] = glm::vec4 { glm::vec3 { y }, 0.f }; place[ 2 ] = glm::vec4 { glm::vec3 { glm::cross( across, y ) }, 0.f }; place[ 3 ] = glm::vec4 { glm::vec3 { fastening.point - Track.m_origin }, 1.f }; - laid.instances[ fastening.part ].emplace_back( glm::translate( place, glm::vec3 { 0.f, -parts.seat, 0.f } ) ); + auto const pivot { glm::vec3 { fastening.pivot, 0.f, 0.f } }; + laid.instances[ fastening.part ].emplace_back( + glm::translate( glm::scale( glm::translate( place, glm::vec3 { fastening.shift, -parts.seat, 0.f } + pivot ), glm::vec3 { fastening.scale, 1.f, 1.f } ), -pivot ) ); } } runbeams.emplace_back( middle, half ); diff --git a/world/TrackSleepers.h b/world/TrackSleepers.h index fd994446..683a451b 100644 --- a/world/TrackSleepers.h +++ b/world/TrackSleepers.h @@ -36,11 +36,15 @@ enum part : int { part_main, // the piece between the ends part_end, // the end on the -x side part_plate, // the fastening of a rail, its origin under the middle of the foot of the rail - part_chair, // the slide chair of a stock rail with the blade beside it, its origin under the stock rail, the slide plate on its -x side + part_chair, // the brace of the slide chair of a stock rail with the blade beside it, its origin under the stock rail, on its +x side part_check, // the fastening of a rail with another one close beside it, as a check rail or a rail of the frog, its origin under the // rail, the other one on its +x side + part_slide, // the slide plate of the chair, apart from it on its -x side, from the edge of the foot of the stock rail on, shortened + // toward it where another rail lies close part_count }; +// the names of the submodels of the parts +inline constexpr std::array part_names { "begin", "main", "end", "plate", "chair", "check", "slide" }; // the parts a timber is put together from, the fastenings after them int constexpr timber_parts { part_end + 1 };