diff --git a/application/editormodeselftestsleepers.cpp b/application/editormodeselftestsleepers.cpp index 63972320..3f795bb0 100644 --- a/application/editormodeselftestsleepers.cpp +++ b/application/editormodeselftestsleepers.cpp @@ -172,8 +172,11 @@ 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", 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]; + text += format(" %s%s %.3f %.3f %+.0f", fastening.pad ? "pad:" : "", track_sleepers::part_names[fastening.part], fastening.point.x, fastening.point.z, glm::degrees(std::atan2(fastening.across.y, fastening.across.x))); + if (fastening.lift != 0.f) + text += format(" ^%.3f", fastening.lift); + if (false == fastening.pad) + ++counts[fastening.part]; } WriteLog(text); } diff --git a/world/RailLayer.cpp b/world/RailLayer.cpp index 816e6c51..3f7a855b 100644 --- a/world/RailLayer.cpp +++ b/world/RailLayer.cpp @@ -383,6 +383,7 @@ struct rail_piece { float lift { 0.f }; float wallu { -1.f }; knots::line rises; + knots::line lows; }; struct top_band { @@ -609,6 +610,7 @@ private: double const m_guardoffset; double const m_rise; // of the feet of the wing rails of the low section glm::dvec2 const m_stock; // the stretch the rail is the stock rail of a blade along + double const m_heel; // of the blade the rail goes on from std::size_t m_topfirst; std::size_t m_toplast { 0 }; std::vector m_tints; @@ -632,6 +634,7 @@ TTrack::rail_layer::rail_layer( TTrack &Track, rail_layout const &Layout, rail_d m_guardoffset { -( flangeway + Drawing.size.head ) }, m_rise { track_sleepers::wing_rise( Track ) }, m_stock { track_sleepers::stock_stretch( Track, Path, Side ) }, + m_heel { track_sleepers::blade_heel( Track, Path, Side ) }, m_topfirst { Profile.size() } { // the running surface on top of the head is split along the edge of the wheel treads, the part of it out of their reach goes matte; the rest of the profile and the end faces are laid whole for( std::size_t j = 0; j < Profile.size(); ++j ) { @@ -797,7 +800,8 @@ void TTrack::rail_layer::lay_cuts( gfx::vertex_array const &Shape, std::vector 0 && Cuts[ i - 1 ].carved && Cuts[ i ].carved ) }; auto const pieceoffsets { part_offsets( i > 0 ? &Cuts[ i - 1 ] : nullptr, i < Cuts.size() ? &Cuts[ i ] : nullptr ) }; - auto const piecerises { part_rises( i > 0 ? &Cuts[ i - 1 ] : nullptr, i < Cuts.size() ? &Cuts[ i ] : nullptr, Wheels ? m_rise : 0.0, m_stock ) }; + auto const pieceshares { Wheels && m_rise > 0.0 ? wing_shares( i > 0 ? &Cuts[ i - 1 ] : nullptr, i < Cuts.size() ? &Cuts[ i ] : nullptr, m_stock ) : knots::line {} }; + auto const piecerises { part_rises( pieceshares, Wheels ? m_rise : 0.0, m_heel ) }; // the limits of the head from both ends can overlap along a short piece, as the planed wing rail of one frog and the cut of the next: the tighter of them holds auto const pieceextents { own ? knots::bound( extents, Cuts[ i ].endextents, true ) : extents }; auto const pieceinner { own ? knots::bound( inner, Cuts[ i ].endinner, false ) : inner }; @@ -846,7 +850,8 @@ void TTrack::rail_layer::lay_cuts( gfx::vertex_array const &Shape, std::vectoredge.empty() && m_topfirst < m_toplast ) { treaded( whole, *tread, ends ); } @@ -1279,9 +1284,10 @@ loft_span TTrack::rail_layer::span( rail_piece const &Piece, piece_ends const &E result.walls = Piece.walls; result.wallu = Piece.wallu; result.rises = Piece.rises; + result.lows = Piece.lows; if( false == Piece.rises.empty() ) { result.head = { m_drawing.size.neck, m_drawing.size.crest }; - result.low = { m_rise, low_web, low_inward, low_outward, low_edge, low_root }; + result.low = { low_web, low_inward, low_outward, low_edge, low_root }; } if( std::abs( Piece.from - Ends.from ) < 1e-6 ) { result.fromend = Ends.fromend; diff --git a/world/RailLayout.h b/world/RailLayout.h index 99b13ba8..925d6fa4 100644 --- a/world/RailLayout.h +++ b/world/RailLayout.h @@ -185,21 +185,21 @@ inline knots::line part_offsets( rail_cut const *Before, rail_cut const *After ) knots::sort( result ); return result; } -// how far the foot of a part of a rail between two cuts is raised, Rise along a wing rail of the low section: from wing_low before the throat on, -// down to the plain rail past it along its forged end; the rail whole along Stock, the stock rail of a blade there -inline knots::line part_rises( rail_cut const *Before, rail_cut const *After, double const Rise, glm::dvec2 const &Stock ) { +// how much of the low section of the blades a part of a rail between two cuts is of along it, 0 to 1: a wing rail from wing_low before the throat +// on, down to the plain rail past it along its forged end; the rail whole along Stock, the stock rail of a blade there +inline knots::line wing_shares( rail_cut const *Before, rail_cut const *After, glm::dvec2 const &Stock ) { knots::line result; auto const going { Before != nullptr && Before->startkind == rail_end::point }; auto const coming { After != nullptr && After->endkind == rail_end::point }; - if( Rise <= 0.0 || ( false == going && false == coming ) ) { + if( false == going && false == coming ) { return result; } auto const lowto { going ? Before->startanchor + wing_low : 0.0 }; auto const lowfrom { coming ? After->endanchor - wing_low : 0.0 }; auto const through { going && coming && lowto + 2.0 * wing_forged >= lowfrom }; auto const stock { Stock.y > Stock.x }; - // the share of the rise: whole along the low section, none along the stock rail, between along the forged ends + // whole along the low section, none along the stock rail, between along the forged ends auto const share = [&]( double const At ) { auto const low { through ? 1.0 : std::max( going ? std::clamp( ( lowto + wing_forged - At ) / wing_forged, 0.0, 1.0 ) : 0.0, @@ -210,7 +210,33 @@ inline knots::line part_rises( rail_cut const *Before, rail_cut const *After, do std::sort( places.begin(), places.end() ); for( auto const at : places ) { if( result.empty() || at > result.back().x + 1e-6 ) { - result.emplace_back( at, Rise * share( at ) ); + result.emplace_back( at, share( at ) ); + } + } + return result; +} +// how far the foot of the part is raised: by Rise along the low section of a wing rail of Shares, and from the heel of a blade at Heel, the +// rail going on from it, down to the plain rail along the forged end of the blade; not along the blade itself, raised on its own +inline knots::line part_rises( knots::line const &Shares, double const Rise, double const Heel ) { + + knots::line result; + if( Rise <= 0.0 || ( Shares.empty() && Heel <= 0.0 ) ) { + return result; + } + std::vector places; + for( auto const &knot : Shares ) { + places.push_back( knot.x ); + } + if( Heel > 0.0 ) { + places.push_back( Heel - 1e-3 ); + places.push_back( Heel ); + places.push_back( Heel + wing_forged ); + } + std::sort( places.begin(), places.end() ); + for( auto const at : places ) { + if( result.empty() || at > result.back().x + 1e-6 ) { + auto const heel { Heel > 0.0 && at >= Heel ? std::clamp( ( Heel + wing_forged - at ) / wing_forged, 0.0, 1.0 ) : 0.0 }; + result.emplace_back( at, Rise * std::max( knots::sample( Shares, at, 0.0 ), heel ) ); } } return result; diff --git a/world/Segment.cpp b/world/Segment.cpp index c87b5853..10f8b361 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -717,7 +717,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig for( auto distance { std::ceil( Span.from / 0.5 ) * 0.5 }; distance < std::min( Span.to, m_dense ); distance += 0.5 ) { stations.push_back( distance ); } - for( auto const *line : { &Span.offsets, &Span.extents, &Span.inner, &Span.drops, &Span.rises } ) { + for( auto const *line : { &Span.offsets, &Span.extents, &Span.inner, &Span.drops, &Span.rises, &Span.lows } ) { for( auto const &knot : *line ) { if( knot.x > Span.from && knot.x < Span.to ) { stations.push_back( knot.x ); @@ -771,7 +771,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } drawn; auto const across = [&]( gfx::basic_vertex const &Point ) { return static_cast( Span.side * Point.position.x - Span.halfwidth ); }; - auto low { Span.low.full > 0.0 && Span.head.x > bottom }; + auto low { false == Span.lows.empty() && Span.head.x > bottom }; if( low ) { glm::dvec2 foot { 1e6, -1e6 }; for( auto const &point : ShapePoints ) { @@ -806,11 +806,11 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } low = ( drawn.web < std::min( drawn.inward, drawn.outward ) && drawn.web < drawn.head && drawn.edge > 0.0 && drawn.edge < drawn.root && drawn.root < Span.head.x - bottom && drawn.underside < 1e6 ); } - // the profile made over into the low section with its foot raised by Rise, by as much of it as the rise is of the full one: below the - // head the web thickened, the foot reshaped, the web shortened + // the profile with its foot raised by Rise made over into the low section by Share of it: below the head the web thickened, the foot + // reshaped, the web shortened; with none of it, only raised std::vector lowpoints; - auto const lowered = [&]( double const Rise ) -> gfx::vertex_array const & { - auto const share { std::clamp( Rise / Span.low.full, 0.0, 1.0 ) }; + auto const lowered = [&]( double const Rise, double const Share ) -> gfx::vertex_array const & { + auto const share { std::clamp( Share, 0.0, 1.0 ) }; auto const web { drawn.web * glm::mix( 1.0, Span.low.web, share ) }; auto const inward { drawn.inward * glm::mix( 1.0, Span.low.inward, share ) }; auto const outward { drawn.outward * glm::mix( 1.0, Span.low.outward, share ) }; @@ -840,7 +840,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig height <= drawn.edge ? height * edge / drawn.edge : height <= drawn.root ? edge + ( height - drawn.edge ) * ( root - edge ) / ( drawn.root - drawn.edge ) : root + ( height - drawn.root ) * ( top - root ) / ( below - drawn.root ) }; - point.position.y = static_cast( bottom + below - top + made ); + point.position.y = static_cast( glm::mix( static_cast( raised( point.position.y, Rise ) ), bottom + below - top + made, share ) ); } point.position.x = static_cast( Span.side * ( Span.halfwidth + drawn.middle + std::copysign( moved, off ) ) ); } @@ -879,7 +879,8 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig auto const worn { knots::sample( Span.ramp, Distance, -1e6 ) }; auto const drop { knots::sample( Span.drops, Distance, 0.0 ) }; auto const sampled { Span.rises.empty() ? Span.rise : knots::sample( Span.rises, Distance, 0.0 ) }; - auto const &points { low && sampled > 0.0 ? lowered( sampled ) : ShapePoints }; + auto const share { low ? knots::sample( Span.lows, Distance, 0.0 ) : 0.0 }; + auto const &points { low && ( sampled > 0.0 || share > 0.0 ) ? lowered( sampled, share ) : ShapePoints }; auto const rise { low ? 0.0 : sampled }; static loft_end const unshaped; auto const &shaped { Distance <= Span.from ? Span.fromend : Distance >= Span.to ? Span.toend : unshaped }; diff --git a/world/Segment.h b/world/Segment.h index 2aa17fb0..24665ddd 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -31,7 +31,6 @@ struct loft_end { // a rail profile made over below its head into a lower section, as the wing rails of a frog of the section of the blades: its web, its // foot inwards and outwards as many times as thick and wide, the top of the edges of its foot and the root of its web as many times as high struct loft_low { - double full { 0.0 }; // the rise of the foot the profile is the whole low section at; none where it's 0 double web { 1.0 }; double inward { 1.0 }; double outward { 1.0 }; @@ -60,7 +59,8 @@ struct loft_span { glm::dvec2 head { 0.0 }; double rise { 0.0 }; // how far the foot of the profile is raised, the web below the head (head.x) shortened by as much knots::line rises; // along the span, in place of rise where given - loft_low low; // what the profile is made over into as its foot is raised + knots::line lows; // along the span, how much of the low section the profile is made over into below its head, 0 to 1 + loft_low low; // what the profile is made over into along lows }; struct segment_data { diff --git a/world/SleeperFastenings.cpp b/world/SleeperFastenings.cpp index c80b6d9e..b11dea4a 100644 --- a/world/SleeperFastenings.cpp +++ b/world/SleeperFastenings.cpp @@ -53,14 +53,11 @@ fastened_rails::fastened_rails( TTrack &Track ) { auto const cut { track_rails::cut_rails( *track ) }; auto const switched { track->eType == tt_Switch && track->SwitchExtension != nullptr }; auto const rise { wing_rise( *track ) }; - // the side of the blade of the path - auto const bladeside = [&]( int const Path ) { - return ( track->SwitchExtension->RightSwitch == ( Path == 0 ) ? 1 : -1 ); }; // what the rail is along the path: a blade, the stock rail of a blade, checked auto const add = [&]( rail &&Line, int const Path, int const Side ) { if( switched ) { - if( Side == bladeside( Path ) ) { - Line.blade = { 0.0, track_rails::blade_length( *track, Path ) }; + if( auto const heel { blade_heel( *track, Path, Side ) }; heel > 0.0 ) { + Line.blade = { 0.0, heel }; } Line.stock = stock_stretch( *track, Path, Side ); } @@ -95,7 +92,7 @@ fastened_rails::fastened_rails( TTrack &Track ) { line.track = track; line.path = path; line.side = side; - line.rises = track_rails::part_rises( before, after, rise, stock_stretch( *track, path, side ) ); + line.rises = track_rails::part_rises( rise > 0.0 ? track_rails::wing_shares( before, after, stock_stretch( *track, path, side ) ) : knots::line {}, rise, blade_heel( *track, path, side ) ); line.foot = size.web - size.low; line.low = glm::dvec2 { std::numeric_limits::max() }; line.high = glm::dvec2 { std::numeric_limits::lowest() }; @@ -673,33 +670,34 @@ void fastened_rails::lift( std::vector &Fastenings, std::vector auto const reach { footprint( Fastening ) }; return Fastening.part != part_chair && Rail.at >= reach.x && Rail.at <= reach.y; }; std::vector pads; - std::vector lifted( Fastenings.size(), false ); + for( auto &fastening : Fastenings ) { + if( fastening.pad || fastening.part == part_chair ) { + continue; + } + // moved up on a pad as wide as it as far as the least raised of the raised rails it holds, a rail not raised sunk in it + auto lift { 0.0 }; + for( auto const &rail : Rails ) { + if( rail.rise >= pad_least && holds( fastening, rail ) ) { + lift = ( lift > 0.0 ? std::min( lift, rail.rise ) : rail.rise ); + } + } + if( lift <= 0.0 ) { + continue; + } + auto const &span { Model.spans[ fastening.part ] }; + auto const a { fastening.placed( span.x, fastening.scale ) }; + auto const b { fastening.placed( span.y, fastening.scale ) }; + pads.push_back( pad( fastening, std::min( a, b ), std::max( a, b ), Model.bed, Model.heights[ fastening.part ].x + static_cast( lift ) ) ); + fastening.lift = static_cast( lift ); + } + // a rail raised more than its fastening on a pad of its own on it, as wide as its foot, short of the rails beside for( std::size_t index = 0; index < Rails.size(); ++index ) { auto const &rail { Rails[ index ] }; - if( rail.rise < pad_least ) { + auto const under { std::find_if( Fastenings.begin(), Fastenings.end(), [&]( fastening const &Fastening ) { + return false == Fastening.pad && holds( Fastening, rail ); } ) }; + if( under == Fastenings.end() || rail.rise - under->lift < pad_least ) { continue; } - auto const under { std::find_if( Fastenings.begin(), Fastenings.end(), [&]( fastening const &Fastening ) { return holds( Fastening, rail ); } ) }; - if( under == Fastenings.end() ) { - continue; - } - auto const which { static_cast( under - Fastenings.begin() ) }; - if( lifted[ which ] ) { - continue; - } - auto const alike { std::all_of( Rails.begin(), Rails.end(), [&]( hit const &Other ) { - return false == holds( *under, Other ) || std::abs( Other.rise - rail.rise ) < pad_least; } ) }; - if( alike ) { - // the fastening holding only the rails raised as much, moved up as far on a pad as wide as it - auto const &span { Model.spans[ under->part ] }; - auto const a { under->placed( span.x, under->scale ) }; - auto const b { under->placed( span.y, under->scale ) }; - pads.push_back( pad( *under, std::min( a, b ), std::max( a, b ), Model.bed, Model.heights[ under->part ].x + static_cast( rail.rise ) ) ); - under->lift = static_cast( rail.rise ); - lifted[ which ] = true; - continue; - } - // the rail on a pad of its own on the fastening, as wide as its foot, short of the rails beside auto low { rail.foot }; auto high { rail.foot }; if( index > 0 ) { @@ -712,7 +710,7 @@ void fastened_rails::lift( std::vector &Fastenings, std::vector continue; } fastening const at { padpart, Beam.from + ( Beam.to - Beam.from ) * ( rail.at / glm::length( plan( Beam.to - Beam.from ) ) ), axis }; - pads.push_back( pad( at, -low, high, Model.seat, Model.seat + static_cast( rail.rise ) ) ); + pads.push_back( pad( at, -low, high, Model.seat + under->lift, Model.seat + static_cast( rail.rise ) ) ); } Fastenings.insert( Fastenings.end(), pads.begin(), pads.end() ); } diff --git a/world/SleeperFastenings.h b/world/SleeperFastenings.h index 59ebf641..f6d22003 100644 --- a/world/SleeperFastenings.h +++ b/world/SleeperFastenings.h @@ -64,7 +64,7 @@ private: TTrack *track { nullptr }; int path { 0 }; int side { 1 }; - knots::line rises; // of its foot, along the path, a wing rail of the low section raised as the blades + knots::line rises; // of its foot, along the path: a wing rail of the low section raised as the blades, the rail going on from the heel of a blade std::vector points; std::vector heights; std::vector inward; // unit vectors in plan from the rail toward the middle of its path @@ -92,8 +92,8 @@ private: // 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 ); - // the fastenings under the raised rails: one holding only rails raised as much moved up as far on a pad, a rail raised - // over a fastening with others on a pad of its own on it + // the fastenings under the raised rails: one holding them moved up on a pad as far as the least raised of them, a rail not raised + // sunk in it, a rail raised more on a pad of its own on it static void lift( std::vector &Fastenings, std::vector const &Rails, beam const &Beam, beam_model const &Model ); std::vector m_rails; diff --git a/world/TrackSleepers.cpp b/world/TrackSleepers.cpp index b4563475..4f3502d2 100644 --- a/world/TrackSleepers.cpp +++ b/world/TrackSleepers.cpp @@ -85,6 +85,14 @@ glm::dvec2 stock_stretch( TTrack const &Track, int const Path, int const Side ) return { std::max( 0.0, ( shape.valid ? shape.tip : 0.0 ) - chair_lead ), track_rails::blade_length( Track, 1 - Path ) }; } +double blade_heel( TTrack const &Track, int const Path, int const Side ) { + + if( Track.eType != tt_Switch || Track.SwitchExtension == nullptr || Side != ( Track.SwitchExtension->RightSwitch == ( Path == 0 ) ? 1 : -1 ) ) { + return 0.0; + } + return track_rails::blade_length( Track, Path ); +} + std::vector> shared_zones( TTrack const &Track, int const Path ) { std::vector> result; diff --git a/world/TrackSleepers.h b/world/TrackSleepers.h index 7efa1681..1e3c831b 100644 --- a/world/TrackSleepers.h +++ b/world/TrackSleepers.h @@ -130,6 +130,8 @@ double wing_rise( TTrack const &Track ); // the stretch along the path its rail of Side is the stock rail of the blade of the other path along, with the slide chairs from // a little before the tip of the blade to its heel; from past to where there's none glm::dvec2 stock_stretch( TTrack const &Track, int const Path, int const Side ); +// how far along the path its rail of Side is the blade of the path, to its heel, the rail going on from there; 0 where it's none +double blade_heel( TTrack const &Track, int const Path, int const Side ); // where along the path of the track its rails lie on timbers shared with other tracks: where another track runs so near that // the plain sleepers of the two would touch, of the lengths of the models they'd be made of. this goes by the shape of the // tracks alone, so it holds whether the beams are drawn or not. the stretches in the order along the path, none where there