mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 17:24:53 +02:00
Merge branch 'feat/rail-crossings' into feat/switch-blades
The frogs, crossings and check rails of feat/rail-crossings next to the track editor and the switch paths made of pieces of feat/track-join-ux. The self-test commands of the crossings (frog, geomhash, frogkey, guards, railtime, crossings and switches 0|1, now called throw 0|1 because the editor already has switches) go into their own group selftest_rails.
This commit is contained in:
@@ -506,6 +506,10 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
|
||||
parallel1 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } };
|
||||
}
|
||||
parallel1 = glm::normalize( parallel1 );
|
||||
if( iSkip == 0 && m_joints[ 0 ] ) {
|
||||
pos1 = glm::vec3{ m_joints[ 0 ]->first - Origin };
|
||||
parallel1 = glm::vec3{ m_joints[ 0 ]->second };
|
||||
}
|
||||
if( iEnd == 0 )
|
||||
iEnd = iSegCount;
|
||||
fEnd = static_cast<float>(fLength * double( iEnd ) / double( iSegCount ));
|
||||
@@ -551,6 +555,10 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
|
||||
parallel2 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } };
|
||||
}
|
||||
parallel2 = glm::normalize( parallel2 );
|
||||
if( i == iSegCount && m_joints[ 1 ] ) {
|
||||
pos2 = glm::vec3{ m_joints[ 1 ]->first - Origin };
|
||||
parallel2 = glm::vec3{ m_joints[ 1 ]->second };
|
||||
}
|
||||
|
||||
// TODO: refactor the loop, there's no need to calculate starting points for each segment when we can copy the end points of the previous one
|
||||
if( Transition ) {
|
||||
@@ -625,6 +633,234 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
|
||||
return true;
|
||||
};
|
||||
|
||||
void TSegment::plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const {
|
||||
auto const t { FastGetTFromS( std::clamp( Distance, 0.0, fLength ) ) };
|
||||
Point = FastGetPoint( t );
|
||||
auto const dir { FastGetDirection( t, 0.1 / fLength ) };
|
||||
Across = glm::dvec3{ -dir.z, 0.0, dir.x };
|
||||
if( glm::length2( Across ) == 0.0 ) {
|
||||
auto const chord { FastGetPoint_1() - FastGetPoint_0() };
|
||||
Across = glm::dvec3{ -chord.z, 0.0, chord.x };
|
||||
}
|
||||
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, bool const Walls, float const Wallu, glm::dvec2 const &Bends ) const {
|
||||
|
||||
if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() )
|
||||
return false;
|
||||
|
||||
auto const sample = []( std::vector<glm::dvec2> const &Knots, double const Distance, double const Default ) {
|
||||
if( Knots.empty() )
|
||||
return Default;
|
||||
if( Distance <= Knots.front().x )
|
||||
return Knots.front().y;
|
||||
for( std::size_t i = 1; i < Knots.size(); ++i ) {
|
||||
if( Distance <= Knots[ i ].x ) {
|
||||
return glm::mix( Knots[ i - 1 ].y, Knots[ i ].y, ( Distance - Knots[ i - 1 ].x ) / std::max( Knots[ i ].x - Knots[ i - 1 ].x, 1e-6 ) );
|
||||
}
|
||||
}
|
||||
return Knots.back().y; };
|
||||
|
||||
std::vector<double> stations { From, To };
|
||||
auto const step { std::max( fStep, 0.2 ) };
|
||||
for( auto distance { std::ceil( From / step ) * step }; distance < To; distance += step ) {
|
||||
stations.push_back( distance );
|
||||
}
|
||||
for( auto const *knots : { &Offsets, &Extents, &Inner } ) {
|
||||
for( auto const &knot : *knots ) {
|
||||
if( knot.x > From && knot.x < To ) {
|
||||
stations.push_back( knot.x );
|
||||
}
|
||||
}
|
||||
}
|
||||
for( auto const *knots : { &Extents, &Inner } ) {
|
||||
for( std::size_t i = 1; i < knots->size(); ++i ) {
|
||||
auto const &a { ( *knots )[ i - 1 ] };
|
||||
auto const &b { ( *knots )[ i ] };
|
||||
if( std::abs( b.y - a.y ) < 1e-9 ) {
|
||||
continue;
|
||||
}
|
||||
for( auto const &point : ShapePoints ) {
|
||||
auto const reach { static_cast<double>( Side * point.position.x - Halfwidth ) };
|
||||
auto const t { ( reach - a.y ) / ( b.y - a.y ) };
|
||||
auto const distance { glm::mix( a.x, b.x, t ) };
|
||||
if( t > 0.0 && t < 1.0 && distance > From && distance < To ) {
|
||||
stations.push_back( distance );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort( stations.begin(), stations.end() );
|
||||
stations.erase( std::unique( stations.begin(), stations.end(), []( double const A, double const B ) { return B - A < 0.001; } ), stations.end() );
|
||||
if( Bends.x != 0.0 || Bends.y != 0.0 ) {
|
||||
auto widest { 0.0 };
|
||||
for( auto const &point : ShapePoints ) {
|
||||
widest = std::max( widest, std::abs( static_cast<double>( Side * point.position.x - Halfwidth ) ) );
|
||||
}
|
||||
stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) {
|
||||
return ( Distance > From && Distance < From + std::abs( Bends.x ) * widest + 0.001 )
|
||||
|| ( Distance < To && Distance > To - std::abs( Bends.y ) * widest - 0.001 ); } ), stations.end() );
|
||||
}
|
||||
|
||||
auto const ring = [&]( double const Distance, double const Middle, std::vector<gfx::basic_vertex> &Ring, std::vector<float> &Wear ) {
|
||||
glm::dvec3 point;
|
||||
glm::dvec3 across;
|
||||
plan_frame( Distance, point, across );
|
||||
if( auto const &joint { m_joints[ Distance < 0.001 ? 0 : 1 ] }; joint && ( Distance < 0.001 || Distance > fLength - 0.001 ) ) {
|
||||
point = joint->first;
|
||||
across = joint->second;
|
||||
}
|
||||
auto const position { glm::vec3{ point - Origin } };
|
||||
auto const parallel { glm::vec3{ across } };
|
||||
auto const offset { sample( Offsets, Distance, 0.0 ) };
|
||||
auto const extent { sample( Extents, Distance, 1e6 ) };
|
||||
auto const innerextent { sample( Inner, Distance, -1e6 ) };
|
||||
auto const middleextent { sample( Extents, Middle, 1e6 ) };
|
||||
auto const middleinner { sample( Inner, Middle, -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 ) };
|
||||
auto const bend { Distance <= From ? Bends.x : Distance >= To ? Bends.y : 0.0 };
|
||||
auto along { glm::vec3 { 0.f } };
|
||||
if( bend != 0.0 ) {
|
||||
glm::dvec3 ahead, behind, unused;
|
||||
plan_frame( std::min( Distance + 0.001, static_cast<double>( fLength ) ), ahead, unused );
|
||||
plan_frame( std::max( Distance - 0.001, 0.0 ), behind, unused );
|
||||
auto const tangent { glm::dvec3 { ahead.x - behind.x, 0.0, ahead.z - behind.z } };
|
||||
if( glm::length2( tangent ) > 0.0 ) {
|
||||
along = glm::vec3 { glm::normalize( tangent ) * bend };
|
||||
}
|
||||
}
|
||||
Ring.clear();
|
||||
Wear.clear();
|
||||
auto const count { ShapePoints.size() };
|
||||
auto const reach = [&]( std::size_t const Index ) {
|
||||
return static_cast<double>( Side * ShapePoints[ Index ].position.x - Halfwidth ); };
|
||||
auto const twin = [&]( std::size_t const A, std::size_t const B ) {
|
||||
return ShapePoints[ A ].position.x == ShapePoints[ B ].position.x && ShapePoints[ A ].position.y == ShapePoints[ B ].position.y; };
|
||||
auto const clamped = [&]( std::size_t const Index ) {
|
||||
auto const outside { reach( Index ) };
|
||||
return ( outside > middleextent ? 1 : outside < middleinner ? -1 : 0 ); };
|
||||
for( std::size_t j = 0; j < count; ++j ) {
|
||||
auto shape { ShapePoints[ j ] };
|
||||
auto outside { reach( j ) };
|
||||
auto wall { glm::vec3{ 0.f } };
|
||||
auto const side { clamped( j ) };
|
||||
if( side != 0 ) {
|
||||
auto const limit { side > 0 ? extent : innerextent };
|
||||
auto const open = [&]( std::size_t const Index ) {
|
||||
return Index < count && ( side > 0 ? reach( Index ) <= middleextent : reach( Index ) >= middleinner ); };
|
||||
auto first { j };
|
||||
auto last { j };
|
||||
if( Walls ) {
|
||||
while( first > 0 && twin( first - 1, j ) ) {
|
||||
--first;
|
||||
}
|
||||
while( last + 1 < count && twin( last + 1, j ) ) {
|
||||
++last;
|
||||
}
|
||||
}
|
||||
auto const paired { last > first && first > 0 && last + 1 < count };
|
||||
auto const before { paired ? first - 1 : j - 1 };
|
||||
auto const after { paired ? last + 1 : j + 1 };
|
||||
auto const partner { j == last ? after : before };
|
||||
auto moved { count };
|
||||
for( auto const neighbour : { paired ? partner : before, paired ? ( partner == before ? after : before ) : after } ) {
|
||||
if( false == open( neighbour ) ) {
|
||||
continue;
|
||||
}
|
||||
auto const &other { ShapePoints[ neighbour ] };
|
||||
auto const t { static_cast<float>( ( limit - reach( neighbour ) ) / ( side > 0 ? std::max( outside - reach( neighbour ), 1e-6 ) : std::min( outside - reach( neighbour ), -1e-6 ) ) ) };
|
||||
shape.position.y = glm::mix( other.position.y, shape.position.y, t );
|
||||
shape.texture.x = glm::mix( other.texture.x, shape.texture.x, t );
|
||||
auto const blend { glm::mix( other.normal, shape.normal, t ) };
|
||||
shape.normal = ( glm::length2( blend ) > 1e-6f ? blend : other.normal );
|
||||
moved = neighbour;
|
||||
break;
|
||||
}
|
||||
if( moved == count ) {
|
||||
auto top { -1e6f };
|
||||
auto bottom { 1e6f };
|
||||
auto ends { 0 };
|
||||
for( auto const step : { -1, 1 } ) {
|
||||
auto index { static_cast<std::ptrdiff_t>( j ) };
|
||||
while( index + step >= 0 && index + step < static_cast<std::ptrdiff_t>( count ) && clamped( static_cast<std::size_t>( index + step ) ) == side ) {
|
||||
index += step;
|
||||
}
|
||||
auto const neighbour { index + step };
|
||||
if( neighbour < 0 || false == open( static_cast<std::size_t>( neighbour ) ) ) {
|
||||
continue;
|
||||
}
|
||||
auto const t { static_cast<float>( ( limit - reach( static_cast<std::size_t>( neighbour ) ) ) / ( reach( static_cast<std::size_t>( index ) ) - reach( static_cast<std::size_t>( neighbour ) ) ) ) };
|
||||
auto const height { glm::mix( ShapePoints[ neighbour ].position.y, ShapePoints[ index ].position.y, t ) };
|
||||
top = std::max( top, height );
|
||||
bottom = std::min( bottom, height );
|
||||
++ends;
|
||||
}
|
||||
if( ends > 0 ) {
|
||||
shape.position.y = std::min( shape.position.y, top );
|
||||
}
|
||||
if( ends > 1 ) {
|
||||
shape.position.y = std::max( shape.position.y, bottom );
|
||||
}
|
||||
}
|
||||
outside = limit;
|
||||
auto const onwall { paired ? ( false == open( partner ) && clamped( partner ) == side ) : moved == count };
|
||||
if( onwall ) {
|
||||
wall = ( side > 0 ? outerwall : innerwall );
|
||||
}
|
||||
}
|
||||
auto pt { parallel * static_cast<float>( Side * ( Halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) };
|
||||
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;
|
||||
if( Wallu >= 0.f ) {
|
||||
shape.texture.x = Wallu;
|
||||
}
|
||||
}
|
||||
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( ( Rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f );
|
||||
}
|
||||
};
|
||||
|
||||
auto const texturelength { static_cast<float>( fTextureLength ) };
|
||||
std::vector<gfx::basic_vertex> ring1, ring2;
|
||||
std::vector<float> wear1, wear2;
|
||||
float tv1 { clamp_circular( 1.f - static_cast<float>( std::fmod( From, fTextureLength ) / fTextureLength ), 1.0f ) };
|
||||
for( std::size_t i = 1; i < stations.size(); ++i ) {
|
||||
auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) };
|
||||
ring( stations[ i - 1 ], middle, ring1, wear1 );
|
||||
ring( stations[ i ], middle, ring2, wear2 );
|
||||
tv1 = clamp_circular( tv1, 1.0f );
|
||||
auto const tv2 { tv1 - static_cast<float>( stations[ i ] - stations[ i - 1 ] ) / texturelength };
|
||||
for( std::size_t j = 0; j < ring1.size(); ++j ) {
|
||||
Output.emplace_back( ring1[ j ].position, ring1[ j ].normal, glm::vec2{ ring1[ j ].texture.x, tv1 } );
|
||||
Output.emplace_back( ring2[ j ].position, ring2[ j ].normal, glm::vec2{ ring2[ j ].texture.x, tv2 } );
|
||||
if( Userdata != nullptr ) {
|
||||
Userdata->push_back( { glm::vec4{ wear1[ j ], 0.f, 0.f, 0.f } } );
|
||||
Userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } );
|
||||
}
|
||||
}
|
||||
tv1 = tv2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TSegment::render_lines(std::vector<gfx::basic_vertex> &out, float quality) const
|
||||
{
|
||||
float step = 1.0f / iSegCount / quality;
|
||||
|
||||
Reference in New Issue
Block a user