17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 14:39:07 +01:00

feat(track): 3D sleepers and switch timbers, blade heel and throw from templates, one rail carving pass

- sleepers3d / sleepers3dplain / sleepers3dbed in eu07.ini: timbers put together from the begin, main and end parts of a model
  and stretched to their length, plain sleepers of their own model; tracks whose plain sleepers would touch are laid as one
  cluster, lines of timbers led by priority and turned to the bisectors of frogs and crossings at a limited rate, whole timbers
  under crossings, split ones between tracks with room to part them, gaps filled per rail and overlaps shortened or dropped;
  clusters worked out in a background thread while driving, on demand in the editor
- the ballast can be left out under the beams (sleepers3dbed no)
- blades get their heel and throw from the switch template or blades <heel> <throw> in the scenery; blades of double switches
  run on into the track linking them to the next switch
- rail cutting at frogs, wing rails, movable noses and switch crossings done by one carving pass (RailCarve)
- tint of the rail treads applied to the colour only, without the matte band at the frog
- a switch trailed through with a movable nose set the other way derails the vehicle
- self-tests: sleepercheck, sharedzones, beamsnear, throw, sleepersbg
This commit is contained in:
maj00r
2026-10-17 23:28:36 +02:00
parent 21eca4d9a6
commit d25f20a56c
49 changed files with 9802 additions and 848 deletions

View File

