17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 12:39:18 +02:00

feat(track): where two rails share a running surface it takes the texture whose running surface is brighter; the brightness is read from the texture file over the u range of the rail head top, the shared parts are the block of crossing grooved rails, the nose of an acute frog from its tip to the end of the V, the through rail across a wide or grooved crossing, the frog of a switch, and the stock rails of a switch with frogs up to half the blade length where the blade grows to a full head

This commit is contained in:
maj00r
2026-10-13 22:36:09 +02:00
parent 2ece18cc4a
commit 7e20023d07
7 changed files with 303 additions and 27 deletions

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include "simulation/simulation.h"
#include "scene/scene.h"
#include "rendering/renderer.h"
#include "stb/stb_image.h"
namespace {
@@ -196,6 +197,97 @@ material_handle cut_material( material_handle const Material ) {
return unrolled ? GfxRenderer->Fetch_Material( "editor_unrolled" ) : GfxRenderer->Matte_Material( Material );
}
float tread_brightness( material_handle const Material, glm::vec2 const &Band ) {
static std::map<std::tuple<material_handle, float, float>, float> brightness;
auto const key { std::make_tuple( Material, Band.x, Band.y ) };
if( auto const lookup { brightness.find( key ) }; lookup != brightness.end() ) {
return lookup->second;
}
auto &result { brightness[ key ] };
result = -1.f;
if( Material == null_handle ) {
return result;
}
auto const texture { GfxRenderer->Material( Material )->GetTexture( 0 ) };
if( texture == null_handle ) {
return result;
}
auto const &image { GfxRenderer->Texture( texture ) };
auto const type { std::string( image.get_type() ) };
auto const file { std::string( image.get_name() ) + type };
auto const columns = [&]( int const Width ) {
auto const first { std::clamp( static_cast<int>( std::floor( std::min( Band.x, Band.y ) * Width ) ), 0, Width - 1 ) };
auto const last { std::clamp( static_cast<int>( std::ceil( std::max( Band.x, Band.y ) * Width ) ) - 1, first, Width - 1 ) };
return std::pair{ first, last }; };
auto sum { 0.0 };
std::size_t count { 0 };
auto const add = [&]( float const Red, float const Green, float const Blue ) {
sum += 0.2126 * Red + 0.7152 * Green + 0.0722 * Blue;
++count; };
if( type == ".dds" ) {
std::ifstream input( file, std::ios::binary );
std::array<unsigned char, 128> header {};
if( input.read( reinterpret_cast<char *>( header.data() ), header.size() ) && std::equal( header.begin(), header.begin() + 4, "DDS " ) ) {
auto const word = [&]( std::size_t const Offset ) {
return static_cast<std::uint32_t>( header[ Offset ] ) | static_cast<std::uint32_t>( header[ Offset + 1 ] ) << 8 | static_cast<std::uint32_t>( header[ Offset + 2 ] ) << 16 | static_cast<std::uint32_t>( header[ Offset + 3 ] ) << 24; };
auto const height { static_cast<int>( word( 12 ) ) };
auto const width { static_cast<int>( word( 16 ) ) };
auto const fourcc { word( 84 ) };
auto const blocksize { fourcc == 0x31545844u ? 8 : ( fourcc == 0x33545844u || fourcc == 0x35545844u ) ? 16 : 0 };
if( blocksize > 0 && width > 0 && height > 0 ) {
auto const wide { ( width + 3 ) / 4 };
auto const high { ( height + 3 ) / 4 };
std::vector<unsigned char> data( static_cast<std::size_t>( wide ) * high * blocksize );
if( input.read( reinterpret_cast<char *>( data.data() ), data.size() ) ) {
auto const [ first, last ] { columns( width ) };
auto const unpack = []( unsigned const Color ) {
return glm::vec3{ ( ( Color >> 11 ) & 31 ) * 255.f / 31.f, ( ( Color >> 5 ) & 63 ) * 255.f / 63.f, ( Color & 31 ) * 255.f / 31.f }; };
for( int row = 0; row < high; ++row ) {
for( int column = first / 4; column <= last / 4; ++column ) {
auto const *block { &data[ ( static_cast<std::size_t>( row ) * wide + column ) * blocksize + ( blocksize - 8 ) ] };
auto const low { static_cast<unsigned>( block[ 0 ] | block[ 1 ] << 8 ) };
auto const top { static_cast<unsigned>( block[ 2 ] | block[ 3 ] << 8 ) };
auto const opaque { blocksize == 16 || low > top };
glm::vec3 const colors[ 4 ] {
unpack( low ),
unpack( top ),
opaque ? ( 2.f * unpack( low ) + unpack( top ) ) / 3.f : 0.5f * ( unpack( low ) + unpack( top ) ),
opaque ? ( unpack( low ) + 2.f * unpack( top ) ) / 3.f : glm::vec3{ 0.f } };
auto const bits { static_cast<std::uint32_t>( block[ 4 ] ) | static_cast<std::uint32_t>( block[ 5 ] ) << 8 | static_cast<std::uint32_t>( block[ 6 ] ) << 16 | static_cast<std::uint32_t>( block[ 7 ] ) << 24 };
for( int texel = 0; texel < 16; ++texel ) {
auto const x { column * 4 + texel % 4 };
auto const index { ( bits >> ( 2 * texel ) ) & 3 };
if( x < first || x > last || ( false == opaque && index == 3 ) ) {
continue;
}
add( colors[ index ].r, colors[ index ].g, colors[ index ].b );
}
}
}
}
}
}
}
else {
int width, height, components;
if( auto *pixels { stbi_load( file.c_str(), &width, &height, &components, 4 ) } ) {
auto const [ first, last ] { columns( width ) };
for( int y = 0; y < height; ++y ) {
for( int x = first; x <= last; ++x ) {
auto const *pixel { &pixels[ ( static_cast<std::size_t>( y ) * width + x ) * 4 ] };
add( pixel[ 0 ], pixel[ 1 ], pixel[ 2 ] );
}
}
stbi_image_free( pixels );
}
}
if( count > 0 ) {
result = static_cast<float>( sum / count );
}
return result;
}
// in the colour of the top of the rail foot
void cap_loft( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::vertex_array &Triangles ) {
@@ -978,6 +1070,47 @@ struct frame_corner {
} // namespace
material_handle TTrack::rail_material( int const Path ) const {
return ( Path == 1 && eType == tt_Switch ? m_material2 : m_material1 );
}
glm::vec2 TTrack::tread_band() const {
glm::vec2 band { 0.45f, 0.55f };
if( m_profiles.empty() ) {
return band;
}
auto const &outline { track_rail_profile( m_profile1.second ) };
auto const count { outline.size() / 2 };
auto top { -1e9f };
for( std::size_t i = 0; i < count; ++i ) {
top = std::max( top, outline[ i ].position.y );
}
glm::vec2 found { 1e9f, -1e9f };
for( std::size_t i = 0; i < count; ++i ) {
if( outline[ i ].position.y > top - 0.0005f ) {
found = { std::min( found.x, outline[ i ].texture.s ), std::max( found.y, outline[ i ].texture.s ) };
}
}
if( found.x > found.y ) {
return band;
}
auto const middle { 0.5f * ( found.x + found.y ) };
auto const half { std::max( 0.5f * ( found.y - found.x ), 0.01f ) };
return { middle - half, middle + half };
}
material_handle TTrack::shared_tread( int const Path, TTrack const &Other, int const Otherpath ) const {
auto const own { rail_material( Path ) };
auto const theirs { Other.rail_material( Otherpath ) };
if( theirs == null_handle || theirs == own ) {
return own;
}
return ( track_rails::tread_brightness( theirs, Other.tread_band() ) > track_rails::tread_brightness( own, tread_band() ) ? theirs : own );
}
TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
rail_layout layout;
@@ -993,6 +1126,21 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
auto &guards { layout.guards };
auto &wingends { layout.wingends };
auto const guardoffset { -( flangeway + railhead ) };
auto const tread = [&]( int const Path, int const Side, glm::dvec2 const &Span, TTrack const &Other, int const Otherpath ) {
auto const material { shared_tread( Path, Other, Otherpath ) };
if( material != rail_material( Path ) && Span.y > Span.x ) {
layout.treads[ Path ][ Side > 0 ].push_back( { Span.x, Span.y, material } );
}
};
auto const across = [&]( rail_crossing const &Crossing, rail_size const &Other ) {
glm::dvec2 span { 1e9, -1e9 };
for( auto const theirs : { Other.grooved ? Other.ledge : 0.0, Other.grooved ? Other.tread : Other.head } ) {
for( auto const ours : { size.grooved ? size.ledge : 0.0, size.grooved ? size.tread : size.head } ) {
auto const distance { Crossing.at + ( Crossing.facing * ours - theirs ) / Crossing.into };
span = { std::min( span.x, distance ), std::max( span.y, distance ) };
}
}
return span; };
std::vector<frame_corner> corners;
if( Crossings ) {
for( auto const &crossing : crossings ) {
@@ -1008,12 +1156,17 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
std::vector<rail_fill> vee;
vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, theirs, true );
vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, theirs, false );
layout.blocks[ crossing.path ][ crossing.side > 0 ].emplace_back( std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) );
glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) };
layout.blocks[ crossing.path ][ crossing.side > 0 ].push_back( block );
tread( crossing.path, crossing.side, block, *crossing.track, crossing.otherpath );
if( through ) {
auto &fills { layout.fills[ crossing.path ][ crossing.side > 0 ] };
fills.insert( fills.end(), vee.begin(), vee.end() );
}
}
else if( through ) {
tread( crossing.path, crossing.side, across( crossing, theirs ), *crossing.track, crossing.otherpath );
}
continue;
}
if( crossing.angle > middle_angle && crossing.angle <= wide_angle ) {
@@ -1023,6 +1176,9 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
auto const other { crossing.track->rail_dimensions() };
auto const through { goes_through( *this, *crossing.track ) };
wide( plan, crossing.at, crossing.into, crossing.facing, through ? 0.0 : -other.head, flangeway, 0.0, railhead );
if( through ) {
tread( crossing.path, crossing.side, across( crossing, other ), *crossing.track, crossing.otherpath );
}
auto const reach { ( flangeway + railhead + 0.3 ) / std::abs( crossing.into ) };
auto &spans { guards[ crossing.path ][ crossing.side > 0 ] };
TTrack::guard_rail guard { crossing.at - reach, crossing.at + reach };
@@ -1047,8 +1203,12 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
wingends[ crossing.path ][ crossing.side > 0 ].emplace_back( crossing.at - toward * wing_reach( slope ), -toward );
acute( plan, crossing.at, toward, slope, crossing.nose,
wing_extension( *this, checks, crossing.path, crossing.side, crossing.at, toward, slope, *crossing.track, crossing.otherpath, crossing.otherside, crossing.otherat, crossing.aligned ? -toward : toward ), size );
auto const nose { toward < 0.0 ? plan.cuts.back().start : plan.cuts.back().end };
std::vector<rail_fill> vee;
vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, crossing.track->rail_dimensions(), true );
tread( crossing.path, crossing.side, { std::min( nose, vee.front().from ), std::max( nose, vee.front().to ) }, *crossing.track, crossing.otherpath );
if( crossing.nose ) {
vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, crossing.track->rail_dimensions(), true );
layout.fills[ crossing.path ][ crossing.side > 0 ].push_back( vee.front() );
}
}
else {
@@ -1135,6 +1295,11 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
}
}
}
if( eType == tt_Switch && cut_rails( *this ) ) {
for( int path = 0; path < 2; ++path ) {
tread( path, -blade_side( path ), { 0.0, 0.5 * blade_length( *this, path ) }, *this, 1 - path );
}
}
if( eType == tt_Switch ) {
auto const frog { this->frog() };
if( frog.valid ) {
@@ -1155,7 +1320,9 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
std::vector<rail_fill> vee;
vee_fill( vee, frog.at[ path ], into, facing, size, size, true );
vee_fill( vee, frog.at[ path ], into, facing, size, size, false );
layout.blocks[ path ][ frog.side[ path ] > 0 ].emplace_back( std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) );
glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) };
layout.blocks[ path ][ frog.side[ path ] > 0 ].push_back( block );
tread( path, frog.side[ path ], block, *this, 1 - path );
if( path == 0 ) {
fills.insert( fills.end(), vee.begin(), vee.end() );
}
@@ -1164,8 +1331,12 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
wingends[ path ][ frog.side[ path ] > 0 ].emplace_back( frog.at[ path ] + wing_reach( slope ), 1.0 );
acute( plans[ path ][ frog.side[ path ] > 0 ], frog.at[ path ], -1.0, slope, path == 0,
wing_extension( *this, checks, path, frog.side[ path ], frog.at[ path ], -1.0, slope, *this, 1 - path, frog.side[ 1 - path ], frog.at[ 1 - path ], 1.0 ), size );
auto const nose { plans[ path ][ frog.side[ path ] > 0 ].cuts.back().start };
std::vector<rail_fill> vee;
vee_fill( vee, frog.at[ path ], into, facing, size, size, true );
tread( path, frog.side[ path ], { std::min( nose, vee.front().from ), std::max( nose, vee.front().to ) }, *this, 1 - path );
if( path == 0 ) {
vee_fill( fills, frog.at[ path ], into, facing, size, size, true );
fills.push_back( vee.front() );
}
}
}
@@ -1247,12 +1418,40 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
auto const &segment { path_segment( *this, Path ) };
auto const &plan { Layout.plans[ Path ][ Side > 0 ] };
auto const length { segment.GetLength() };
auto const &treads { Layout.treads[ Path ][ Side > 0 ] };
material_handle current { null_handle };
auto const sink = [&]( bool const Matte ) -> geometryhandle_sequence & {
if( current == null_handle ) {
return Matte ? Cuts : Geometry;
}
if( current == m_material1 ) {
return Matte ? GeometryCut1 : Geometry1;
}
if( eType == tt_Switch && current == m_material2 ) {
return Matte ? GeometryCut2 : Geometry2;
}
auto found { std::find_if( GeometryTreads.begin(), GeometryTreads.end(), [&]( tread_geometry const &Tread ) { return Tread.material == current; } ) };
if( found == GeometryTreads.end() ) {
found = GeometryTreads.insert( GeometryTreads.end(), { current, cut_material( current ), {}, {} } );
}
return Matte ? found->cuts : found->geometry; };
auto const texturelength = [&]() {
return ( current == null_handle ? Texturelength : texture_length( current ) ); };
auto const aim = [&]( std::vector<rail_tread> const &Spans, double const Middle ) {
current = null_handle;
for( auto const &span : Spans ) {
if( Middle > span.from && Middle < span.to ) {
current = span.material;
break;
}
}
};
// parts no wheel rolls on go in the matte material of the cut faces
auto const piece = [&]( double const Start, double const End, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, bool const Capstart, bool const Capend, gfx::vertex_array const &Shape, bool const Rolled, std::vector<glm::dvec2> const &Inner, std::vector<glm::dvec2> const &Ramp = {}, bool const Walls = false, float const Lift = 0.f, float const Wallu = -1.f ) {
gfx::vertex_array vertices;
gfx::userdata_array userdata;
if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, Texturelength, Start, End, Offsets, Extents, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : blend, Walls, Wallu ) ) {
cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, Cuts );
if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : blend, Walls, Wallu ) ) {
cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) );
gfx::vertex_array triangles;
gfx::userdata_array triangleuserdata;
auto const ring { 2 * Shape.size() };
@@ -1267,7 +1466,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
}
}
}
( Rolled ? Geometry : Cuts ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) );
sink( false == Rolled ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) );
}
};
auto const level = []( std::vector<glm::dvec2> const &Knots, double const Distance ) {
@@ -1320,8 +1519,8 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
return gfx::vertex_array( Profile.begin() + First, Profile.begin() + Last + 1 ); };
auto const running = [&]( double const Start, double const End, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, std::vector<glm::dvec2> const &Inner, std::vector<glm::dvec2> const &Rolled, bool const Capstart, bool const Capend ) {
gfx::vertex_array vertices;
if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, Texturelength, Start, End, Offsets, Extents, 0.f, Inner ) ) {
cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, Cuts );
if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner ) ) {
cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) );
}
piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner );
piece( Start, End, Offsets, Extents, false, false, strip( toplast, Profile.size() - 1 ), true, Inner );
@@ -1470,11 +1669,13 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
auto const a { std::max( fill.from, From ) };
auto const b { std::min( fill.to, length ) };
if( b - a > 0.01 ) {
aim( treads, 0.5 * ( a + b ) );
piece( a, b, {}, fill.outer, a == fill.from && fill.capfrom, b == fill.to && fill.capto, Drawing.plates[ Side > 0 ? 0 : 1 ], true, fill.inner, {}, true, static_cast<float>( fill.lift ) );
current = null_handle;
}
}
auto const &bands { Layout.bands[ Path ][ Side > 0 ] };
if( plan.cuts.empty() && bands.empty() ) {
if( plan.cuts.empty() && bands.empty() && treads.empty() ) {
return false;
}
auto const byposition = []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; };
@@ -1509,7 +1710,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
( outwards ? cut.endinner : cut.endextents ) = endknots;
( outwards ? cut.startextents : cut.startinner ) = startknots;
return cut; };
auto const lay = [&]( gfx::vertex_array const &Shape, std::vector<rail_cut> cuts, bool const Wheels, float const Wallu = -1.f ) {
auto const lay = [&]( gfx::vertex_array const &Shape, std::vector<rail_cut> cuts, bool const Wheels, std::vector<rail_tread> const &Spans, float const Wallu = -1.f ) {
std::sort( cuts.begin(), cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } );
auto start { -1e9 };
auto startcap { true };
@@ -1534,12 +1735,30 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
auto const capstart { start > From ? startcap : Openstart };
auto const capend { end < length ? cuts[ i ].endcap : Openend };
if( b - a > 0.01 ) {
if( false == Wheels || rolled.empty() || topfirst >= toplast ) {
piece( a, b, pieceoffsets, pieceextents, capstart, capend, Shape, true, pieceinner, {}, false == Wheels, 0.f, Wallu );
std::vector<double> splits;
for( auto const &span : Spans ) {
for( auto const edge : { span.from, span.to } ) {
if( edge > a + 0.01 && edge < b - 0.01 ) {
splits.push_back( edge );
}
}
}
else {
running( a, b, pieceoffsets, pieceextents, pieceinner, rolled, capstart, capend );
std::sort( splits.begin(), splits.end() );
splits.erase( std::unique( splits.begin(), splits.end(), []( double const A, double const B ) { return B - A < 0.002; } ), splits.end() );
splits.insert( splits.begin(), a );
splits.push_back( b );
for( std::size_t k = 1; k < splits.size(); ++k ) {
auto const first { k == 1 };
auto const last { k + 1 == splits.size() };
aim( Spans, 0.5 * ( splits[ k - 1 ] + splits[ k ] ) );
if( false == Wheels || rolled.empty() || topfirst >= toplast ) {
piece( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, first && capstart, last && capend, Shape, true, pieceinner, {}, false == Wheels, 0.f, Wallu );
}
else {
running( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, pieceinner, rolled, first && capstart, last && capend );
}
}
current = null_handle;
}
if( i < cuts.size() ) {
rolledfrom = cuts[ i ].startrolled;
@@ -1559,7 +1778,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
for( auto const &band : bands ) {
cuts.push_back( band_cut( band, reach ) );
}
lay( Profile, cuts, true );
lay( Profile, cuts, true, treads );
return true;
}
auto const height = [&]( std::size_t const Index ) {
@@ -1661,7 +1880,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
wholecuts.push_back( band_cut( band, bodyreach ) );
}
}
lay( body, wholecuts, false );
lay( body, wholecuts, false, {} );
auto const wallu { floortextures > 0 ? floortexture / floortextures : -1.f };
for( std::size_t k = 0; k < tops.size(); ++k ) {
auto const reach { reaches( tops[ k ] ) };
@@ -1708,9 +1927,18 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
}
}
if( merged.empty() ) {
lay( tops[ k ], cuts, false, wallu );
lay( tops[ k ], cuts, false, treads, wallu );
continue;
}
std::vector<rail_tread> blocktreads;
for( auto const &span : merged ) {
for( auto const &tread : treads ) {
if( tread.from < span.y && tread.to > span.x ) {
blocktreads.push_back( { span.x, span.y, tread.material } );
break;
}
}
}
auto const seam = []( double const At, double const End, double const Start ) {
rail_cut cut;
cut.at = At;
@@ -1731,8 +1959,8 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
seam( 0.5 * ( merged[ m ].y + merged[ m + 1 ].x ), merged[ m ].y, merged[ m + 1 ].x ) :
seam( merged[ m ].y + 0.001, merged[ m ].y, 1e9 ) );
}
lay( tops[ k ], outside, false, wallu );
lay( flats[ k ], inside, false, wallu );
lay( tops[ k ], outside, false, treads, wallu );
lay( flats[ k ], inside, false, blocktreads, wallu );
}
return true;
}