mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 22:39:19 +02:00
feat(track): frogs and crossings for every rail profile; grooved rails share the floor of their grooves where they cross and need no check rails, the V beside the rail going through is one block where the lofts of the rails overlap; the nose of an acute frog of flat rails is filled between its legs as far as their lofts overlap, like a cast insert
This commit is contained in:
@@ -580,7 +580,7 @@ void TSegment::plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3
|
||||
Across = glm::normalize( Across );
|
||||
}
|
||||
|
||||
bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, float const Lift, std::vector<glm::dvec2> const &Inner, gfx::userdata_array *Userdata, std::vector<glm::dvec2> const &Ramp, double const Rampwidth ) const {
|
||||
bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, float const Lift, std::vector<glm::dvec2> const &Inner, gfx::userdata_array *Userdata, std::vector<glm::dvec2> const &Ramp, double const Rampwidth, bool const Walls ) const {
|
||||
|
||||
if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() )
|
||||
return false;
|
||||
@@ -642,6 +642,20 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
auto const offset { sample( Offsets, Distance, 0.0 ) };
|
||||
auto const extent { sample( Extents, Distance, 1e6 ) };
|
||||
auto const innerextent { sample( Inner, Distance, -1e6 ) };
|
||||
glm::vec3 outerwall { 0.f }, innerwall { 0.f };
|
||||
if( Walls ) {
|
||||
auto const delta { 0.001 };
|
||||
glm::dvec3 ahead, behind, unused;
|
||||
plan_frame( std::min( Distance + delta, static_cast<double>( fLength ) ), ahead, unused );
|
||||
plan_frame( std::max( Distance - delta, 0.0 ), behind, unused );
|
||||
auto tangent { glm::dvec3{ ahead.x - behind.x, 0.0, ahead.z - behind.z } };
|
||||
tangent = ( glm::length2( tangent ) > 0.0 ? glm::normalize( tangent ) : glm::dvec3{ 0.0 } );
|
||||
auto const outward { glm::dvec3{ across } * static_cast<double>( Side ) };
|
||||
auto const slope = [&]( std::vector<glm::dvec2> const &Knots, double const Default ) {
|
||||
return ( sample( Knots, Distance + delta, Default ) - sample( Knots, Distance - delta, Default ) + sample( Offsets, Distance + delta, 0.0 ) - sample( Offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); };
|
||||
outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Extents, 1e6 ) ) };
|
||||
innerwall = glm::vec3{ glm::normalize( tangent * slope( Inner, -1e6 ) - outward ) };
|
||||
}
|
||||
auto const worn { sample( Ramp, Distance, -1e6 ) };
|
||||
Ring.clear();
|
||||
Wear.clear();
|
||||
@@ -651,7 +665,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
for( std::size_t j = 0; j < count; ++j ) {
|
||||
auto shape { ShapePoints[ j ] };
|
||||
auto outside { reach( j ) };
|
||||
auto wall { glm::vec3{ 0.f } };
|
||||
if( outside > extent ) {
|
||||
wall = outerwall;
|
||||
for( auto const neighbour : { j - 1, j + 1 } ) {
|
||||
if( neighbour >= count || reach( neighbour ) > extent ) {
|
||||
continue;
|
||||
@@ -661,11 +677,13 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
shape.position.y = glm::mix( inner.position.y, shape.position.y, t );
|
||||
shape.texture.x = glm::mix( inner.texture.x, shape.texture.x, t );
|
||||
shape.normal = inner.normal;
|
||||
wall = glm::vec3{ 0.f };
|
||||
break;
|
||||
}
|
||||
outside = extent;
|
||||
}
|
||||
else if( outside < innerextent ) {
|
||||
wall = innerwall;
|
||||
for( auto const neighbour : { j - 1, j + 1 } ) {
|
||||
if( neighbour >= count || reach( neighbour ) < innerextent ) {
|
||||
continue;
|
||||
@@ -675,6 +693,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
shape.position.y = glm::mix( other.position.y, shape.position.y, t );
|
||||
shape.texture.x = glm::mix( other.texture.x, shape.texture.x, t );
|
||||
shape.normal = other.normal;
|
||||
wall = glm::vec3{ 0.f };
|
||||
break;
|
||||
}
|
||||
outside = innerextent;
|
||||
@@ -683,6 +702,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
pt.y += shape.position.y + Lift;
|
||||
auto norm { shape.normal.x * parallel };
|
||||
norm.y += shape.normal.y;
|
||||
if( glm::length2( wall ) > 0.f ) {
|
||||
norm = wall;
|
||||
}
|
||||
Ring.emplace_back( pt, glm::normalize( norm ), glm::vec2{ shape.texture.x, 0.f } );
|
||||
Wear.push_back( Rampwidth > 0.0 ? static_cast<float>( std::clamp( ( outside - worn + Rampwidth ) / Rampwidth, 0.0, 1.0 ) ) : 1.f );
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public:
|
||||
RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, bool const Transition, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, std::pair<float, float> fOffsetX = {0.f, 0.f}, glm::vec3 **p = nullptr, bool bRender = true );
|
||||
// the profile lofted from distance From to To, moved outwards by Offsets and cut on its outer side to Extents beyond the gauge line, both as (distance, value) knots
|
||||
bool
|
||||
RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents = {}, float const Lift = 0.f, std::vector<glm::dvec2> const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector<glm::dvec2> const &Ramp = {}, double const Rampwidth = 0.0 ) const;
|
||||
RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents = {}, float const Lift = 0.f, std::vector<glm::dvec2> const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector<glm::dvec2> const &Ramp = {}, double const Rampwidth = 0.0, bool const Walls = false ) const;
|
||||
// point of the path and the unit vector across it towards the right rail profile, at a distance from Point1
|
||||
void
|
||||
plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const;
|
||||
|
||||
@@ -2633,6 +2633,53 @@ TTrack::rail_dimensions() const {
|
||||
result.head = std::max( result.head, static_cast<double>( point.x ) );
|
||||
}
|
||||
}
|
||||
std::vector<std::size_t> heads, lips;
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { profile[ i ].position };
|
||||
if( point.y > top - 0.015 ) {
|
||||
( point.x < -0.02 ? lips : heads ).push_back( i );
|
||||
}
|
||||
}
|
||||
if( heads.empty() || lips.empty() ) {
|
||||
return result;
|
||||
}
|
||||
auto const first { heads.back() < lips.front() ? heads.back() : lips.back() };
|
||||
auto const last { heads.back() < lips.front() ? lips.front() : heads.front() };
|
||||
if( first >= last || ( heads.back() > lips.front() && lips.back() > heads.front() ) ) {
|
||||
return result;
|
||||
}
|
||||
auto groove { 1e9 };
|
||||
for( auto i { first + 1 }; i < last; ++i ) {
|
||||
groove = std::min( groove, static_cast<double>( profile[ i ].position.y ) );
|
||||
}
|
||||
if( groove > top ) {
|
||||
return result;
|
||||
}
|
||||
result.grooved = true;
|
||||
result.groove = groove;
|
||||
result.lip = -1e9;
|
||||
result.liptop = -1e9;
|
||||
for( auto const i : lips ) {
|
||||
result.lip = std::max( result.lip, static_cast<double>( profile[ i ].position.x ) );
|
||||
result.liptop = std::max( result.liptop, static_cast<double>( profile[ i ].position.y ) );
|
||||
}
|
||||
result.rim = 1e9;
|
||||
result.crown = -1e9;
|
||||
auto const reach = [&]( double const X ) {
|
||||
result.rim = std::min( result.rim, X );
|
||||
result.crown = std::max( result.crown, X ); };
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { profile[ i ].position };
|
||||
if( point.y > groove + 1e-4 ) {
|
||||
reach( point.x );
|
||||
}
|
||||
if( i + 1 < count ) {
|
||||
auto const &next { profile[ i + 1 ].position };
|
||||
if( ( point.y - groove ) * ( next.y - groove ) < 0.0 ) {
|
||||
reach( point.x + ( next.x - point.x ) * ( groove - point.y ) / ( next.y - point.y ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -315,6 +315,7 @@ public:
|
||||
int otherside { 0 }; // rail profile of the other rail, 1 right, -1 left
|
||||
double otherat { 0.0 }; // distance of the crossing along the other path
|
||||
bool aligned { true }; // both paths run the same way at the crossing
|
||||
TTrack *owner { nullptr };
|
||||
};
|
||||
std::vector<rail_crossing> rail_crossings( bool const Joined = true );
|
||||
// the size of the rail profile, out from the gauge face: positive outwards, the side of the head away from the track
|
||||
@@ -323,6 +324,12 @@ public:
|
||||
double height { 0.172 };
|
||||
double low { -0.0385 }; // the reach of the profile inwards
|
||||
double high { 0.1115 }; // the reach of the profile outwards
|
||||
bool grooved { false };
|
||||
double groove { 0.0 };
|
||||
double lip { 0.0 };
|
||||
double rim { 0.0 };
|
||||
double crown { 0.0 };
|
||||
double liptop { 0.0 };
|
||||
};
|
||||
rail_size rail_dimensions() const;
|
||||
// a check rail beside a rail, how far its ends turn away from the running rail
|
||||
@@ -344,6 +351,9 @@ public:
|
||||
std::vector<glm::dvec2> endrolled, startrolled; // how far out from the gauge line the wheels roll on the head, of the part coming from below and of the part going on
|
||||
glm::dvec2 endmiter { 0.0 };
|
||||
glm::dvec2 startmiter { 0.0 };
|
||||
std::vector<glm::dvec2> endinner, startinner;
|
||||
bool endcap { true };
|
||||
bool startcap { true };
|
||||
};
|
||||
struct rail_plan {
|
||||
std::vector<rail_cut> cuts;
|
||||
@@ -358,8 +368,25 @@ public:
|
||||
glm::dvec2 frommiter { 0.0 };
|
||||
glm::dvec2 tomiter { 0.0 };
|
||||
};
|
||||
struct rail_band {
|
||||
double at { 0.0 };
|
||||
double into { 0.0 };
|
||||
double facing { 0.0 };
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
bool whole { true };
|
||||
};
|
||||
struct rail_fill {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
std::vector<glm::dvec2> inner, outer;
|
||||
bool capfrom { false };
|
||||
bool capto { false };
|
||||
};
|
||||
struct rail_layout {
|
||||
rail_plan plans[ 2 ][ 2 ]; // path, right side
|
||||
std::vector<rail_band> bands[ 2 ][ 2 ];
|
||||
std::vector<rail_fill> fills[ 2 ][ 2 ];
|
||||
std::vector<guard_rail> guards[ 2 ][ 2 ];
|
||||
std::vector<glm::dvec2> wingends[ 2 ][ 2 ]; // ends of the wing rails of other rails running along each rail, and which way they run there
|
||||
check_rail_set checks;
|
||||
@@ -465,6 +492,7 @@ private:
|
||||
rail_size size;
|
||||
float halfwidth { 0.f };
|
||||
gfx::vertex_array checkshapes[ 2 ];
|
||||
gfx::vertex_array plates[ 2 ];
|
||||
};
|
||||
rail_drawing make_rail_drawing( gfx::geometrybank_handle const &Bank );
|
||||
// a rail along a path from a distance on, in parts where other rails cross it, and the check rails beside it. false: no crossing, the rail is laid whole as before
|
||||
|
||||
@@ -93,13 +93,6 @@ gauge_line make_gauge_line( TSegment const &Segment, double const Offset, double
|
||||
return line;
|
||||
}
|
||||
|
||||
// rails of the profiles the frogs, check rails and guard rails are made for; others are laid whole
|
||||
bool cut_profile( std::string Profile ) {
|
||||
|
||||
std::transform( Profile.begin(), Profile.end(), Profile.begin(), []( unsigned char const Char ) { return static_cast<char>( std::tolower( Char ) ); } );
|
||||
return Profile.empty() || Profile == "default" || Profile == "s60" || Profile == "uic60" || Profile == "s39";
|
||||
}
|
||||
|
||||
bool joined( TTrack const &Track, TTrack const &Other ) {
|
||||
|
||||
auto const next = []( TTrack const &From, TTrack const &To ) {
|
||||
@@ -181,7 +174,6 @@ bool cut_rails( TTrack const &Track ) {
|
||||
return ( Track.iCategoryFlag & 15 ) == 1
|
||||
&& Track.m_material1 != null_handle
|
||||
&& false == Track.m_editorremoved
|
||||
&& cut_profile( Track.m_profile1.first )
|
||||
&& ( Track.eType == tt_Normal ? Track.Segment != nullptr :
|
||||
Track.eType == tt_Switch && Track.SwitchExtension && Track.SwitchExtension->m_frogs && Track.SwitchExtension->Segments[ 0 ] && Track.SwitchExtension->Segments[ 1 ] );
|
||||
}
|
||||
@@ -576,6 +568,7 @@ std::vector<TTrack::rail_crossing> TTrack::rail_crossings( bool const Joined ) {
|
||||
auto mapped { crossing };
|
||||
mapped.path = path;
|
||||
mapped.at = ( end ? length + along : -along );
|
||||
mapped.owner = neighbour;
|
||||
if( false == same ) {
|
||||
mapped.side = -crossing.side;
|
||||
mapped.into = -crossing.into;
|
||||
@@ -606,12 +599,15 @@ TTrack::check_rail_set TTrack::check_rails( std::vector<rail_crossing> const &Cr
|
||||
std::vector<planned> plans[ 2 ][ 2 ];
|
||||
check_rail_set result;
|
||||
auto const size { rail_dimensions() };
|
||||
if( size.grooved ) {
|
||||
return result;
|
||||
}
|
||||
auto const acute = [&]( std::vector<planned> &Other, double const At, double const Toward, double const Slope, double const Flare ) {
|
||||
auto const half { 0.5 * check_length( Slope ) };
|
||||
Other.push_back( { At + Toward * 0.25 - half, At + Toward * 0.25 + half, true, true, 1.5, Flare } );
|
||||
};
|
||||
for( auto const &crossing : Crossings ) {
|
||||
if( crossing.angle > middle_angle ) {
|
||||
if( crossing.angle > middle_angle || crossing.track->rail_dimensions().grooved ) {
|
||||
continue;
|
||||
}
|
||||
if( crossing.facing < 0.0 ) {
|
||||
@@ -924,6 +920,43 @@ bool goes_through( TTrack const &Track, TTrack const &Other ) {
|
||||
return ( here.x != there.x ? here.x < there.x : here.z < there.z );
|
||||
}
|
||||
|
||||
TTrack::rail_band groove_band( double const At, double const Into, double const Facing, bool const Through, TTrack::rail_size const &Other ) {
|
||||
|
||||
auto const seam { 0.001 };
|
||||
TTrack::rail_band band { At, Into, Facing };
|
||||
band.whole = ( false == Through );
|
||||
band.from = ( Through ? 0.0 : seam - std::max( Other.head, Other.crown ) );
|
||||
band.to = ( Through ? ( Other.grooved ? -Other.lip : flangeway ) : -Other.rim - seam );
|
||||
return band;
|
||||
}
|
||||
|
||||
void vee_fill( std::vector<TTrack::rail_fill> &Fills, double const At, double const Into, double const Facing, TTrack::rail_size const &Own, TTrack::rail_size const &Other, bool const Outwards ) {
|
||||
|
||||
auto const seam { 0.001 };
|
||||
auto const sign { Outwards ? 1.0 : -1.0 };
|
||||
auto const edge { Outwards ? std::max( Own.head, Own.crown ) : Own.rim };
|
||||
auto const inside { Outwards == ( Facing > 0.0 ) };
|
||||
auto const closest { inside ? -Other.rim : -std::max( Other.head, Other.crown ) };
|
||||
auto const own { Outwards ? Own.high - edge : edge - Own.low };
|
||||
auto const other { inside ? Other.rim - Other.low : Other.high - std::max( Other.head, Other.crown ) };
|
||||
auto const gap { own + other / std::max( std::abs( Facing ), 0.1 ) };
|
||||
auto const meets = [&]( double const Width ) {
|
||||
return At + ( closest + Facing * ( edge + sign * Width ) ) / Into; };
|
||||
auto const apex { meets( 0.0 ) };
|
||||
auto const end { meets( gap ) };
|
||||
TTrack::rail_fill fill;
|
||||
fill.from = std::min( apex, end );
|
||||
fill.to = std::max( apex, end );
|
||||
fill.capfrom = ( end < apex );
|
||||
fill.capto = ( end > apex );
|
||||
std::vector<glm::dvec2> line { { apex, edge + sign * seam }, { end, edge + sign * ( gap + seam ) } };
|
||||
std::sort( line.begin(), line.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } );
|
||||
std::vector<glm::dvec2> fixed { { fill.from, edge - sign * seam }, { fill.to, edge - sign * seam } };
|
||||
fill.outer = ( Outwards ? line : fixed );
|
||||
fill.inner = ( Outwards ? fixed : line );
|
||||
Fills.push_back( fill );
|
||||
}
|
||||
|
||||
// crossings at a middle angle: guard rails along the sides of the quadrilateral between the tracks, glued at its corners to the guard rails of the other track
|
||||
struct frame_corner {
|
||||
TTrack *track { nullptr };
|
||||
@@ -956,6 +989,19 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
|
||||
if( Crossings ) {
|
||||
for( auto const &crossing : crossings ) {
|
||||
auto &plan { plans[ crossing.path ][ crossing.side > 0 ] };
|
||||
if( std::find( layout.crossed.begin(), layout.crossed.end(), crossing.track ) == layout.crossed.end() ) {
|
||||
layout.crossed.push_back( crossing.track );
|
||||
}
|
||||
auto const theirs { crossing.track->rail_dimensions() };
|
||||
if( size.grooved || theirs.grooved ) {
|
||||
auto const through { size.grooved != theirs.grooved ? size.grooved : goes_through( crossing.owner ? *crossing.owner : *this, *crossing.track ) };
|
||||
layout.bands[ crossing.path ][ crossing.side > 0 ].push_back( groove_band( crossing.at, crossing.into, crossing.facing, through, theirs ) );
|
||||
if( through && size.grooved && theirs.grooved && crossing.angle <= middle_angle ) {
|
||||
vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, theirs, true );
|
||||
vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, theirs, false );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if( crossing.angle > middle_angle && crossing.angle <= wide_angle ) {
|
||||
corners.push_back( { crossing.track, crossing.path, crossing.side, crossing.otherpath, crossing.at, crossing.into, crossing.facing } );
|
||||
}
|
||||
@@ -987,13 +1033,13 @@ 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 );
|
||||
if( crossing.nose ) {
|
||||
vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, crossing.track->rail_dimensions(), true );
|
||||
}
|
||||
}
|
||||
else {
|
||||
obtuse( plan, crossing.at, crossing.into, size );
|
||||
}
|
||||
if( std::find( layout.crossed.begin(), layout.crossed.end(), crossing.track ) == layout.crossed.end() ) {
|
||||
layout.crossed.push_back( crossing.track );
|
||||
}
|
||||
}
|
||||
for( auto &pathguards : guards ) {
|
||||
std::vector<glm::dvec2> spans[ 2 ];
|
||||
@@ -1078,11 +1124,32 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) {
|
||||
if( eType == tt_Switch ) {
|
||||
auto const frog { this->frog() };
|
||||
if( frog.valid ) {
|
||||
glm::dvec3 point, tangents[ 2 ];
|
||||
glm::dvec2 normals[ 2 ];
|
||||
for( int path = 0; path < 2; ++path ) {
|
||||
auto const &line { rail_line( *this, path, frog.side[ path ] > 0, false ) };
|
||||
line.at( frog.at[ path ], point, tangents[ path ] );
|
||||
normals[ path ] = glm::normalize( plan( line.normal( frog.at[ path ], frog.side[ path ] ) ) );
|
||||
}
|
||||
auto const facing { glm::dot( normals[ 0 ], normals[ 1 ] ) };
|
||||
auto const slope { std::tan( frog.angle ) };
|
||||
for( int path = 0; path < 2; ++path ) {
|
||||
auto const into { glm::dot( glm::normalize( plan( tangents[ path ] ) ), normals[ 1 - path ] ) };
|
||||
auto &fills { layout.fills[ path ][ frog.side[ path ] > 0 ] };
|
||||
if( size.grooved ) {
|
||||
layout.bands[ path ][ frog.side[ path ] > 0 ].push_back( groove_band( frog.at[ path ], into, facing, path == 0, size ) );
|
||||
if( path == 0 ) {
|
||||
vee_fill( fills, frog.at[ path ], into, facing, size, size, true );
|
||||
vee_fill( fills, frog.at[ path ], into, facing, size, size, false );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
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 );
|
||||
if( path == 0 ) {
|
||||
vee_fill( fills, frog.at[ path ], into, facing, size, size, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1114,6 +1181,30 @@ TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const &
|
||||
for( auto &shape : drawing.checkshapes ) {
|
||||
shape.resize( checkshape.size() );
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
auto const top { static_cast<float>( ( drawing.size.grooved ? drawing.size.liptop : highest ) - 0.0005 ) };
|
||||
auto const bottom { static_cast<float>( -drawing.size.height ) };
|
||||
gfx::vertex_array plate {
|
||||
{{ 1.f, bottom, 0.f }, { 1.f, 0.f, 0.f }, { texture, 0.f }},
|
||||
{{ 1.f, top, 0.f }, { 1.f, 0.f, 0.f }, { texture, 0.f }},
|
||||
{{ 1.f, top, 0.f }, { 0.f, 1.f, 0.f }, { texture, 0.f }},
|
||||
{{ -1.f, top, 0.f }, { 0.f, 1.f, 0.f }, { texture, 0.f }},
|
||||
{{ -1.f, top, 0.f }, { -1.f, 0.f, 0.f }, { texture, 0.f }},
|
||||
{{ -1.f, bottom, 0.f }, { -1.f, 0.f, 0.f }, { texture, 0.f }} };
|
||||
create_track_rail_profile( drawing.plates[ 0 ], drawing.plates[ 1 ], &plate );
|
||||
for( auto &shape : drawing.plates ) {
|
||||
shape.resize( plate.size() );
|
||||
}
|
||||
}
|
||||
return drawing;
|
||||
}
|
||||
|
||||
@@ -1128,10 +1219,10 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
|
||||
auto const &plan { Layout.plans[ Path ][ Side > 0 ] };
|
||||
auto const length { segment.GetLength() };
|
||||
// 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 = {} ) {
|
||||
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 ) {
|
||||
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, 0.f, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : blend ) ) {
|
||||
if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, Texturelength, Start, End, Offsets, Extents, 0.f, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : blend, Walls ) ) {
|
||||
cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, Cuts );
|
||||
gfx::vertex_array triangles;
|
||||
gfx::userdata_array triangleuserdata;
|
||||
@@ -1160,7 +1251,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
|
||||
}
|
||||
}
|
||||
return Knots.back().y; };
|
||||
auto const lowest = [&]( std::vector<glm::dvec2> const &A, std::vector<glm::dvec2> const &B ) {
|
||||
auto const bound = [&]( std::vector<glm::dvec2> const &A, std::vector<glm::dvec2> const &B, bool const Lower ) {
|
||||
if( A.empty() || B.empty() ) {
|
||||
return ( A.empty() ? B : A );
|
||||
}
|
||||
@@ -1184,7 +1275,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
|
||||
std::sort( stations.begin(), stations.end() );
|
||||
std::vector<glm::dvec2> result;
|
||||
for( auto const station : stations ) {
|
||||
result.emplace_back( station, std::min( level( A, station ), level( B, station ) ) );
|
||||
result.emplace_back( station, Lower ? std::min( level( A, station ), level( B, station ) ) : std::max( level( A, station ), level( B, station ) ) );
|
||||
}
|
||||
return result; };
|
||||
// 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
|
||||
@@ -1198,13 +1289,13 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
|
||||
}
|
||||
auto const strip = [&]( std::size_t const First, std::size_t const Last ) {
|
||||
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 &Rolled, bool const Capstart, bool const Capend ) {
|
||||
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 ) ) {
|
||||
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 );
|
||||
}
|
||||
piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, {} );
|
||||
piece( Start, End, Offsets, Extents, false, false, strip( toplast, Profile.size() - 1 ), 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 );
|
||||
auto const top { strip( topfirst, toplast ) };
|
||||
auto low { 1e9 };
|
||||
auto high { -1e9 };
|
||||
@@ -1230,18 +1321,18 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
|
||||
}
|
||||
}
|
||||
std::sort( splits.begin(), splits.end() );
|
||||
auto const shiny { lowest( Extents, inner ) };
|
||||
auto const blended { lowest( Extents, Rolled ) };
|
||||
auto const shiny { bound( Extents, inner, true ) };
|
||||
auto const blended { bound( Extents, Rolled, true ) };
|
||||
for( std::size_t i = 1; i < splits.size(); ++i ) {
|
||||
auto const reach { level( Rolled, 0.5 * ( splits[ i - 1 ] + splits[ i ] ) ) };
|
||||
if( reach - blend > low ) {
|
||||
piece( splits[ i - 1 ], splits[ i ], Offsets, shiny, false, false, top, true, {} );
|
||||
piece( splits[ i - 1 ], splits[ i ], Offsets, shiny, false, false, top, true, Inner );
|
||||
}
|
||||
if( reach > low && reach - blend < high ) {
|
||||
piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, inner, Rolled );
|
||||
piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, bound( inner, Inner, false ), Rolled );
|
||||
}
|
||||
if( reach < high ) {
|
||||
piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, Rolled );
|
||||
piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, bound( Rolled, Inner, false ) );
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1346,44 +1437,187 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
|
||||
}
|
||||
piece( a, b, offsets, {}, a > 0.0 || from >= 0.0, to <= length, shape, false, {} );
|
||||
}
|
||||
if( plan.cuts.empty() ) {
|
||||
for( auto const &fill : Layout.fills[ Path ][ Side > 0 ] ) {
|
||||
auto const a { std::max( fill.from, From ) };
|
||||
auto const b { std::min( fill.to, length ) };
|
||||
if( b - a > 0.01 ) {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
auto const &bands { Layout.bands[ Path ][ Side > 0 ] };
|
||||
if( plan.cuts.empty() && bands.empty() ) {
|
||||
return false;
|
||||
}
|
||||
auto cuts { plan.cuts };
|
||||
std::sort( cuts.begin(), cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } );
|
||||
auto const byposition = []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; };
|
||||
auto start { -1e9 };
|
||||
std::vector<glm::dvec2> rolledfrom;
|
||||
std::vector<glm::dvec2> offsets, extents;
|
||||
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 };
|
||||
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() );
|
||||
auto const reaches = [&]( gfx::vertex_array const &Shape ) {
|
||||
glm::dvec2 result { 1e9, -1e9 };
|
||||
for( auto const &point : Shape ) {
|
||||
auto const reach { static_cast<double>( Side * point.position.x - halfwidth ) };
|
||||
result = { std::min( result.x, reach ), std::max( result.y, reach ) };
|
||||
}
|
||||
std::sort( pieceoffsets.begin(), pieceoffsets.end(), byposition );
|
||||
std::sort( pieceextents.begin(), pieceextents.end(), byposition );
|
||||
auto const a { std::max( start, From ) };
|
||||
auto const b { std::min( end, length ) };
|
||||
auto const rolled { i < cuts.size() ? lowest( rolledfrom, cuts[ i ].endrolled ) : rolledfrom };
|
||||
auto const capstart { start > From ? true : Openstart };
|
||||
auto const capend { end < length ? true : Openend };
|
||||
if( b - a > 0.01 ) {
|
||||
if( rolled.empty() || topfirst >= toplast ) {
|
||||
piece( a, b, pieceoffsets, pieceextents, capstart, capend, Profile, true, {} );
|
||||
return result; };
|
||||
auto const 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 Reach ) {
|
||||
return glm::dvec2{ Band.at + ( Edge + Band.facing * Reach ) / Band.into, Reach }; };
|
||||
if( std::abs( Band.facing ) < 1e-3 ) {
|
||||
cut.end = meets( before, 0.0 ).x;
|
||||
cut.start = meets( after, 0.0 ).x;
|
||||
return cut;
|
||||
}
|
||||
std::vector<glm::dvec2> endknots { meets( before, Reach.x ), meets( before, Reach.y ) };
|
||||
std::vector<glm::dvec2> startknots { meets( after, Reach.x ), meets( after, Reach.y ) };
|
||||
std::sort( endknots.begin(), endknots.end(), byposition );
|
||||
std::sort( startknots.begin(), startknots.end(), byposition );
|
||||
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; };
|
||||
auto const lay = [&]( gfx::vertex_array const &Shape, std::vector<rail_cut> cuts, bool const Wheels ) {
|
||||
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 };
|
||||
std::vector<glm::dvec2> rolledfrom;
|
||||
std::vector<glm::dvec2> 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() );
|
||||
}
|
||||
std::sort( pieceoffsets.begin(), pieceoffsets.end(), byposition );
|
||||
std::sort( pieceextents.begin(), pieceextents.end(), byposition );
|
||||
std::sort( pieceinner.begin(), pieceinner.end(), byposition );
|
||||
auto const a { std::max( start, From ) };
|
||||
auto const b { std::min( end, length ) };
|
||||
auto const rolled { i < cuts.size() ? bound( rolledfrom, cuts[ i ].endrolled, true ) : rolledfrom };
|
||||
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 );
|
||||
}
|
||||
else {
|
||||
running( a, b, pieceoffsets, pieceextents, pieceinner, rolled, capstart, capend );
|
||||
}
|
||||
}
|
||||
if( i < cuts.size() ) {
|
||||
rolledfrom = cuts[ i ].startrolled;
|
||||
start = cuts[ i ].start;
|
||||
startcap = cuts[ i ].startcap;
|
||||
offsets = cuts[ i ].startoffsets;
|
||||
extents = cuts[ i ].startextents;
|
||||
inner = cuts[ i ].startinner;
|
||||
}
|
||||
}
|
||||
};
|
||||
auto const &outline { track_rail_profile( m_profile1.second ) };
|
||||
auto const count { Profile.size() };
|
||||
if( false == size.grooved || outline.size() != 2 * count ) {
|
||||
auto cuts { plan.cuts };
|
||||
auto const reach { reaches( Profile ) };
|
||||
for( auto const &band : bands ) {
|
||||
cuts.push_back( band_cut( band, reach ) );
|
||||
}
|
||||
lay( Profile, cuts, true );
|
||||
return true;
|
||||
}
|
||||
auto const height = [&]( std::size_t const Index ) {
|
||||
return static_cast<double>( outline[ 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 { Profile[ above( A ) ? A : B ] };
|
||||
point.position = glm::mix( Profile[ A ].position, Profile[ B ].position, t );
|
||||
point.texture = glm::mix( Profile[ A ].texture, Profile[ B ].texture, t );
|
||||
return point; };
|
||||
std::vector<gfx::vertex_array> tops;
|
||||
gfx::vertex_array 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 += Profile[ i ].texture.x;
|
||||
++floortextures;
|
||||
}
|
||||
body.push_back( Profile[ i ] );
|
||||
continue;
|
||||
}
|
||||
auto const first { i };
|
||||
while( i + 1 < count && above( i + 1 ) ) {
|
||||
++i;
|
||||
}
|
||||
gfx::vertex_array top;
|
||||
if( first > 0 ) {
|
||||
if( height( first - 1 ) > -1e-4 ) {
|
||||
top.push_back( Profile[ first - 1 ] );
|
||||
}
|
||||
else {
|
||||
running( a, b, pieceoffsets, pieceextents, rolled, capstart, capend );
|
||||
top.push_back( floorpoint( first - 1, first ) );
|
||||
body.push_back( top.back() );
|
||||
floors.push_back( body.size() );
|
||||
body.push_back( top.back() );
|
||||
}
|
||||
}
|
||||
if( i < cuts.size() ) {
|
||||
rolledfrom = cuts[ i ].startrolled;
|
||||
start = cuts[ i ].start;
|
||||
offsets = cuts[ i ].startoffsets;
|
||||
extents = cuts[ i ].startextents;
|
||||
top.insert( top.end(), Profile.begin() + first, Profile.begin() + i + 1 );
|
||||
if( i + 1 < count ) {
|
||||
if( height( i + 1 ) > -1e-4 ) {
|
||||
top.push_back( Profile[ i + 1 ] );
|
||||
}
|
||||
else {
|
||||
top.push_back( floorpoint( i, i + 1 ) );
|
||||
floors.push_back( body.size() );
|
||||
body.push_back( top.back() );
|
||||
body.push_back( top.back() );
|
||||
}
|
||||
}
|
||||
top.insert( top.begin(), top.front() );
|
||||
top.push_back( top.back() );
|
||||
tops.push_back( top );
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<rail_cut> wholecuts { plan.cuts };
|
||||
auto const bodyreach { reaches( body ) };
|
||||
for( auto const &band : bands ) {
|
||||
if( band.whole ) {
|
||||
wholecuts.push_back( band_cut( band, bodyreach ) );
|
||||
}
|
||||
}
|
||||
lay( body, wholecuts, false );
|
||||
for( auto const &top : tops ) {
|
||||
auto const reach { reaches( top ) };
|
||||
std::vector<rail_cut> cuts { plan.cuts };
|
||||
for( auto const &band : bands ) {
|
||||
cuts.push_back( band_cut( band, reach ) );
|
||||
}
|
||||
lay( top, cuts, false );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user