17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 07:29:20 +02:00
Files
maszyna/world/RailLayer.cpp

1149 lines
49 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "world/TrackRails.h"
#include "rendering/renderer.h"
namespace track_rails {
namespace {
// in the colour of the top of the rail foot
glm::vec2 cap_texture( std::vector<gfx::basic_vertex> const &Ring, std::vector<glm::dvec2> const &Plan ) {
auto bottom { 0.0 }, top { 0.0 };
for( auto const &point : Plan ) {
bottom = std::min( bottom, point.y );
top = std::max( top, point.y );
}
auto web { Ring[ 0 ].texture };
auto nearest { std::numeric_limits<double>::max() };
for( std::size_t j = 0; j < Ring.size() / 2; ++j ) {
auto const distance { std::abs( Plan[ j ].y - 0.5 * ( bottom + top ) ) };
if( distance < nearest ) {
nearest = distance;
web = Ring[ j ].texture;
}
}
auto lowest { std::numeric_limits<double>::max() };
for( std::size_t j = 0; j < Ring.size() / 2; ++j ) {
if( Ring[ j ].normal.y > 0.5f && Plan[ j ].y < lowest ) {
lowest = Plan[ j ].y;
web = Ring[ j ].texture;
}
}
return web;
}
std::vector<std::array<std::size_t, 3>> ear_clip( std::vector<glm::dvec2> const &Polygon ) {
auto const cross = []( glm::dvec2 const &A, glm::dvec2 const &B, glm::dvec2 const &C ) {
return ( B.x - A.x ) * ( C.y - A.y ) - ( B.y - A.y ) * ( C.x - A.x ); };
double area { 0.0 };
for( std::size_t j = 0; j < Polygon.size(); ++j ) {
auto const &a { Polygon[ j ] };
auto const &b { Polygon[ ( j + 1 ) % Polygon.size() ] };
area += a.x * b.y - b.x * a.y;
}
auto const orientation { area < 0.0 ? -1.0 : 1.0 };
std::vector<std::size_t> left( Polygon.size() );
for( std::size_t j = 0; j < Polygon.size(); ++j ) {
left[ j ] = j;
}
std::vector<std::array<std::size_t, 3>> triangles;
while( left.size() > 3 ) {
auto ear { left.size() };
for( std::size_t j = 0; j < left.size() && ear == left.size(); ++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 turn { cross( a, b, c ) * orientation };
if( turn < -1e-12 ) {
continue;
}
auto inside { false };
if( turn > 1e-12 ) {
for( auto const k : left ) {
auto const &p { Polygon[ k ] };
if( p == a || p == b || p == c ) {
continue;
}
if( cross( a, b, p ) * orientation > 1e-12 && cross( b, c, p ) * orientation > 1e-12 && cross( c, a, p ) * orientation > 1e-12 ) {
inside = true;
break;
}
}
}
if( false == inside ) {
ear = j;
}
}
if( ear == left.size() ) {
ear = 0;
}
triangles.push_back( { left[ ( ear + left.size() - 1 ) % left.size() ], left[ ear ], left[ ( ear + 1 ) % left.size() ] } );
left.erase( left.begin() + ear );
}
triangles.push_back( { left[ 0 ], left[ 1 ], left[ 2 ] } );
return triangles;
}
} // namespace
void cap_loft( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::vertex_array &Triangles ) {
if( Ring < 3 || Strip.size() < 2 * Ring ) {
return;
}
std::vector<std::pair<std::vector<gfx::basic_vertex>, glm::vec3>> caps;
if( Start ) {
std::vector<gfx::basic_vertex> ring;
for( std::size_t j = 0; j < Ring; ++j ) {
ring.emplace_back( Strip[ 2 * j ] );
}
caps.emplace_back( ring, Strip[ 0 ].position - Strip[ 1 ].position );
}
if( End ) {
auto const base { Strip.size() - 2 * Ring };
std::vector<gfx::basic_vertex> ring;
for( std::size_t j = 0; j < Ring; ++j ) {
ring.emplace_back( Strip[ base + 2 * j + 1 ] );
}
caps.emplace_back( ring, Strip[ base + 1 ].position - Strip[ base ].position );
}
for( auto const &cap : caps ) {
auto const &ring { cap.first };
if( glm::length2( cap.second ) == 0.f ) {
continue;
}
auto const outward { glm::normalize( cap.second ) };
auto across { glm::vec3{ -outward.z, 0.f, outward.x } };
if( glm::length2( across ) == 0.f ) {
continue;
}
across = glm::normalize( across );
std::vector<glm::dvec2> plan;
for( auto const &vertex : ring ) {
auto const offset { vertex.position - ring[ 0 ].position };
plan.emplace_back( glm::dot( offset, across ), offset.y );
}
auto const web { cap_texture( ring, plan ) };
auto const triangles { ear_clip( plan ) };
for( auto const &triangle : triangles ) {
for( auto const &order : { std::array<int, 3>{ 0, 1, 2 }, std::array<int, 3>{ 0, 2, 1 } } ) {
for( auto const k : order ) {
auto const index { triangle[ k ] };
Triangles.emplace_back( ring[ index ].position, outward, web );
}
}
}
}
}
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 } } );
}
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 ) );
}
}
} // namespace track_rails
using namespace track_rails;
namespace {
gfx::vertex_array box_profile( float const Bottom, float const Top, float const Texture, float const Spread = 0.f ) {
return {
{{ 1.f, Bottom, 0.f }, { 1.f, 0.f, 0.f }, { Texture + Spread, 0.f }},
{{ 1.f, Top, 0.f }, { 1.f, 0.f, 0.f }, { Texture + Spread, 0.f }},
{{ 1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture + Spread, 0.f }},
{{ -1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture - Spread, 0.f }},
{{ -1.f, Top, 0.f }, { -1.f, 0.f, 0.f }, { Texture - Spread, 0.f }},
{{ -1.f, Bottom, 0.f }, { -1.f, 0.f, 0.f }, { Texture - Spread, 0.f }} };
}
} // namespace
TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const &Bank ) {
auto const sides = [&]( gfx::vertex_array const &Shape, gfx::vertex_array ( &Sides )[ 2 ] ) {
create_track_rail_profile( Sides[ 0 ], Sides[ 1 ], &Shape );
for( auto &side : Sides ) {
side.resize( Shape.size() );
} };
rail_drawing drawing;
drawing.bank = Bank;
drawing.size = rail_dimensions();
drawing.halfwidth = 0.5f * std::abs( fTrackWidth );
// check rail: a narrow head facing the running rail across the flangeway, on a thin web with a foot turned away from it, sized after the rail
gfx::vertex_array checkshape {
{{ -0.041f, -0.170f, 0.f }, { 1.000f, 0.000f, 0.f }, { 0.15f, 0.f }},
{{ -0.041f, -0.010f, 0.f }, { 0.707f, 0.707f, 0.f }, { 0.40f, 0.f }},
{{ -0.051f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.45f, 0.f }},
{{ -0.071f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.55f, 0.f }},
{{ -0.081f, -0.010f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.60f, 0.f }},
{{ -0.081f, -0.045f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.65f, 0.f }},
{{ -0.066f, -0.060f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.75f, 0.f }},
{{ -0.066f, -0.150f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.85f, 0.f }},
{{ -0.101f, -0.170f, 0.f }, { -1.000f, 0.000f, 0.f }, { 1.00f, 0.f }} };
for( auto &point : checkshape ) {
point.position.x = -0.041f + ( point.position.x + 0.041f ) * static_cast<float>( drawing.size.head / 0.0735 );
point.position.y *= static_cast<float>( drawing.size.height / 0.172 );
}
sides( checkshape, drawing.checkshapes );
if( false == m_profiles.empty() ) {
auto const &outline { track_rail_profile( m_profile1.second ) };
auto texture { 0.f };
auto highest { -1e9f };
for( std::size_t i = 0; i < outline.size() / 2; ++i ) {
if( outline[ i ].position.y > highest ) {
highest = outline[ i ].position.y;
texture = outline[ i ].texture.s;
}
}
if( drawing.size.grooved ) {
auto sum { 0.f };
auto points { 0 };
for( std::size_t i = 0; i < outline.size() / 2; ++i ) {
if( outline[ i ].position.y > highest - 0.0005f && outline[ i ].position.x > -0.02f ) {
sum += outline[ i ].texture.s;
++points;
}
}
texture = sum / std::max( points, 1 );
}
auto const top { static_cast<float>( ( drawing.size.grooved ? drawing.size.liptop : highest ) - 0.0005 ) };
auto const bottom { static_cast<float>( -drawing.size.height ) };
drawing.platetexture = texture;
sides( box_profile( bottom, top, texture ), drawing.plates );
auto const neck { static_cast<float>( drawing.size.neck ) };
auto webtexture { texture };
for( std::size_t i = 0; i < outline.size() / 2; ++i ) {
auto const &point { outline[ i ] };
if( point.position.y < neck && point.normal.y > 0.9f ) {
webtexture = point.texture.s;
break;
}
}
glm::vec2 across { 1e9f, -1e9f };
glm::vec2 band { 1e9f, -1e9f };
for( std::size_t i = 0; i < outline.size() / 2; ++i ) {
if( outline[ i ].position.y > highest - 0.0005f ) {
across = { std::min( across.x, outline[ i ].position.x ), std::max( across.y, outline[ i ].position.x ) };
band = { std::min( band.x, outline[ i ].texture.s ), std::max( band.y, outline[ i ].texture.s ) };
}
}
auto const spread { across.y > across.x ? ( band.y - band.x ) / ( across.y - across.x ) : 0.f };
sides( box_profile( bottom, neck, webtexture, spread ), drawing.fillers );
}
return drawing;
}
namespace {
struct piece_ends {
double from { 0.0 };
double to { 0.0 };
loft_end fromend;
loft_end toend;
};
struct rail_piece {
material_handle material { null_handle };
double from { 0.0 };
double to { 0.0 };
knots::line offsets;
knots::line extents;
knots::line inner;
bool capfrom { false };
bool capto { false };
bool rolled { true };
knots::line ramp;
double fade { blend };
bool walls { false };
float lift { 0.f };
float wallu { -1.f };
};
struct top_band {
material_handle material { null_handle };
knots::line extents;
knots::line inner;
bool rolled { true };
knots::line ramp;
double fade { blend };
};
struct grooved_profile {
gfx::vertex_array body;
std::vector<gfx::vertex_array> tops;
std::vector<gfx::vertex_array> flats;
float wallu { -1.f };
};
glm::vec3 const plain_mark { 0.f, 1.f, 1.f };
bool treads_marked() {
static auto const marked { std::getenv( "EU07_EDITOR_TREADS" ) != nullptr };
return marked;
}
void sort_cuts( std::vector<rail_cut> &Cuts ) {
std::sort( Cuts.begin(), Cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } );
}
void add_meetings( knots::line const &A, knots::line const &B, double const Start, double const End, double const Margin, std::vector<double> &Splits ) {
for( auto const distance : knots::meetings( A, B ) ) {
if( distance > Start + Margin && distance < End - Margin ) {
Splits.push_back( distance );
}
}
}
std::vector<double> cross_splits( rail_piece const &Whole, glm::dvec2 const &Reach, std::initializer_list<knots::line const *> const Edges, double const Margin ) {
std::vector<double> result { Whole.from, Whole.to };
knots::line const lowline { { Whole.from, Reach.x }, { Whole.to, Reach.x } };
knots::line const highline { { Whole.from, Reach.y }, { Whole.to, Reach.y } };
for( auto const *edge : Edges ) {
for( auto const *limit : { &lowline, &highline, &Whole.extents, &Whole.inner } ) {
add_meetings( *edge, *limit, Whole.from, Whole.to, Margin, result );
}
}
std::sort( result.begin(), result.end() );
return result;
}
knots::line flared( double const From, double const To, double const Flare, double const Base, 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 } );
}
if( Todepth > 0.0 ) {
result.insert( result.end(), { { To - Flare, Base }, { To, Base - Todepth } } );
}
else {
result.push_back( { To, Base } );
}
return result;
}
knots::line envelope( knots::line const &Lines, bool const Upper, double const A, double const B ) {
knots::line result;
if( Lines.empty() ) {
return result;
}
std::vector<double> stations { A, B };
for( std::size_t i = 0; i < Lines.size(); ++i ) {
for( std::size_t j = i + 1; j < Lines.size(); ++j ) {
if( std::abs( Lines[ i ].x - Lines[ j ].x ) > 1e-12 ) {
auto const d { ( Lines[ j ].y - Lines[ i ].y ) / ( Lines[ i ].x - Lines[ j ].x ) };
if( d > A && d < B ) {
stations.push_back( d );
}
}
}
}
std::sort( stations.begin(), stations.end() );
for( auto const d : stations ) {
auto value { Upper ? 1e6 : -1e6 };
for( auto const &line : Lines ) {
value = Upper ? std::min( value, line.x * d + line.y ) : std::max( value, line.x * d + line.y );
}
result.emplace_back( d, value );
}
return result;
}
rail_cut band_cut( rail_band const &Band, glm::dvec2 const &Reach ) {
rail_cut cut;
cut.at = Band.at;
auto const before { Band.into > 0.0 ? Band.from : Band.to };
auto const after { Band.into > 0.0 ? Band.to : Band.from };
auto const meets = [&]( double const Edge, double const Across ) {
return glm::dvec2{ Band.at + ( Edge + Band.facing * Across ) / Band.into, Across }; };
if( std::abs( Band.facing ) < 1e-3 ) {
cut.end = meets( before, 0.0 ).x;
cut.start = meets( after, 0.0 ).x;
return cut;
}
knots::line endknots { meets( before, Reach.x ), meets( before, Reach.y ) };
knots::line startknots { meets( after, Reach.x ), meets( after, Reach.y ) };
knots::sort( endknots );
knots::sort( startknots );
cut.end = endknots.back().x;
cut.start = startknots.front().x;
cut.endcap = false;
cut.startcap = false;
auto const outwards { Band.facing / Band.into > 0.0 };
( outwards ? cut.endinner : cut.endextents ) = endknots;
( outwards ? cut.startextents : cut.startinner ) = startknots;
return cut;
}
rail_cut seam( double const At, double const End, double const Start ) {
rail_cut cut;
cut.at = At;
cut.end = End;
cut.start = Start;
cut.endcap = false;
cut.startcap = false;
return cut;
}
knots::line merged_blocks( knots::line Blocks, std::vector<rail_cut> const &Cuts ) {
for( auto const &cut : Cuts ) {
for( auto &block : Blocks ) {
if( cut.at > block.x && cut.at < block.y ) {
block = { std::min( block.x, cut.end ), std::max( block.y, cut.start ) };
}
}
}
knots::sort( Blocks );
knots::line result;
for( auto const &block : Blocks ) {
if( false == result.empty() && block.x <= result.back().y ) {
result.back().y = std::max( result.back().y, block.y );
}
else {
result.push_back( block );
}
}
return result;
}
rail_tread const *tread_at( std::vector<rail_tread> const &Treads, double const Middle ) {
for( auto const &tread : Treads ) {
if( Middle > tread.from && Middle < tread.to ) {
return &tread;
}
}
return nullptr;
}
material_handle tread_material( rail_tread const *Tread ) {
return ( Tread != nullptr ? Tread->material : null_handle );
}
} // namespace
class TTrack::rail_layer {
public:
// constructors
rail_layer( TTrack &Track, rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts );
// methods
bool lay();
private:
// methods
template <typename Type_>
Type_ const &ours( Type_ const ( &Sides )[ 2 ][ 2 ] ) const {
return Sides[ m_path ][ m_side > 0 ]; }
std::vector<rail_tread> const &treads() const {
return ours( m_layout.treads ); }
void lay_guards();
void lay_checks();
void lay_fills();
void lay_treads( rail_piece const &Whole, std::vector<rail_tread> const &Treads );
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 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 = {} );
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 );
material_handle paint( material_handle const Material, glm::vec3 const &Colour );
glm::dvec2 reaches( gfx::vertex_array const &Shape ) const;
gfx::vertex_array strip( std::size_t const First, std::size_t const Last ) const;
// members
TTrack &m_track;
rail_layout const &m_layout;
rail_drawing const &m_drawing;
int const m_path;
gfx::vertex_array const &m_profile;
int const m_side;
float const m_texturelength;
double const m_from;
bool const m_openstart;
bool const m_openend;
geometryhandle_sequence &m_geometry;
geometryhandle_sequence &m_cuts;
TSegment const &m_segment;
double const m_length;
double const m_guardoffset;
std::size_t m_topfirst;
std::size_t m_toplast { 0 };
std::vector<material_handle> m_tints;
};
TTrack::rail_layer::rail_layer( TTrack &Track, rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) :
m_track { Track },
m_layout { Layout },
m_drawing { Drawing },
m_path { Path },
m_profile { Profile },
m_side { Side },
m_texturelength { Texturelength },
m_from { From },
m_openstart { Openstart },
m_openend { Openend },
m_geometry { Geometry },
m_cuts { Cuts },
m_segment { path_segment( Track, Path ) },
m_length { m_segment.GetLength() },
m_guardoffset { -( flangeway + Drawing.size.head ) },
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 ) {
if( Profile[ j ].position.y > static_cast<float>( -0.087 * Drawing.size.height ) ) {
m_topfirst = std::min( m_topfirst, j );
m_toplast = std::max( m_toplast, j );
}
}
}
bool TTrack::rail_layer::lay() {
lay_guards();
lay_checks();
lay_fills();
auto const &plan { ours( m_layout.plans ) };
auto const &bands { ours( m_layout.bands ) };
if( plan.cuts.empty() && bands.empty() && treads().empty() ) {
return false;
}
auto const &outline { track_rail_profile( m_track.m_profile1.second ) };
if( false == m_drawing.size.grooved || outline.size() != 2 * m_profile.size() ) {
auto cuts { plan.cuts };
auto const reach { reaches( m_profile ) };
for( auto const &band : bands ) {
cuts.push_back( band_cut( band, reach ) );
}
lay_cuts( m_profile, cuts, true, treads() );
return true;
}
lay_grooved( outline );
return true;
}
void TTrack::rail_layer::lay_guards() {
auto const &size { m_drawing.size };
auto const top { flangeway + size.head + size.low - 0.002 };
auto const mitered = [&]( glm::dvec2 const &Miter, double const Square, bool const Latest ) {
if( Miter.y == 0.0 ) {
return Square;
}
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 ); };
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 ) };
auto cuts { guard.plan.cuts };
sort_cuts( cuts );
auto start { guard.from };
auto startmiter { guard.frommiter };
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 ) } ) };
if( b - a > 0.01 ) {
knots::line uppers { { 0.0, top } };
knots::line lowers;
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 );
}
if( i < cuts.size() && cuts[ i ].start > start ) {
start = cuts[ i ].start;
startmiter = cuts[ i ].startmiter;
}
}
}
}
void TTrack::rail_layer::lay_checks() {
auto const &shape { m_drawing.checkshapes[ m_side > 0 ? 0 : 1 ] };
for( auto const &check : m_layout.checks[ m_path ][ m_side > 0 ] ) {
auto const a { std::max( check.from, m_from ) };
auto const b { std::min( check.to, m_length ) };
if( replaced_by_wing( check, ours( m_layout.wingends ) ) || b - a <= 0.01 ) {
continue;
}
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 },
{ 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 ) {
std::vector<double> splits { Whole.from, Whole.to };
for( auto const &tread : Treads ) {
for( auto const edge : { tread.from, tread.to } ) {
if( edge > Whole.from + 0.01 && edge < Whole.to - 0.01 ) {
splits.push_back( edge );
}
}
}
std::sort( splits.begin(), splits.end() );
piece_ends const ends { Whole.from, Whole.to };
for( std::size_t k = 1; k < splits.size(); ++k ) {
auto part { Whole };
part.from = splits[ k - 1 ];
part.to = splits[ k ];
part.capfrom = ( k == 1 && Whole.capfrom );
part.capto = ( k + 1 == splits.size() && Whole.capto );
auto const *tread { tread_at( Treads, 0.5 * ( part.from + part.to ) ) };
if( tread != nullptr && m_topfirst < m_toplast && tread->edge.empty() ) {
plain( part, *tread, ends );
}
else if( tread != nullptr && m_topfirst < m_toplast ) {
treaded( part, *tread, ends );
}
else {
piece( m_profile, part, ends );
}
}
}
void TTrack::rail_layer::lay_fills() {
for( auto const &fill : ours( m_layout.fills ) ) {
auto const a { std::max( fill.from, m_from ) };
auto const b { std::min( fill.to, m_length ) };
if( b - a > 0.01 ) {
auto const &shapes { fill.web ? m_drawing.fillers : m_drawing.plates };
piece(
shapes[ m_side > 0 ? 0 : 1 ],
{ .material = ( fill.web ? null_handle : tread_material( tread_at( treads(), 0.5 * ( a + b ) ) ) ), .from = a, .to = b, .extents = fill.outer, .inner = fill.inner, .capfrom = a == fill.from && fill.capfrom, .capto = b == fill.to && fill.capto, .walls = true, .lift = static_cast<float>( fill.lift ) } );
}
}
}
void TTrack::rail_layer::lay_cuts( gfx::vertex_array const &Shape, std::vector<rail_cut> Cuts, bool const Wheels, std::vector<rail_tread> const &Treads, float const Wallu ) {
sort_cuts( Cuts );
auto start { -1e9 };
auto startcap { true };
auto startbend { 0.0 };
knots::line rolledfrom;
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 };
auto pieceoffsets { offsets };
auto pieceextents { extents };
auto pieceinner { inner };
if( i < Cuts.size() ) {
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 );
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 capstart { start > m_from ? startcap : m_openstart };
auto const capend { end < m_length ? Cuts[ i ].endcap : m_openend };
piece_ends const ends {
a, b,
{ start > m_from ? startbend : 0.0 },
{ end < m_length ? Cuts[ i ].endbend : 0.0 } };
if( b - a > 0.01 ) {
std::vector<rail_tread> active;
for( auto const &tread : Treads ) {
if( tread.anchor == 0
|| ( tread.anchor > 0 && b > tread.from + 0.01 && b < tread.to + 0.01 )
|| ( tread.anchor < 0 && a > tread.from - 0.01 && a < tread.to - 0.01 ) ) {
active.push_back( tread );
}
}
std::vector<double> splits;
for( auto const &tread : active ) {
for( auto const edge : { tread.from, tread.to } ) {
if( edge > a + 0.01 && edge < b - 0.01 ) {
splits.push_back( edge );
}
}
}
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 *tread { tread_at( active, 0.5 * ( splits[ k - 1 ] + splits[ k ] ) ) };
rail_piece const whole {
.material = ( tread != nullptr && treads_marked() ? paint( tread->material, plain_mark ) : tread_material( tread ) ),
.from = splits[ k - 1 ],
.to = splits[ k ],
.offsets = pieceoffsets,
.extents = pieceextents,
.inner = pieceinner,
.capfrom = k == 1 && capstart,
.capto = k + 1 == splits.size() && capend,
.walls = false == Wheels,
.wallu = Wallu };
if( Wheels && tread != nullptr && false == tread->edge.empty() && m_topfirst < m_toplast ) {
treaded( whole, *tread, ends );
}
else if( false == Wheels || rolled.empty() || m_topfirst >= m_toplast ) {
piece( Shape, whole, ends );
}
else {
running( whole, rolled, ends );
}
}
}
if( i < Cuts.size() ) {
rolledfrom = Cuts[ i ].startrolled;
start = Cuts[ i ].start;
startcap = Cuts[ i ].startcap;
startbend = Cuts[ i ].startbend;
offsets = Cuts[ i ].startoffsets;
extents = Cuts[ i ].startextents;
inner = Cuts[ i ].startinner;
}
}
}
void TTrack::rail_layer::lay_grooved( gfx::vertex_array const &Outline ) {
auto const grooved { split_grooved( Outline ) };
auto const &bands { ours( m_layout.bands ) };
auto wholecuts { ours( m_layout.plans ).cuts };
auto const bodyreach { reaches( grooved.body ) };
for( auto const &band : bands ) {
if( band.whole ) {
wholecuts.push_back( band_cut( band, bodyreach ) );
}
}
lay_cuts( grooved.body, wholecuts, false, {} );
for( std::size_t k = 0; k < grooved.tops.size(); ++k ) {
auto const cuts { top_cuts( grooved.tops[ k ] ) };
auto const merged { merged_blocks( ours( m_layout.blocks ), cuts ) };
if( merged.empty() ) {
lay_cuts( grooved.tops[ k ], cuts, false, treads(), grooved.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;
}
}
}
std::vector<rail_cut> outside, inside;
for( auto const &cut : cuts ) {
auto const within { std::any_of( merged.begin(), merged.end(), [&]( glm::dvec2 const &Span ) { return cut.at > Span.x && cut.at < Span.y; } ) };
( within ? inside : outside ).push_back( cut );
}
inside.push_back( seam( merged.front().x - 0.001, -1e9, merged.front().x ) );
for( std::size_t m = 0; m < merged.size(); ++m ) {
outside.push_back( seam( 0.5 * ( merged[ m ].x + merged[ m ].y ), merged[ m ].x, merged[ m ].y ) );
inside.push_back( m + 1 < merged.size() ?
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_cuts( grooved.tops[ k ], outside, false, treads(), grooved.wallu );
lay_cuts( grooved.flats[ k ], inside, false, blocktreads, grooved.wallu );
}
}
grooved_profile TTrack::rail_layer::split_grooved( gfx::vertex_array const &Outline ) const {
auto const &size { m_drawing.size };
auto const count { m_profile.size() };
auto const height = [&]( std::size_t const Index ) {
return static_cast<double>( Outline[ m_side > 0 ? Index : count - 1 - Index ].position.y ) - size.groove; };
auto const above = [&]( std::size_t const Index ) {
return height( Index ) > 1e-4; };
auto const floorpoint = [&]( std::size_t const A, std::size_t const B ) {
auto const t { static_cast<float>( height( A ) / ( height( A ) - height( B ) ) ) };
auto point { m_profile[ above( A ) ? A : B ] };
point.position = glm::mix( m_profile[ A ].position, m_profile[ B ].position, t );
point.texture = glm::mix( m_profile[ A ].texture, m_profile[ B ].texture, t );
return point; };
auto const flattened = [&]( std::size_t const Index ) {
auto point { m_profile[ Index ] };
auto const &file { Outline[ m_side > 0 ? Index : count - 1 - Index ].position };
if( file.y > size.crest - 0.0005 || ( file.x < -0.02 && file.y > size.liptop - 0.0005 ) ) {
point.normal = { 0.f, 1.f, 0.f };
point.texture.x = m_drawing.platetexture;
}
return point; };
grooved_profile result;
auto &body { result.body };
std::vector<std::size_t> floors;
auto floortexture { 0.f };
auto floortextures { 0 };
for( std::size_t i = 0; i < count; ++i ) {
if( false == above( i ) ) {
if( height( i ) > -1e-4 ) {
floors.push_back( body.size() );
floortexture += m_profile[ i ].texture.x;
++floortextures;
}
body.push_back( m_profile[ i ] );
continue;
}
auto const first { i };
while( i + 1 < count && above( i + 1 ) ) {
++i;
}
gfx::vertex_array top, flat;
if( first > 0 ) {
if( height( first - 1 ) > -1e-4 ) {
top.push_back( m_profile[ first - 1 ] );
flat.push_back( flattened( first - 1 ) );
}
else {
top.push_back( floorpoint( first - 1, first ) );
flat.push_back( top.back() );
body.push_back( top.back() );
floors.push_back( body.size() );
body.push_back( top.back() );
}
}
for( auto k { first }; k <= i; ++k ) {
top.push_back( m_profile[ k ] );
flat.push_back( flattened( k ) );
}
if( i + 1 < count ) {
if( height( i + 1 ) > -1e-4 ) {
top.push_back( m_profile[ i + 1 ] );
flat.push_back( flattened( i + 1 ) );
}
else {
top.push_back( floorpoint( i, i + 1 ) );
flat.push_back( top.back() );
floors.push_back( body.size() );
body.push_back( top.back() );
body.push_back( top.back() );
}
}
for( auto *strip : { &top, &flat } ) {
gfx::vertex_array doubled { strip->front(), strip->front() };
for( std::size_t k = 1; k + 1 < strip->size(); ++k ) {
doubled.insert( doubled.end(), 2, ( *strip )[ k ] );
}
doubled.insert( doubled.end(), 2, strip->back() );
*strip = doubled;
}
result.tops.push_back( top );
result.flats.push_back( flat );
}
if( floors.size() > 1 ) {
auto const across { body[ floors.back() ].position - body[ floors.front() ].position };
auto up { glm::normalize( glm::vec3{ -across.y, across.x, 0.f } ) };
if( up.y < 0.f ) {
up = -up;
}
for( auto const index : floors ) {
body[ index ].normal = up;
if( floortextures > 0 ) {
body[ index ].texture.x = floortexture / floortextures;
}
}
}
result.wallu = ( floortextures > 0 ? floortexture / floortextures : -1.f );
return result;
}
std::vector<rail_cut> TTrack::rail_layer::top_cuts( gfx::vertex_array const &Top ) const {
auto const &size { m_drawing.size };
auto const reach { reaches( Top ) };
auto const head { reach.x + reach.y > 0.0 };
auto edge { head ? 1e9 : -1e9 };
for( auto const &point : Top ) {
if( point.position.y > ( head ? size.crest : size.liptop ) - 0.0005 ) {
auto const across { static_cast<double>( m_side * point.position.x - m_drawing.halfwidth ) };
edge = ( head ? std::min( edge, across ) : std::max( edge, across ) );
}
}
auto cuts { ours( m_layout.plans ).cuts };
for( auto const &band : ours( m_layout.bands ) ) {
auto cut { band_cut( band, reach ) };
if( false == band.whole && std::abs( band.facing ) >= 1e-3 && std::abs( edge ) < 1.0 ) {
auto const blunt { edge + ( head ? nose_tip : -nose_tip ) };
if( head == ( band.facing / band.into > 0.0 ) ) {
cut.start = std::max( cut.start, band.at + ( ( band.into > 0.0 ? band.to : band.from ) + band.facing * blunt ) / band.into );
cut.startcap = true;
}
else {
cut.end = std::min( cut.end, band.at + ( ( band.into > 0.0 ? band.from : band.to ) + band.facing * blunt ) / band.into );
cut.endcap = true;
}
}
cuts.push_back( cut );
}
return cuts;
}
void TTrack::rail_layer::running( rail_piece const &Whole, knots::line const &Rolled, 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 };
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 } },
[&]( double const Middle ) {
auto const rolled { knots::sample( Rolled, Middle, 0.0 ) };
return std::vector<double> { rolled - blend, rolled }; },
Ends );
}
void TTrack::rail_layer::plain( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends ) {
Whole.material = null_handle;
auto const top { lay_sides( Whole, Ends ) };
Whole.material = ( treads_marked() ? paint( Tread.material, plain_mark ) : Tread.material );
Whole.capfrom = false;
Whole.capto = false;
Whole.rolled = true;
piece( top, Whole, Ends );
}
void TTrack::rail_layer::treaded( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends ) {
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() ) ) };
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 fade { Tread.fade };
auto const centre { knots::middle( lower, upper ) };
auto const beneath { knots::shifted( lower, -fade ) };
auto const beyond { knots::shifted( upper, fade ) };
auto const lowfade { knots::bound( knots::shifted( lower, fade ), centre, true ) };
auto const highfade { knots::bound( knots::shifted( upper, -fade ), centre, false ) };
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() );
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 },
{ 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 },
{ null_handle, Whole.extents, knots::bound( Whole.inner, beyond, false ), Whole.rolled } },
[&]( 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, knots::sample( lowfade, Middle, 0.0 ), knots::sample( highfade, Middle, 0.0 ), to, to + fade }; },
Ends );
}
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 );
return strip( m_topfirst, m_toplast );
}
void TTrack::rail_layer::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 ) {
for( std::size_t i = 1; i < Splits.size(); ++i ) {
auto const middle { 0.5 * ( Splits[ i - 1 ] + Splits[ i ] ) };
auto const least { std::max( Reach.x, knots::sample( Whole.inner, middle, Reach.x ) ) };
auto const most { std::min( Reach.y, knots::sample( Whole.extents, middle, Reach.y ) ) };
auto edges { Edges( middle ) };
edges.insert( edges.begin(), -1e9 );
edges.push_back( 1e9 );
for( std::size_t k = 0; k < Bands.size(); ++k ) {
auto const &band { Bands[ k ] };
if( edges[ k + 1 ] > edges[ k ] + 1e-6 && edges[ k + 1 ] > least && edges[ k ] < most ) {
piece(
Top,
{ .material = band.material, .from = Splits[ i - 1 ], .to = Splits[ i ], .offsets = Whole.offsets, .extents = band.extents, .inner = band.inner, .rolled = band.rolled, .ramp = band.ramp, .fade = band.fade },
Ends );
}
}
}
}
void TTrack::rail_layer::piece( gfx::vertex_array const &Shape, rail_piece const &Piece, piece_ends const &Ends ) {
gfx::vertex_array vertices;
gfx::userdata_array userdata;
auto loft { span( Piece, Ends ) };
loft.userdata = ( Piece.rolled ? nullptr : &userdata );
if( Shape.size() < 2 || false == m_segment.RenderLoftSpan( vertices, m_track.m_origin, Shape, loft ) ) {
return;
}
cut_faces( vertices, Shape.size(), Piece.capfrom, Piece.capto, m_drawing.bank, sink( Piece.material, true ) );
gfx::vertex_array triangles;
gfx::userdata_array triangleuserdata;
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 ) {
for( auto const index : { 0, 1, 2, 1, 3, 2 } ) {
auto const vertex { block + 2 * j + index };
triangles.push_back( vertices[ vertex ] );
if( false == Piece.rolled ) {
triangleuserdata.push_back( userdata[ vertex ] );
}
}
}
}
if( triangles.empty() ) {
return;
}
sink( Piece.material, false == Piece.rolled ).emplace_back( GfxRenderer->Insert( triangles, triangleuserdata, m_drawing.bank, GL_TRIANGLES ) );
}
loft_span TTrack::rail_layer::span( rail_piece const &Piece, piece_ends const &Ends ) const {
loft_span result;
result.side = m_side;
result.halfwidth = m_drawing.halfwidth;
result.texturelength = texture_length( Piece.material );
result.from = Piece.from;
result.to = Piece.to;
result.offsets = Piece.offsets;
result.extents = Piece.extents;
result.inner = Piece.inner;
result.lift = Piece.lift;
result.ramp = Piece.ramp;
result.rampwidth = ( Piece.ramp.empty() ? 0.0 : Piece.fade );
result.walls = Piece.walls;
result.wallu = Piece.wallu;
if( std::abs( Piece.from - Ends.from ) < 1e-6 ) {
result.fromend = Ends.fromend;
}
if( std::abs( Piece.to - Ends.to ) < 1e-6 ) {
result.toend = Ends.toend;
}
return result;
}
double TTrack::rail_layer::texture_length( material_handle const Material ) const {
return ( Material == null_handle ? m_texturelength : m_track.texture_length( Material ) );
}
// parts no wheel rolls on go in the matte material of the cut faces
TTrack::geometryhandle_sequence &TTrack::rail_layer::sink( material_handle const Material, bool const Matte ) {
if( Material == null_handle ) {
return Matte ? m_cuts : m_geometry;
}
if( Material == m_track.m_material1 ) {
return Matte ? m_track.GeometryCut1 : m_track.Geometry1;
}
if( m_track.eType == tt_Switch && Material == m_track.m_material2 ) {
return Matte ? m_track.GeometryCut2 : m_track.Geometry2;
}
auto const tinted { std::find( m_tints.begin(), m_tints.end(), Material ) != m_tints.end() };
auto &treads { m_track.GeometryTreads };
auto found { std::find_if( treads.begin(), treads.end(), [&]( tread_geometry const &Tread ) { return Tread.material == Material; } ) };
if( found == treads.end() ) {
found = treads.insert( treads.end(), { Material, tinted ? Material : cut_material( Material ), {}, {} } );
}
return Matte && false == tinted ? found->cuts : found->geometry;
}
material_handle TTrack::rail_layer::paint( material_handle const Material, glm::vec3 const &Colour ) {
auto const tinted { tint_material( Material, Colour ) };
if( tinted != Material ) {
m_tints.push_back( tinted );
}
return tinted;
}
glm::dvec2 TTrack::rail_layer::reaches( gfx::vertex_array const &Shape ) const {
glm::dvec2 result { 1e9, -1e9 };
for( auto const &point : Shape ) {
auto const reach { static_cast<double>( m_side * point.position.x - m_drawing.halfwidth ) };
result = { std::min( result.x, reach ), std::max( result.y, reach ) };
}
return result;
}
gfx::vertex_array TTrack::rail_layer::strip( std::size_t const First, std::size_t const Last ) const {
return gfx::vertex_array( m_profile.begin() + First, m_profile.begin() + Last + 1 );
}
bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
return rail_layer( *this, Layout, Drawing, Path, Profile, Side, Texturelength, From, Openstart, Openend, Geometry, Cuts ).lay();
}