17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-06 10:28:41 +01:00

refactor(track): a lofted rail span is described by one loft_span value instead of twenty arguments, and the knot line helpers (sample, bound, meetings, middle, shifted) live once in world/Knots.h instead of in lambdas of the segment and of lay_rail

This commit is contained in:
maj00r
2026-10-14 09:16:37 +02:00
parent 9a8cc08494
commit 347d9035e0
4 changed files with 243 additions and 167 deletions

110
world/Knots.h Normal file
View File

@@ -0,0 +1,110 @@
/*
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/.
*/
#pragma once
#include <algorithm>
#include <vector>
#include <glm/glm.hpp>
namespace knots {
using line = std::vector<glm::dvec2>;
inline bool by_position( glm::dvec2 const &A, glm::dvec2 const &B ) {
return A.x < B.x;
}
inline void sort( line &Line ) {
std::sort( Line.begin(), Line.end(), by_position );
}
inline double sample( line const &Line, double const Position, double const Default ) {
if( Line.empty() ) {
return Default;
}
if( Position <= Line.front().x ) {
return Line.front().y;
}
for( std::size_t i = 1; i < Line.size(); ++i ) {
if( Position <= Line[ i ].x ) {
return glm::mix( Line[ i - 1 ].y, Line[ i ].y, ( Position - Line[ i - 1 ].x ) / std::max( Line[ i ].x - Line[ i - 1 ].x, 1e-6 ) );
}
}
return Line.back().y;
}
inline line shifted( line Line, double const Value ) {
for( auto &knot : Line ) {
knot.y += Value;
}
return Line;
}
inline std::vector<double> stations( line const &A, line const &B ) {
std::vector<double> result;
for( auto const *each : { &A, &B } ) {
for( auto const &knot : *each ) {
result.push_back( knot.x );
}
}
std::sort( result.begin(), result.end() );
return result;
}
inline std::vector<double> meetings( line const &A, line const &B ) {
std::vector<double> result;
if( A.empty() || B.empty() ) {
return result;
}
auto const all { stations( A, B ) };
for( std::size_t i = 1; i < all.size(); ++i ) {
auto const before { sample( A, all[ i - 1 ], 0.0 ) - sample( B, all[ i - 1 ], 0.0 ) };
auto const after { sample( A, all[ i ], 0.0 ) - sample( B, all[ i ], 0.0 ) };
if( before * after < 0.0 ) {
result.push_back( all[ i - 1 ] + ( all[ i ] - all[ i - 1 ] ) * before / ( before - after ) );
}
}
return result;
}
inline line bound( line const &A, line const &B, bool const Lower ) {
if( A.empty() || B.empty() ) {
return ( A.empty() ? B : A );
}
auto all { stations( A, B ) };
auto const crossed { meetings( A, B ) };
all.insert( all.end(), crossed.begin(), crossed.end() );
std::sort( all.begin(), all.end() );
line result;
for( auto const position : all ) {
auto const a { sample( A, position, 0.0 ) };
auto const b { sample( B, position, 0.0 ) };
result.emplace_back( position, Lower ? std::min( a, b ) : std::max( a, b ) );
}
return result;
}
inline line middle( line const &A, line const &B ) {
line result;
for( auto const position : stations( A, B ) ) {
result.emplace_back( position, 0.5 * ( sample( A, position, 0.0 ) + sample( B, position, 0.0 ) ) );
}
return result;
}
} // namespace knots

View File