@@ -58,7 +58,28 @@ std::vector<std::array<std::size_t, 3>> ear_clip( std::vector<glm::dvec2> const
for( std::size_t j = 0; j < Polygon.size(); ++j ) {
left[ j ] = j;
}
// the parts of the outline of no width, as the web and the head of a rail planed away to nothing at the tip of a nose with its foot whole,
// go out first: the ears cut across them would cover what lies beside them
auto const thin = [&]( std::size_t const J ) {
auto const &a { Polygon[ left[ ( J + left.size() - 1 ) % left.size() ] ] };
auto const &b { Polygon[ left[ J ] ] };
auto const &c { Polygon[ left[ ( J + 1 ) % left.size() ] ] };
auto const longest { std::max( { glm::length( b - a ), glm::length( c - b ), glm::length( a - c ) } ) };
return ( longest <= 0.0 || std::abs( cross( a, b, c ) ) / longest < 1e-4 ); };
for( auto pruned { true }; pruned && left.size() > 3; ) {
pruned = false;
for( std::size_t j = 0; j < left.size() && left.size() > 3; ++j ) {
if( thin( j ) ) {
left.erase( left.begin() + j );
pruned = true;
break;
}
}
}
std::vector<std::array<std::size_t, 3>> triangles;
if( left.size() < 3 || ( left.size() == 3 && thin( 0 ) ) ) {
return triangles;
}
while( left.size() > 3 ) {
auto ear { left.size() };
for( std::size_t j = 0; j < left.size() && ear == left.size(); ++j ) {
@@ -81,6 +102,23 @@ std::vector<std::array<std::size_t, 3>> ear_clip( std::vector<glm::dvec2> const
break;
}
}
// an outline narrower than a millimetre in places lets an ear past the test above across a notch of it, with no corner inside;
// the middle of the ear tells: out of the outline, the ear isn't one; the outline of a planed rail may go over itself, so it's what it winds around
if( false == inside ) {
auto const middle { ( a + b + c ) / 3.0 };
auto winding { 0 };
for( std::size_t k = 0; k < left.size(); ++k ) {
auto const &p { Polygon[ left[ k ] ] };
auto const &q { Polygon[ left[ ( k + 1 ) % left.size() ] ] };
if( p.y <= middle.y && q.y > middle.y && cross( p, q, middle ) > 0.0 ) {
++winding;
}
else if( q.y <= middle.y && p.y > middle.y && cross( p, q, middle ) < 0.0 ) {
--winding;
}
}
inside = ( winding == 0 );
}
}
if( false == inside ) {
ear = j;
@@ -153,13 +191,59 @@ gfx::userdata_array worn_userdata( std::size_t const Count ) {
return gfx::userdata_array( Count, gfx::vertex_userdata{ glm::vec4{ 1.f, 0.f, 0.f, 0.f } } );
}
namespace {
chunk_pool *reused { nullptr };
} // namespace
reused_chunks::reused_chunks( chunk_pool *Pool ) :
m_previous { reused } {
if( Pool != nullptr ) {
for( auto &chunk : *Pool ) {
chunk.used = false;
}
}
reused = Pool;
}
reused_chunks::~reused_chunks() {
reused = m_previous;
}
gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type ) {
if( reused == nullptr ) {
return GfxRenderer->Insert( Vertices, Userdata, Bank, Type );
}
for( auto &chunk : *reused ) {
if( chunk.used || chunk.type != Type || chunk.handle.bank != Bank.bank ) {
continue;
}
// the data are swapped into the chunk when of the same size, the caller keeps its own
auto vertices { Vertices };
auto userdata { Userdata };
if( GfxRenderer->Replace( vertices, userdata, chunk.handle, Type ) ) {
chunk.used = true;
return chunk.handle;
}
}
auto const handle { GfxRenderer->Insert( Vertices, Userdata, Bank, Type ) };
if( handle != null_handle ) {
reused->push_back( { Type, handle, true } );
}
return handle;
}
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 ) {
gfx::vertex_array triangles;
cap_loft( Strip, Ring, Start, End, triangles );
if( false == triangles.empty() ) {
auto userdata { worn_userdata( triangles.size() ) };
Geometry.emplace_back( GfxRenderer->Insert( triangles, userdata, Bank, GL_TRIANGLES ) );
Geometry.emplace_back( insert_chunk( triangles, userdata, Bank, GL_TRIANGLES ) );
}
}
@@ -335,22 +419,17 @@ std::vector<double> cross_splits( rail_piece const &Whole, glm::dvec2 const &Rea
return result;
}
knots::line flared( double const From, double const To, double const Flare, double const Base, double const Fromdepth, double const Todepth ) {
// the face of a check or a guard rail toward the running rail, planed off at the ends by Fromdepth and Todepth down to nothing over Length; lines for the upper envelope of the reach
void planed( knots::line &Lines, double const From, double const To, double const Length, double const Face, double const Fromdepth, double const Todepth ) {
knots::line result;
if( Fromdepth > 0.0 ) {
result.insert( result.end(), { { From, Base - Fromdepth }, { From + Flare, Base } } );
}
else {
result.push_back( { From, Base } );
auto const slope { Fromdepth / Length };
Lines.emplace_back( slope, Face - Fromdepth - slope * From );
}
if( Todepth > 0.0 ) {
result.insert( result.end(), { { To - Flare, Base }, { To, Base - Todepth } } );
auto const slope { Todepth / Length };
Lines.emplace_back( -slope, Face - Todepth + slope * To );
}
else {
result.push_back( { To, Base } );
}
return result;
}
knots::line envelope( knots::line const &Lines, bool const Upper, double const A, double const B ) {
@@ -476,17 +555,18 @@ private:
void lay_guards();
void lay_checks();
void lay_fills();
void lay_treads( rail_piece const &Whole, std::vector<rail_tread> const &Treads );
void lay_treads( rail_piece const &Whole, std::vector<rail_tread> const &Treads, piece_ends const &Ends );
void lay_cuts( gfx::vertex_array const &Shape, std::vector<rail_cut> Cuts, bool const Wheels, std::vector<rail_tread> const &Treads, float const Wallu = -1.f );
void lay_grooved( gfx::vertex_array const &Outline );
grooved_profile split_grooved( gfx::vertex_array const &Outline ) const;
std::vector<rail_cut> top_cuts( gfx::vertex_array const &Top ) const;
void running( rail_piece const &Whole, knots::line const &Rolled, piece_ends const &Ends );
void running( rail_piece const &Whole, knots::line const &Rolled, knots::line const &Worn, material_handle const Wornby, piece_ends const &Ends );
void plain( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends );
void treaded( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends );
gfx::vertex_array lay_sides( rail_piece const &Whole, piece_ends const &Ends );
void lay_top( gfx::vertex_array const &Top, rail_piece const &Whole, glm::dvec2 const &Reach, std::vector<double> const &Splits, std::vector<top_band> const &Bands, std::function<std::vector<double>( double )> const &Edges, piece_ends const &Ends );
void piece( gfx::vertex_array const &Shape, rail_piece const &Piece, piece_ends const &Ends = {} );
// the faces between the points Leftfrom and Leftto of the shape are left out, laid apart
void piece( gfx::vertex_array const &Shape, rail_piece const &Piece, piece_ends const &Ends = {}, std::size_t const Leftfrom = 0, std::size_t const Leftto = 0 );
loft_span span( rail_piece const &Piece, piece_ends const &Ends ) const;
double texture_length( material_handle const Material ) const;
geometryhandle_sequence &sink( material_handle const Material, bool const Matte );
@@ -575,8 +655,10 @@ void TTrack::rail_layer::lay_guards() {
auto const inside { Miter.x + Miter.y * ( size.low + m_guardoffset ) };
auto const outside { Miter.x + Miter.y * ( top + m_guardoffset ) };
return Latest ? std::max( inside, outside ) : std::min( inside, outside ); };
// a flared end is cut slanted from the face of the guard rail toward the running rail: its back reaches out to the end, its face stops short of it
auto const back { -( reaches( m_profile ).x + m_guardoffset ) };
for( auto const &guard : ours( m_layout.guards ) ) {
auto const offsets { flared( guard.from, guard.to, std::min( guard_flare, 0.25 * ( guard.to - guard.from ) ), m_guardoffset, guard.flarefrom ? 0.02 : 0.0, guard.flareto ? 0.02 : 0.0 ) };
knots::line const offsets { { guard.from, m_guardoffset }, { guard.to, m_guardoffset } };
auto cuts { guard.plan.cuts };
sort_cuts( cuts );
auto start { guard.from };
@@ -584,17 +666,23 @@ void TTrack::rail_layer::lay_guards() {
for( std::size_t i = 0; i <= cuts.size(); ++i ) {
auto const end { i < cuts.size() ? cuts[ i ].end : guard.to };
auto const endmiter { i < cuts.size() ? cuts[ i ].endmiter : guard.tomiter };
auto const a { std::max( { mitered( startmiter, start, false ), m_from, 0.0 } ) };
auto const b { std::min( { mitered( endmiter, end, true ), m_length, mitered( guard.tomiter, guard.to, true ) } ) };
auto const bevelfrom { start == guard.from && startmiter.y == 0.0 && guard.flarefrom && guard.from >= std::max( m_from, 0.0 ) };
auto const bevelto { i == cuts.size() && endmiter.y == 0.0 && guard.flareto && guard.to <= m_length };
auto const a { bevelfrom ? guard.from + guard_bevel * back : std::max( { mitered( startmiter, start, false ), m_from, 0.0 } ) };
auto const b { bevelto ? guard.to - guard_bevel * back : std::min( { mitered( endmiter, end, true ), m_length, mitered( guard.tomiter, guard.to, true ) } ) };
if( b - a > 0.01 ) {
knots::line uppers { { 0.0, top } };
knots::line lowers;
planed( uppers, guard.from, guard.to, std::min( guard_flare, 0.25 * ( guard.to - guard.from ) ), size.head, guard.flarefrom ? guard_planing : 0.0, guard.flareto ? guard_planing : 0.0 );
for( auto const &[ miter, ending ] : { std::pair{ startmiter, false }, std::pair{ endmiter, true } } ) {
if( miter.y != 0.0 ) {
( ending == ( miter.y < 0.0 ) ? uppers : lowers ).emplace_back( 1.0 / miter.y, -miter.x / miter.y - m_guardoffset );
}
}
lay_treads( { .from = a, .to = b, .offsets = offsets, .extents = envelope( uppers, true, a, b ), .inner = envelope( lowers, false, a, b ), .capfrom = true, .capto = true, .rolled = false }, guard.treads );
lay_treads(
{ .from = a, .to = b, .offsets = offsets, .extents = envelope( uppers, true, a, b ), .inner = envelope( lowers, false, a, b ), .capfrom = true, .capto = true, .rolled = false },
guard.treads,
{ a, b, { bevelfrom ? guard_bevel : 0.0 }, { bevelto ? -guard_bevel : 0.0 } } );
}
if( i < cuts.size() && cuts[ i ].start > start ) {
start = cuts[ i ].start;
@@ -613,14 +701,17 @@ void TTrack::rail_layer::lay_checks() {
if( replaced_by_wing( check, ours( m_layout.wingends ) ) || b - a <= 0.01 ) {
continue;
}
// the flared ends are bent away from the running rail, the whole of the check rail
auto const flare { check_flare( check ) };
knots::line const offsets { { check.from, -check.flarefrom }, { check.from + flare, 0.0 }, { check.to - flare, 0.0 }, { check.to, -check.flareto } };
piece(
shape,
{ .from = a, .to = b, .offsets = flared( check.from, check.to, check_flare( check ), 0.0, check.flarefrom, check.flareto ), .capfrom = a > 0.0 || check.from >= 0.0, .capto = check.to <= m_length, .rolled = false },
{ .from = a, .to = b, .offsets = offsets, .capfrom = a > 0.0 || check.from >= 0.0, .capto = check.to <= m_length, .rolled = false },
{ a, b, { a == check.from ? check.frombend : 0.0 }, { b == check.to ? check.tobend : 0.0 } } );
}
}
void TTrack::rail_layer::lay_treads( rail_piece const &Whole, std::vector<rail_tread> const &Treads ) {
void TTrack::rail_layer::lay_treads( rail_piece const &Whole, std::vector<rail_tread> const &Treads, piece_ends const &Ends ) {
std::vector<double> splits { Whole.from, Whole.to };
for( auto const &tread : Treads ) {
@@ -631,7 +722,7 @@ void TTrack::rail_layer::lay_treads( rail_piece const &Whole, std::vector<rail_t
}
}
std::sort( splits.begin(), splits.end() );
piece_ends const ends { Whole.from, Whole.to };
auto const &ends { Ends };
for( std::size_t k = 1; k < splits.size(); ++k ) {
auto part { Whole };
part.from = splits[ k - 1 ];
@@ -671,24 +762,31 @@ void TTrack::rail_layer::lay_cuts( gfx::vertex_array const &Shape, std::vector<r
auto start { -1e9 };
auto startcap { true };
auto startbend { 0.0 };
knots::line rolledfrom;
knots::line rolledfrom, wornfrom;
material_handle wornbyfrom { null_handle };
auto const brighter = [&]( material_handle const A, material_handle const B ) {
if( A == null_handle || B == null_handle ) {
return ( A == null_handle ? B : A );
}
return ( tread_brightness( B, m_track.tread_band() ) > tread_brightness( A, m_track.tread_band() ) ? B : A ); };
knots::line offsets, extents, inner;
for( std::size_t i = 0; i <= Cuts.size(); ++i ) {
auto const end { i < Cuts.size() ? Cuts[ i ].end : 1e9 };
// a piece carved between two cuts has the same lines at both
auto const own { i < Cuts.size() && false == ( i > 0 && Cuts[ i - 1 ].carved && Cuts[ i ].carved ) };
auto pieceoffsets { offsets };
auto pieceextents { extents };
auto pieceinner { inner };
if( i < Cuts.size() ) {
if( own ) {
pieceoffsets.insert( pieceoffsets.end(), Cuts[ i ].endoffsets.begin(), Cuts[ i ].endoffsets.end() );
pieceextents.insert( pieceextents.end(), Cuts[ i ].endextents.begin(), Cuts[ i ].endextents.end() );
pieceinner.insert( pieceinner.end(), Cuts[ i ].endinner.begin(), Cuts[ i ].endinner.end() );
}
knots::sort( pieceoffsets );
knots::sort( pieceextents );
knots::sort( pieceinner );
// 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 };
auto const a { std::max( start, m_from ) };
auto const b { std::min( end, m_length ) };
auto const rolled { i < Cuts.size() ? knots::bound( rolledfrom, Cuts[ i ].endrolled, true ) : rolledfrom };
auto const rolled { own ? knots::bound( rolledfrom, Cuts[ i ].endrolled, true ) : rolledfrom };
auto const worn { own ? knots::bound( wornfrom, Cuts[ i ].endworn, true ) : wornfrom };
auto const wornby { own ? brighter( wornbyfrom, Cuts[ i ].endwornby ) : wornbyfrom };
auto const capstart { start > m_from ? startcap : m_openstart };
auto const capend { end < m_length ? Cuts[ i ].endcap : m_openend };
piece_ends const ends {
@@ -736,12 +834,14 @@ void TTrack::rail_layer::lay_cuts( gfx::vertex_array const &Shape, std::vector<r
piece( Shape, whole, ends );
}
else {
running( whole, rolled, ends );
running( whole, rolled, worn, wornby, ends );
}
}
}
if( i < Cuts.size() ) {
rolledfrom = Cuts[ i ].startrolled;
wornfrom = Cuts[ i ].startworn;
wornbyfrom = Cuts[ i ].startwornby;
start = Cuts[ i ].start;
startcap = Cuts[ i ].startcap;
startbend = Cuts[ i ].startbend;
@@ -928,23 +1028,67 @@ std::vector<rail_cut> TTrack::rail_layer::top_cuts( gfx::vertex_array const &Top
return cuts;
}
void TTrack::rail_layer::running( rail_piece const &Whole, knots::line const &Rolled, piece_ends const &Ends ) {
void TTrack::rail_layer::running( rail_piece const &Whole, knots::line const &Rolled, knots::line const &Worn, material_handle const Wornby, piece_ends const &Ends ) {
auto const top { lay_sides( Whole, Ends ) };
auto const reach { reaches( top ) };
auto const inner { knots::shifted( Rolled, -blend ) };
auto const splits { cross_splits( Whole, reach, { &Rolled, &inner }, 0.0 ) };
auto const marked { treads_marked() };
auto const own { Whole.material == null_handle ? m_track.rail_material( m_path ) : Whole.material };
// the wheels of the rail beside polish the head as they do their own; where the wheels of both roll on it, the brighter polish shows
auto const theirs { ( Wornby == null_handle || Worn.empty() ) ? own : Wornby };
auto const theirsbrighter { theirs != own && tread_brightness( theirs, m_track.tread_band() ) > tread_brightness( own, m_track.tread_band() ) };
auto const theirmaterial { theirs == own ? Whole.material : theirs };
// the wheels wander across, rolling a little further than their reach in the middle of the gauge
auto const ownreach { knots::shifted( Rolled, wheel_play ) };
auto const theirreach { Worn.empty() ? knots::line {} : knots::shifted( Worn, -wheel_play ) };
// the polish fades across the edge of the reach of the wheels, as it does at the treads
auto reached { knots::shifted( ownreach, 0.5 * blend ) };
// where the head is planed flush against a rail beside it, the wheels of that rail roll on it again from the outside; where the reach of both
// overlaps, the head is rolled across, each fade cut off halfway between them, or the duller polish where the brighter one reaches. the wheels
// wander across the gap between the reaches narrower than a fade, so both fades close in on it, down to none where the reaches touch
knots::line outside, meet;
if( false == theirreach.empty() ) {
auto const widest { knots::shifted( ownreach, blend ) };
auto all { knots::stations( ownreach, theirreach ) };
for( auto const *edge : { &ownreach, &widest } ) {
auto const crossed { knots::meetings( theirreach, *edge ) };
all.insert( all.end(), crossed.begin(), crossed.end() );
}
std::sort( all.begin(), all.end() );
reached.clear();
for( auto const position : all ) {
if( false == outside.empty() && position - outside.back().x < 1e-9 ) {
continue;
}
auto const rolled { knots::sample( ownreach, position, 0.0 ) };
auto const worn { knots::sample( theirreach, position, 0.0 ) };
auto const half { 0.5 * std::clamp( worn - rolled, 0.0, blend ) };
reached.emplace_back( position, rolled - half + blend );
outside.emplace_back( position, worn + half - blend );
meet.emplace_back( position, ( worn >= rolled || theirs == own ) ? 0.5 * ( rolled + worn ) : theirsbrighter ? worn : rolled );
}
}
auto const inner { outside.empty() ? knots::shifted( reached, -blend ) : knots::bound( knots::shifted( reached, -blend ), meet, true ) };
auto const rolled { outside.empty() ? reached : knots::bound( reached, meet, true ) };
auto const worn { outside.empty() ? knots::line {} : knots::bound( outside, meet, false ) };
auto const outer { outside.empty() ? knots::line {} : knots::bound( knots::shifted( outside, blend ), meet, false ) };
auto const splits { cross_splits( Whole, reach, { &rolled, &inner, &worn, &outer }, 0.0 ) };
auto const marked { treads_marked() };
std::vector<top_band> bands {
{ Whole.material, knots::bound( Whole.extents, inner, true ), Whole.inner },
{ marked ? paint( own, { 1.f, 1.f, 0.f } ) : Whole.material, knots::bound( Whole.extents, rolled, true ), knots::bound( inner, Whole.inner, false ), false, marked ? knots::line {} : reached },
{ marked ? paint( own, { 1.f, 0.f, 1.f } ) : Whole.material, worn.empty() ? Whole.extents : knots::bound( Whole.extents, worn, true ), knots::bound( rolled, Whole.inner, false ), false } };
if( false == worn.empty() ) {
bands.push_back( { marked ? paint( own, { 0.f, 1.f, 1.f } ) : theirmaterial, knots::bound( Whole.extents, outer, true ), knots::bound( worn, Whole.inner, false ), false, marked ? knots::line {} : outside, -blend } );
bands.push_back( { theirmaterial, Whole.extents, knots::bound( outer, Whole.inner, false ) } );
}
lay_top(
top, Whole, reach, splits,
{
{ Whole.material, knots::bound( Whole.extents, inner, true ), Whole.inner },
{ marked ? paint( own, { 1.f, 1.f, 0.f } ) : Whole.material, knots::bound( Whole.extents, Rolled, true ), knots::bound( inner, Whole.inner, false ), false, marked ? knots::line {} : Rolled },
{ marked ? paint( own, { 1.f, 0.f, 1.f } ) : Whole.material, Whole.extents, knots::bound( Rolled, Whole.inner, false ), false } },
top, Whole, reach, splits, bands,
[&]( double const Middle ) {
auto const rolled { knots::sample( Rolled, Middle, 0.0 ) };
return std::vector<double> { rolled - blend, rolled }; },
if( worn.empty() ) {
auto const edge { knots::sample( reached, Middle, 0.0 ) };
return std::vector<double> { edge - blend, edge };
}
return std::vector<double> { knots::sample( inner, Middle, 0.0 ), knots::sample( rolled, Middle, 0.0 ), knots::sample( worn, Middle, 0.0 ), knots::sample( outer, Middle, 0.0 ) }; },
Ends );
}
@@ -961,21 +1105,39 @@ void TTrack::rail_layer::plain( rail_piece Whole, rail_tread const &Tread, piece
void TTrack::rail_layer::treaded( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends ) {
auto const marked { treads_marked() };
auto const own { m_track.rail_material( m_path ) };
// where the wheels from beside polish the head as its own do, there's no tint between two polishes, only the polish of the rail itself
auto const same { Tread.material == own && false == marked };
if( same && Whole.rolled ) {
plain( Whole, Tread, Ends );
return;
}
Whole.material = null_handle;
auto const top { lay_sides( Whole, Ends ) };
auto const reach { reaches( top ) };
auto const &lower { Tread.below ? Tread.gauge : Tread.edge };
auto const &upper { Tread.below ? Tread.edge : Tread.gauge };
auto const marked { treads_marked() };
auto const own { m_track.rail_material( m_path ) };
auto const colour { 0.5f * ( tread_colour( Tread.material, m_track.tread_band() ) + tread_colour( own, m_track.tread_band() ) ) };
// the lines of the tread run out from the gauge line of the rail, the piece is laid shifted off it
knots::line back;
for( auto const &knot : Whole.offsets ) {
back.emplace_back( knot.x, -knot.y );
}
auto const edge { knots::sum( Tread.edge, back ) };
auto const gauge { knots::sum( Tread.gauge, back ) };
auto const &lower { Tread.below ? gauge : edge };
auto const &upper { Tread.below ? edge : gauge };
// the brighter polish of the tread fades out towards the edge of the reach of the wheels into the colour of the polish of the rail itself,
// keeping the gloss of its steel
auto const colour { glm::clamp( tread_colour( own, m_track.tread_band() ) / glm::max( tread_colour( Tread.material, m_track.tread_band() ), glm::vec3 { 1e-3f } ), 0.f, 4.f ) };
auto const tint = [&]( material_handle const Material, glm::vec3 const &Mark ) {
return paint( Material, marked ? Mark : colour ); };
auto const owntint { Whole.rolled ? tint( own, { 1.f, 0.f, 0.f } ) : null_handle };
auto const sharedtint { tint( Tread.material, { 0.f, 1.f, 0.f } ) };
auto const shared { marked ? tint( Tread.material, { 0.f, 0.f, 1.f } ) : Tread.material };
auto const ramp = [&]( knots::line const &Knots ) {
return marked ? knots::line {} : Knots; };
auto const owned = [&]( knots::line const &Extents, knots::line const &Inner, double const Fade ) {
return marked ?
top_band { Whole.rolled ? tint( own, { 1.f, 0.f, 0.f } ) : null_handle, Extents, Inner, false, {}, Fade } :
top_band { null_handle, Extents, Inner, Whole.rolled }; };
auto const fade { Tread.fade };
auto const centre { knots::middle( lower, upper ) };
auto const beneath { knots::shifted( lower, -fade ) };
@@ -985,15 +1147,32 @@ void TTrack::rail_layer::treaded( rail_piece Whole, rail_tread const &Tread, pie
auto splits { cross_splits( Whole, reach, { &lower, &upper, &lowfade, &highfade, &beneath, &beyond }, 0.001 ) };
add_meetings( knots::shifted( lower, 2.0 * fade ), upper, Whole.from, Whole.to, 0.001, splits );
std::sort( splits.begin(), splits.end() );
if( same ) {
// the head no wheel of its own rolls on is polished across the tread alone, fading into the matte outside it
lay_top(
top, Whole, reach, splits,
{
{ null_handle, knots::bound( Whole.extents, beneath, true ), Whole.inner, false },
{ null_handle, knots::bound( Whole.extents, lower, true ), knots::bound( Whole.inner, beneath, false ), false, beneath, -fade },
{ shared, knots::bound( Whole.extents, upper, true ), knots::bound( Whole.inner, lower, false ) },
{ null_handle, knots::bound( Whole.extents, beyond, true ), knots::bound( Whole.inner, upper, false ), false, beyond, fade },
{ null_handle, Whole.extents, knots::bound( Whole.inner, beyond, false ), false } },
[&]( double const Middle ) {
auto const from { knots::sample( lower, Middle, 0.0 ) };
auto const to { knots::sample( upper, Middle, 0.0 ) };
return std::vector<double> { from - fade, from, to, to + fade }; },
Ends );
return;
}
lay_top(
top, Whole, reach, splits,
{
{ null_handle, knots::bound( Whole.extents, beneath, true ), Whole.inner, Whole.rolled },
{ owntint, knots::bound( Whole.extents, lower, true ), knots::bound( Whole.inner, beneath, false ), false, ramp( lower ), fade },
owned( knots::bound( Whole.extents, lower, true ), knots::bound( Whole.inner, beneath, false ), fade ),
{ sharedtint, knots::bound( Whole.extents, lowfade, true ), knots::bound( Whole.inner, lower, false ), false, ramp( lower ), -fade },
{ shared, knots::bound( Whole.extents, highfade, true ), knots::bound( Whole.inner, lowfade, false ), false == marked },
{ sharedtint, knots::bound( Whole.extents, upper, true ), knots::bound( Whole.inner, highfade, false ), false, ramp( upper ), fade },
{ owntint, knots::bound( Whole.extents, beyond, true ), knots::bound( Whole.inner, upper, false ), false, ramp( upper ), -fade },
owned( knots::bound( Whole.extents, beyond, true ), knots::bound( Whole.inner, upper, false ), -fade ),
{ null_handle, Whole.extents, knots::bound( Whole.inner, beyond, false ), Whole.rolled } },
[&]( double const Middle ) {
auto const from { knots::sample( lower, Middle, 0.0 ) };
@@ -1004,15 +1183,9 @@ void TTrack::rail_layer::treaded( rail_piece Whole, rail_tread const &Tread, pie
gfx::vertex_array TTrack::rail_layer::lay_sides( rail_piece const &Whole, piece_ends const &Ends ) {
gfx::vertex_array vertices;
if( m_segment.RenderLoftSpan( vertices, m_track.m_origin, m_profile, span( Whole, Ends ) ) ) {
cut_faces( vertices, m_profile.size(), Whole.capfrom, Whole.capto, m_drawing.bank, sink( Whole.material, true ) );
}
auto side { Whole };
side.capfrom = false;
side.capto = false;
piece( strip( 0, m_topfirst ), side, Ends );
piece( strip( m_toplast, m_profile.size() - 1 ), side, Ends );
// the sides are lofted in the whole profile, the top left out: lofted apart, the points of a side of the head planed along the rail had none
// of the top beside them to stop at, and went down past the wall of the cut, which went missing between the top and the underside of the head
piece( m_profile, Whole, Ends, m_topfirst, m_toplast );
return strip( m_topfirst, m_toplast );
}
@@ -1037,7 +1210,7 @@ void TTrack::rail_layer::lay_top( gfx::vertex_array const &Top, rail_piece const
}
}
void TTrack::rail_layer::piece( gfx::vertex_array const &Shape, rail_piece const &Piece, piece_ends const &Ends ) {
void TTrack::rail_layer::piece( gfx::vertex_array const &Shape, rail_piece const &Piece, piece_ends const &Ends, std::size_t const Leftfrom, std::size_t const Leftto ) {
gfx::vertex_array vertices;
gfx::userdata_array userdata;
@@ -1052,6 +1225,9 @@ void TTrack::rail_layer::piece( gfx::vertex_array const &Shape, rail_piece const
auto const ring { 2 * Shape.size() };
for( std::size_t block = 0; block + ring <= vertices.size(); block += ring ) {
for( std::size_t j = 0; j + 1 < Shape.size(); ++j ) {
if( j >= Leftfrom && j < Leftto ) {
continue;
}
for( auto const index : { 0, 1, 2, 1, 3, 2 } ) {
auto const vertex { block + 2 * j + index };
triangles.push_back( vertices[ vertex ] );
@@ -1064,7 +1240,7 @@ void TTrack::rail_layer::piece( gfx::vertex_array const &Shape, rail_piece const
if( triangles.empty() ) {
return;
}
sink( Piece.material, false == Piece.rolled ).emplace_back( GfxRenderer->Insert( triangles, triangleuserdata, m_drawing.bank, GL_TRIANGLES ) );
sink( Piece.material, false == Piece.rolled ).emplace_back( insert_chunk( triangles, triangleuserdata, m_drawing.bank, GL_TRIANGLES ) );
}
loft_span TTrack::rail_layer::span( rail_piece const &Piece, piece_ends const &Ends ) const {