@@ -580,47 +580,35 @@ void TSegment::plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3
Across = glm::normalize( Across ); 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, std::vector<glm::dvec2> const &Fitfrom, std::vector<glm::dvec2> const &Fitto ) const { bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, loft_span const &Span ) const {
if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() ) if( fTsBuffer.empty() || Span.to - Span.from < 0.001 || ShapePoints.empty() )
return false; return false;
auto const sample = []( std::vector<glm::dvec2> const &Knots, double const Distance, double const Default ) { std::vector<double> stations { Span.from, Span.to };
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 ) }; auto const step { std::max( fStep, 0.2 ) };
for( auto distance { std::ceil( From / step ) * step }; distance < To; distance += step ) { for( auto distance { std::ceil( Span.from / step ) * step }; distance < Span.to; distance += step ) {
stations.push_back( distance ); stations.push_back( distance );
} }
for( auto const *knots : { &Offsets, &Extents, &Inner } ) { for( auto const *line : { &Span.offsets, &Span.extents, &Span.inner } ) {
for( auto const &knot : *knots ) { for( auto const &knot : *line ) {
if( knot.x > From && knot.x < To ) { if( knot.x > Span.from && knot.x < Span.to ) {
stations.push_back( knot.x ); stations.push_back( knot.x );
} }
} }
} }
for( auto const *knots : { &Extents, &Inner } ) { for( auto const *line : { &Span.extents, &Span.inner } ) {
for( std::size_t i = 1; i < knots->size(); ++i ) { for( std::size_t i = 1; i < line->size(); ++i ) {
auto const &a { ( *knots )[ i - 1 ] }; auto const &a { ( *line )[ i - 1 ] };
auto const &b { ( *knots )[ i ] }; auto const &b { ( *line )[ i ] };
if( std::abs( b.y - a.y ) < 1e-9 ) { if( std::abs( b.y - a.y ) < 1e-9 ) {
continue; continue;
} }
for( auto const &point : ShapePoints ) { for( auto const &point : ShapePoints ) {
auto const reach { static_cast<double>( Side * point.position.x - Halfwidth ) }; auto const reach { static_cast<double>( Span.side * point.position.x - Span.halfwidth ) };
auto const t { ( reach - a.y ) / ( b.y - a.y ) }; auto const t { ( reach - a.y ) / ( b.y - a.y ) };
auto const distance { glm::mix( a.x, b.x, t ) }; auto const distance { glm::mix( a.x, b.x, t ) };
if( t > 0.0 && t < 1.0 && distance > From && distance < To ) { if( t > 0.0 && t < 1.0 && distance > Span.from && distance < Span.to ) {
stations.push_back( distance ); stations.push_back( distance );
} }
} }
@@ -628,26 +616,26 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
} }
std::sort( stations.begin(), stations.end() ); 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() ); 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 || false == Fitfrom.empty() || false == Fitto.empty() ) { if( Span.fromend.bend != 0.0 || Span.toend.bend != 0.0 || false == Span.fromend.fit.empty() || false == Span.toend.fit.empty() ) {
auto widest { 0.0 }; auto widest { 0.0 };
for( auto const &point : ShapePoints ) { for( auto const &point : ShapePoints ) {
widest = std::max( widest, std::abs( static_cast<double>( Side * point.position.x - Halfwidth ) ) ); widest = std::max( widest, std::abs( static_cast<double>( Span.side * point.position.x - Span.halfwidth ) ) );
} }
auto offsetmost { 0.0 }; auto offsetmost { 0.0 };
for( auto const &knot : Offsets ) { for( auto const &knot : Span.offsets ) {
offsetmost = std::max( offsetmost, std::abs( knot.y ) ); offsetmost = std::max( offsetmost, std::abs( knot.y ) );
} }
auto const margin = [&]( double const Bend, std::vector<glm::dvec2> const &Fit ) { auto const margin = [&]( loft_end const &End ) {
auto fitmost { 0.0 }; auto fitmost { 0.0 };
for( auto const &knot : Fit ) { for( auto const &knot : End.fit ) {
fitmost = std::max( fitmost, std::abs( knot.y ) ); fitmost = std::max( fitmost, std::abs( knot.y ) );
} }
return std::abs( Bend ) * ( Fit.empty() ? widest : widest + offsetmost ) + fitmost + 0.001; }; return std::abs( End.bend ) * ( End.fit.empty() ? widest : widest + offsetmost ) + fitmost + 0.001; };
auto const frommargin { margin( Bends.x, Fitfrom ) }; auto const frommargin { margin( Span.fromend ) };
auto const tomargin { margin( Bends.y, Fitto ) }; auto const tomargin { margin( Span.toend ) };
stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) { stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) {
return ( Distance > From && Distance < To ) return ( Distance > Span.from && Distance < Span.to )
&& ( Distance < From + frommargin || Distance > To - tomargin ); } ), stations.end() ); && ( Distance < Span.from + frommargin || Distance > Span.to - tomargin ); } ), stations.end() );
} }
auto const ring = [&]( double const Distance, double const Middle, std::vector<gfx::basic_vertex> &Ring, std::vector<float> &Wear ) { auto const ring = [&]( double const Distance, double const Middle, std::vector<gfx::basic_vertex> &Ring, std::vector<float> &Wear ) {
@@ -660,29 +648,30 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
} }
auto const position { glm::vec3{ point - Origin } }; auto const position { glm::vec3{ point - Origin } };
auto const parallel { glm::vec3{ across } }; auto const parallel { glm::vec3{ across } };
auto const offset { sample( Offsets, Distance, 0.0 ) }; auto const offset { knots::sample( Span.offsets, Distance, 0.0 ) };
auto const extent { sample( Extents, Distance, 1e6 ) }; auto const extent { knots::sample( Span.extents, Distance, 1e6 ) };
auto const innerextent { sample( Inner, Distance, -1e6 ) }; auto const innerextent { knots::sample( Span.inner, Distance, -1e6 ) };
auto const middleextent { sample( Extents, Middle, 1e6 ) }; auto const middleextent { knots::sample( Span.extents, Middle, 1e6 ) };
auto const middleinner { sample( Inner, Middle, -1e6 ) }; auto const middleinner { knots::sample( Span.inner, Middle, -1e6 ) };
glm::vec3 outerwall { 0.f }, innerwall { 0.f }; glm::vec3 outerwall { 0.f }, innerwall { 0.f };
if( Walls ) { if( Span.walls ) {
auto const delta { 0.001 }; auto const delta { 0.001 };
glm::dvec3 ahead, behind, unused; glm::dvec3 ahead, behind, unused;
plan_frame( std::min( Distance + delta, static_cast<double>( fLength ) ), ahead, unused ); plan_frame( std::min( Distance + delta, static_cast<double>( fLength ) ), ahead, unused );
plan_frame( std::max( Distance - delta, 0.0 ), behind, 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 } }; 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 } ); 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 outward { glm::dvec3{ across } * static_cast<double>( Span.side ) };
auto const slope = [&]( std::vector<glm::dvec2> const &Knots, double const Default ) { auto const slope = [&]( knots::line 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 ); }; return ( knots::sample( Knots, Distance + delta, Default ) - knots::sample( Knots, Distance - delta, Default ) + knots::sample( Span.offsets, Distance + delta, 0.0 ) - knots::sample( Span.offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); };
outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Extents, 1e6 ) ) }; outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Span.extents, 1e6 ) ) };
innerwall = glm::vec3{ glm::normalize( tangent * slope( Inner, -1e6 ) - outward ) }; innerwall = glm::vec3{ glm::normalize( tangent * slope( Span.inner, -1e6 ) - outward ) };
} }
auto const worn { sample( Ramp, Distance, -1e6 ) }; auto const worn { knots::sample( Span.ramp, Distance, -1e6 ) };
auto const bend { Distance <= From ? Bends.x : Distance >= To ? Bends.y : 0.0 }; static loft_end const unshaped;
static std::vector<glm::dvec2> const unfitted; auto const &shaped { Distance <= Span.from ? Span.fromend : Distance >= Span.to ? Span.toend : unshaped };
auto const &fit { Distance <= From ? Fitfrom : Distance >= To ? Fitto : unfitted }; auto const bend { shaped.bend };
auto const &fit { shaped.fit };
auto along { glm::vec3 { 0.f } }; auto along { glm::vec3 { 0.f } };
auto forward { glm::vec3 { 0.f } }; auto forward { glm::vec3 { 0.f } };
if( bend != 0.0 || false == fit.empty() ) { if( bend != 0.0 || false == fit.empty() ) {
@@ -699,7 +688,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
Wear.clear(); Wear.clear();
auto const count { ShapePoints.size() }; auto const count { ShapePoints.size() };
auto const reach = [&]( std::size_t const Index ) { auto const reach = [&]( std::size_t const Index ) {
return static_cast<double>( Side * ShapePoints[ Index ].position.x - Halfwidth ); }; return static_cast<double>( Span.side * ShapePoints[ Index ].position.x - Span.halfwidth ); };
auto const twin = [&]( std::size_t const A, std::size_t const B ) { 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; }; 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 clamped = [&]( std::size_t const Index ) {
@@ -716,7 +705,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
return Index < count && ( side > 0 ? reach( Index ) <= middleextent : reach( Index ) >= middleinner ); }; return Index < count && ( side > 0 ? reach( Index ) <= middleextent : reach( Index ) >= middleinner ); };
auto first { j }; auto first { j };
auto last { j }; auto last { j };
if( Walls ) { if( Span.walls ) {
while( first > 0 && twin( first - 1, j ) ) { while( first > 0 && twin( first - 1, j ) ) {
--first; --first;
} }
@@ -774,25 +763,25 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
wall = ( side > 0 ? outerwall : innerwall ); wall = ( side > 0 ? outerwall : innerwall );
} }
} }
auto pt { parallel * static_cast<float>( Side * ( Halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) + forward * static_cast<float>( sample( fit, shape.position.y, 0.0 ) ) }; auto pt { parallel * static_cast<float>( Span.side * ( Span.halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) + forward * static_cast<float>( knots::sample( fit, shape.position.y, 0.0 ) ) };
pt.y += shape.position.y + Lift; pt.y += shape.position.y + Span.lift;
auto norm { shape.normal.x * parallel }; auto norm { shape.normal.x * parallel };
norm.y += shape.normal.y; norm.y += shape.normal.y;
if( glm::length2( wall ) > 0.f ) { if( glm::length2( wall ) > 0.f ) {
norm = wall; norm = wall;
if( Wallu >= 0.f ) { if( Span.wallu >= 0.f ) {
shape.texture.x = Wallu; shape.texture.x = Span.wallu;
} }
} }
Ring.emplace_back( pt, glm::normalize( norm ), glm::vec2{ shape.texture.x, 0.f } ); 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 ); Wear.push_back( Span.rampwidth != 0.0 ? static_cast<float>( std::clamp( ( Span.rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Span.rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f );
} }
}; };
auto const texturelength { static_cast<float>( fTextureLength ) }; auto const texturelength { static_cast<float>( Span.texturelength ) };
std::vector<gfx::basic_vertex> ring1, ring2; std::vector<gfx::basic_vertex> ring1, ring2;
std::vector<float> wear1, wear2; std::vector<float> wear1, wear2;
float tv1 { clamp_circular( 1.f - static_cast<float>( std::fmod( From, fTextureLength ) / fTextureLength ), 1.0f ) }; float tv1 { clamp_circular( 1.f - static_cast<float>( std::fmod( Span.from, Span.texturelength ) / Span.texturelength ), 1.0f ) };
for( std::size_t i = 1; i < stations.size(); ++i ) { for( std::size_t i = 1; i < stations.size(); ++i ) {
auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) }; auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) };
ring( stations[ i - 1 ], middle, ring1, wear1 ); ring( stations[ i - 1 ], middle, ring1, wear1 );
@@ -802,9 +791,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
for( std::size_t j = 0; j < ring1.size(); ++j ) { 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( 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 } ); Output.emplace_back( ring2[ j ].position, ring2[ j ].normal, glm::vec2{ ring2[ j ].texture.x, tv2 } );
if( Userdata != nullptr ) { if( Span.userdata != nullptr ) {
Userdata->push_back( { glm::vec4{ wear1[ j ], 0.f, 0.f, 0.f } } ); Span.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 } } ); Span.userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } );
} }
} }
tv1 = tv2; tv1 = tv2;

View File

@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/geometrybank.h" #include "rendering/geometrybank.h"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <optional> #include <optional>
#include "world/Knots.h"
struct map_colored_paths { struct map_colored_paths {
std::vector<gfx::geometrybank_handle> switches; std::vector<gfx::geometrybank_handle> switches;
@@ -21,6 +22,30 @@ struct map_colored_paths {
std::vector<gfx::geometrybank_handle> highlighted; std::vector<gfx::geometrybank_handle> highlighted;
}; };
struct loft_end {
double bend { 0.0 };
knots::line fit;
};
struct loft_span {
int side { 1 };
float halfwidth { 0.f };
double texturelength { 1.0 };
double from { 0.0 };
double to { 0.0 };
knots::line offsets;
knots::line extents;
knots::line inner;
float lift { 0.f };
gfx::userdata_array *userdata { nullptr };
knots::line ramp;
double rampwidth { 0.0 };
bool walls { false };
float wallu { -1.f };
loft_end fromend;
loft_end toend;
};
struct segment_data { struct segment_data {
// types // types
enum point { enum point {
@@ -132,7 +157,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 ); 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 // 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 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, bool const Walls = false, float const Wallu = -1.f, glm::dvec2 const &Bends = glm::dvec2 { 0.0 }, std::vector<glm::dvec2> const &Fitfrom = {}, std::vector<glm::dvec2> const &Fitto = {} ) const; RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, loft_span const &Span ) const;
// point of the path and the unit vector across it towards the right rail profile, at a distance from Point1 // point of the path and the unit vector across it towards the right rail profile, at a distance from Point1
void void
plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const; plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const;

View File

@@ -1678,10 +1678,31 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
std::vector<glm::dvec2> const unfitted; std::vector<glm::dvec2> const unfitted;
auto const fitted = [&]( std::pair<double, std::vector<glm::dvec2>> const &Fit, double const Distance ) -> std::vector<glm::dvec2> const & { auto const fitted = [&]( std::pair<double, std::vector<glm::dvec2>> const &Fit, double const Distance ) -> std::vector<glm::dvec2> const & {
return ( std::abs( Distance - Fit.first ) < 1e-6 ? Fit.second : unfitted ); }; return ( std::abs( Distance - Fit.first ) < 1e-6 ? Fit.second : unfitted ); };
auto const span = [&]( double const Start, double const End, knots::line const &Offsets, knots::line const &Extents, knots::line const &Inner ) {
loft_span result;
result.side = Side;
result.halfwidth = halfwidth;
result.texturelength = texturelength();
result.from = Start;
result.to = End;
result.offsets = Offsets;
result.extents = Extents;
result.inner = Inner;
auto const bent { bends( Start, End ) };
result.fromend = { bent.x, fitted( fitfrom, Start ) };
result.toend = { bent.y, fitted( fitto, End ) };
return result; };
auto const piece = [&]( double const Start, double const End, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, bool const Capstart, bool const Capend, gfx::vertex_array const &Shape, bool const Rolled, std::vector<glm::dvec2> const &Inner, std::vector<glm::dvec2> const &Ramp = {}, bool const Walls = false, float const Lift = 0.f, float const Wallu = -1.f, double const Fade = blend ) { auto const piece = [&]( double const Start, double const End, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, bool const Capstart, bool const Capend, gfx::vertex_array const &Shape, bool const Rolled, std::vector<glm::dvec2> const &Inner, std::vector<glm::dvec2> const &Ramp = {}, bool const Walls = false, float const Lift = 0.f, float const Wallu = -1.f, double const Fade = blend ) {
gfx::vertex_array vertices; gfx::vertex_array vertices;
gfx::userdata_array userdata; gfx::userdata_array userdata;
if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : Fade, Walls, Wallu, bends( Start, End ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { auto loft { span( Start, End, Offsets, Extents, Inner ) };
loft.lift = Lift;
loft.userdata = ( Rolled ? nullptr : &userdata );
loft.ramp = Ramp;
loft.rampwidth = ( Ramp.empty() ? 0.0 : Fade );
loft.walls = Walls;
loft.wallu = Wallu;
if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, loft ) ) {
cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) ); cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) );
gfx::vertex_array triangles; gfx::vertex_array triangles;
gfx::userdata_array triangleuserdata; gfx::userdata_array triangleuserdata;
@@ -1700,43 +1721,6 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
sink( false == Rolled ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) ); sink( false == Rolled ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) );
} }
}; };
auto const level = []( std::vector<glm::dvec2> const &Knots, double const Distance ) {
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; };
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 );
}
std::vector<double> stations;
for( auto const *knots : { &A, &B } ) {
for( auto const &knot : *knots ) {
stations.push_back( knot.x );
}
}
std::sort( stations.begin(), stations.end() );
auto const count { stations.size() };
for( std::size_t i = 1; i < count; ++i ) {
auto const a { stations[ i - 1 ] };
auto const b { stations[ i ] };
auto const before { level( A, a ) - level( B, a ) };
auto const after { level( A, b ) - level( B, b ) };
if( before * after < 0.0 ) {
stations.push_back( a + ( b - a ) * before / ( before - after ) );
}
}
std::sort( stations.begin(), stations.end() );
std::vector<glm::dvec2> result;
for( auto const station : stations ) {
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 // 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
std::size_t topfirst { Profile.size() }; std::size_t topfirst { Profile.size() };
std::size_t toplast { 0 }; std::size_t toplast { 0 };
@@ -1748,36 +1732,16 @@ 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 ) { auto const strip = [&]( std::size_t const First, std::size_t const Last ) {
return gfx::vertex_array( Profile.begin() + First, Profile.begin() + Last + 1 ); }; return gfx::vertex_array( Profile.begin() + First, Profile.begin() + Last + 1 ); };
auto const shifted = []( std::vector<glm::dvec2> Knots, double const Lateral ) { auto const crossings = []( knots::line const &A, knots::line const &B, double const Start, double const End, double const Margin, std::vector<double> &Splits ) {
for( auto &knot : Knots ) { for( auto const distance : knots::meetings( A, B ) ) {
knot.y += Lateral; if( distance > Start + Margin && distance < End - Margin ) {
} Splits.push_back( distance );
return Knots; };
auto const crossings = [&]( std::vector<glm::dvec2> const &A, std::vector<glm::dvec2> const &B, double const Start, double const End, double const Margin, std::vector<double> &Splits ) {
if( A.empty() || B.empty() ) {
return;
}
std::vector<double> stations;
for( auto const *knots : { &A, &B } ) {
for( auto const &knot : *knots ) {
stations.push_back( knot.x );
}
}
std::sort( stations.begin(), stations.end() );
for( std::size_t i = 1; i < stations.size(); ++i ) {
auto const before { level( A, stations[ i - 1 ] ) - level( B, stations[ i - 1 ] ) };
auto const after { level( A, stations[ i ] ) - level( B, stations[ i ] ) };
if( before * after < 0.0 ) {
auto const distance { stations[ i - 1 ] + ( stations[ i ] - stations[ i - 1 ] ) * before / ( before - after ) };
if( distance > Start + Margin && distance < End - Margin ) {
Splits.push_back( distance );
}
} }
} }
}; };
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 ) { 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; gfx::vertex_array vertices;
if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner, nullptr, {}, 0.0, false, -1.f, bends( Start, End ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { if( segment.RenderLoftSpan( vertices, m_origin, Profile, span( Start, End, Offsets, Extents, Inner ) ) ) {
cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) );
} }
piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner );
@@ -1789,7 +1753,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
low = std::min( low, static_cast<double>( Side * point.position.x - halfwidth ) ); low = std::min( low, static_cast<double>( Side * point.position.x - halfwidth ) );
high = std::max( high, static_cast<double>( Side * point.position.x - halfwidth ) ); high = std::max( high, static_cast<double>( Side * point.position.x - halfwidth ) );
} }
auto const inner { shifted( Rolled, -blend ) }; auto const inner { knots::shifted( Rolled, -blend ) };
std::vector<double> splits { Start, End }; std::vector<double> splits { Start, End };
std::vector<glm::dvec2> const lowline { { Start, low }, { End, low } }; std::vector<glm::dvec2> const lowline { { Start, low }, { End, low } };
std::vector<glm::dvec2> const highline { { Start, high }, { End, high } }; std::vector<glm::dvec2> const highline { { Start, high }, { End, high } };
@@ -1799,13 +1763,13 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
} }
} }
std::sort( splits.begin(), splits.end() ); std::sort( splits.begin(), splits.end() );
auto const shiny { bound( Extents, inner, true ) }; auto const shiny { knots::bound( Extents, inner, true ) };
auto const blended { bound( Extents, Rolled, true ) }; auto const blended { knots::bound( Extents, Rolled, true ) };
for( std::size_t i = 1; i < splits.size(); ++i ) { for( std::size_t i = 1; i < splits.size(); ++i ) {
auto const middle { 0.5 * ( splits[ i - 1 ] + splits[ i ] ) }; auto const middle { 0.5 * ( splits[ i - 1 ] + splits[ i ] ) };
auto const reach { level( Rolled, middle ) }; auto const reach { knots::sample( Rolled, middle, 0.0 ) };
auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; auto const least { std::max( low, Inner.empty() ? low : knots::sample( Inner, middle, 0.0 ) ) };
auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; auto const most { std::min( high, Extents.empty() ? high : knots::sample( Extents, middle, 0.0 ) ) };
auto const shown = [&]( double const Lowest, double const Highest ) { auto const shown = [&]( double const Lowest, double const Highest ) {
return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; };
if( shown( -1e9, reach - blend ) ) { if( shown( -1e9, reach - blend ) ) {
@@ -1815,11 +1779,11 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
auto const own { current == null_handle ? rail_material( Path ) : current }; auto const own { current == null_handle ? rail_material( Path ) : current };
if( shown( reach - blend, reach ) ) { if( shown( reach - blend, reach ) ) {
current = ( marked ? paint( own, { 1.f, 1.f, 0.f } ) : plain ); current = ( marked ? paint( own, { 1.f, 1.f, 0.f } ) : plain );
piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, bound( inner, Inner, false ), marked ? unramped : Rolled ); piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, knots::bound( inner, Inner, false ), marked ? unramped : Rolled );
} }
if( shown( reach, 1e9 ) ) { if( shown( reach, 1e9 ) ) {
current = ( marked ? paint( own, { 1.f, 0.f, 1.f } ) : plain ); current = ( marked ? paint( own, { 1.f, 0.f, 1.f } ) : plain );
piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, bound( Rolled, Inner, false ) ); piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, knots::bound( Rolled, Inner, false ) );
} }
current = plain; current = plain;
} }
@@ -1827,7 +1791,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
auto const treaded = [&]( double const Start, double const End, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, std::vector<glm::dvec2> const &Inner, bool const Capstart, bool const Capend, rail_tread const &Span ) { auto const treaded = [&]( double const Start, double const End, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, std::vector<glm::dvec2> const &Inner, bool const Capstart, bool const Capend, rail_tread const &Span ) {
current = null_handle; current = null_handle;
gfx::vertex_array vertices; gfx::vertex_array vertices;
if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner, nullptr, {}, 0.0, false, -1.f, bends( Start, End ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { if( segment.RenderLoftSpan( vertices, m_origin, Profile, span( Start, End, Offsets, Extents, Inner ) ) ) {
cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) );
} }
piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner );
@@ -1850,23 +1814,11 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
auto const shared { marked ? tint( Span.material, { 0.f, 0.f, 1.f } ) : Span.material }; auto const shared { marked ? tint( Span.material, { 0.f, 0.f, 1.f } ) : Span.material };
auto const ramp = [&]( std::vector<glm::dvec2> const &Knots ) -> std::vector<glm::dvec2> const & { auto const ramp = [&]( std::vector<glm::dvec2> const &Knots ) -> std::vector<glm::dvec2> const & {
return marked ? unramped : Knots; }; return marked ? unramped : Knots; };
std::vector<glm::dvec2> centre; auto const centre { knots::middle( lower, upper ) };
{ auto const beneath { knots::shifted( lower, -tread_fade ) };
std::vector<double> stations; auto const beyond { knots::shifted( upper, tread_fade ) };
for( auto const *knots : { &lower, &upper } ) { auto const lowfade { knots::bound( knots::shifted( lower, tread_fade ), centre, true ) };
for( auto const &knot : *knots ) { auto const highfade { knots::bound( knots::shifted( upper, -tread_fade ), centre, false ) };
stations.push_back( knot.x );
}
}
std::sort( stations.begin(), stations.end() );
for( auto const station : stations ) {
centre.emplace_back( station, 0.5 * ( level( lower, station ) + level( upper, station ) ) );
}
}
auto const beneath { shifted( lower, -tread_fade ) };
auto const beyond { shifted( upper, tread_fade ) };
auto const lowfade { bound( shifted( lower, tread_fade ), centre, true ) };
auto const highfade { bound( shifted( upper, -tread_fade ), centre, false ) };
std::vector<double> splits { Start, End }; std::vector<double> splits { Start, End };
std::vector<glm::dvec2> const lowline { { Start, low }, { End, low } }; std::vector<glm::dvec2> const lowline { { Start, low }, { End, low } };
std::vector<glm::dvec2> const highline { { Start, high }, { End, high } }; std::vector<glm::dvec2> const highline { { Start, high }, { End, high } };
@@ -1875,47 +1827,47 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
crossings( *knots, *limit, Start, End, 0.001, splits ); crossings( *knots, *limit, Start, End, 0.001, splits );
} }
} }
crossings( shifted( lower, 2.0 * tread_fade ), upper, Start, End, 0.001, splits ); crossings( knots::shifted( lower, 2.0 * tread_fade ), upper, Start, End, 0.001, splits );
std::sort( splits.begin(), splits.end() ); std::sort( splits.begin(), splits.end() );
for( std::size_t i = 1; i < splits.size(); ++i ) { for( std::size_t i = 1; i < splits.size(); ++i ) {
auto const a { splits[ i - 1 ] }; auto const a { splits[ i - 1 ] };
auto const b { splits[ i ] }; auto const b { splits[ i ] };
auto const middle { 0.5 * ( a + b ) }; auto const middle { 0.5 * ( a + b ) };
auto const from { level( lower, middle ) }; auto const from { knots::sample( lower, middle, 0.0 ) };
auto const to { level( upper, middle ) }; auto const to { knots::sample( upper, middle, 0.0 ) };
auto const inner { level( lowfade, middle ) }; auto const inner { knots::sample( lowfade, middle, 0.0 ) };
auto const outer { level( highfade, middle ) }; auto const outer { knots::sample( highfade, middle, 0.0 ) };
auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; auto const least { std::max( low, Inner.empty() ? low : knots::sample( Inner, middle, 0.0 ) ) };
auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; auto const most { std::min( high, Extents.empty() ? high : knots::sample( Extents, middle, 0.0 ) ) };
auto const shown = [&]( double const Lowest, double const Highest ) { auto const shown = [&]( double const Lowest, double const Highest ) {
return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; };
if( shown( -1e9, from - tread_fade ) ) { if( shown( -1e9, from - tread_fade ) ) {
current = null_handle; current = null_handle;
piece( a, b, Offsets, bound( Extents, beneath, true ), false, false, top, true, Inner ); piece( a, b, Offsets, knots::bound( Extents, beneath, true ), false, false, top, true, Inner );
} }
if( shown( from - tread_fade, from ) ) { if( shown( from - tread_fade, from ) ) {
current = owntint; current = owntint;
piece( a, b, Offsets, bound( Extents, lower, true ), false, false, top, false, bound( Inner, beneath, false ), ramp( lower ), false, 0.f, -1.f, tread_fade ); piece( a, b, Offsets, knots::bound( Extents, lower, true ), false, false, top, false, knots::bound( Inner, beneath, false ), ramp( lower ), false, 0.f, -1.f, tread_fade );
} }
if( shown( from, inner ) ) { if( shown( from, inner ) ) {
current = sharedtint; current = sharedtint;
piece( a, b, Offsets, bound( Extents, lowfade, true ), false, false, top, false, bound( Inner, lower, false ), ramp( lower ), false, 0.f, -1.f, -tread_fade ); piece( a, b, Offsets, knots::bound( Extents, lowfade, true ), false, false, top, false, knots::bound( Inner, lower, false ), ramp( lower ), false, 0.f, -1.f, -tread_fade );
} }
if( shown( inner, outer ) ) { if( shown( inner, outer ) ) {
current = shared; current = shared;
piece( a, b, Offsets, bound( Extents, highfade, true ), false, false, top, false == marked, bound( Inner, lowfade, false ) ); piece( a, b, Offsets, knots::bound( Extents, highfade, true ), false, false, top, false == marked, knots::bound( Inner, lowfade, false ) );
} }
if( shown( outer, to ) ) { if( shown( outer, to ) ) {
current = sharedtint; current = sharedtint;
piece( a, b, Offsets, bound( Extents, upper, true ), false, false, top, false, bound( Inner, highfade, false ), ramp( upper ), false, 0.f, -1.f, tread_fade ); piece( a, b, Offsets, knots::bound( Extents, upper, true ), false, false, top, false, knots::bound( Inner, highfade, false ), ramp( upper ), false, 0.f, -1.f, tread_fade );
} }
if( shown( to, to + tread_fade ) ) { if( shown( to, to + tread_fade ) ) {
current = owntint; current = owntint;
piece( a, b, Offsets, bound( Extents, beyond, true ), false, false, top, false, bound( Inner, upper, false ), ramp( upper ), false, 0.f, -1.f, -tread_fade ); piece( a, b, Offsets, knots::bound( Extents, beyond, true ), false, false, top, false, knots::bound( Inner, upper, false ), ramp( upper ), false, 0.f, -1.f, -tread_fade );
} }
if( shown( to + tread_fade, 1e9 ) ) { if( shown( to + tread_fade, 1e9 ) ) {
current = null_handle; current = null_handle;
piece( a, b, Offsets, Extents, false, false, top, true, bound( Inner, beyond, false ) ); piece( a, b, Offsets, Extents, false, false, top, true, knots::bound( Inner, beyond, false ) );
} }
} }
current = null_handle; current = null_handle;
@@ -2107,7 +2059,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
std::sort( pieceinner.begin(), pieceinner.end(), byposition ); std::sort( pieceinner.begin(), pieceinner.end(), byposition );
auto const a { std::max( start, From ) }; auto const a { std::max( start, From ) };
auto const b { std::min( end, length ) }; auto const b { std::min( end, length ) };
auto const rolled { i < cuts.size() ? bound( rolledfrom, cuts[ i ].endrolled, true ) : rolledfrom }; auto const rolled { i < cuts.size() ? knots::bound( rolledfrom, cuts[ i ].endrolled, true ) : rolledfrom };
auto const capstart { start > From ? startcap : Openstart }; auto const capstart { start > From ? startcap : Openstart };
auto const capend { end < length ? cuts[ i ].endcap : Openend }; auto const capend { end < length ? cuts[ i ].endcap : Openend };
bendfrom = { a, start > From ? startbend : 0.0 }; bendfrom = { a, start > From ? startbend : 0.0 };