17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 06:19:19 +02: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 );
}
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;
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 };
std::vector<double> stations { Span.from, Span.to };
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 );
}
for( auto const *knots : { &Offsets, &Extents, &Inner } ) {
for( auto const &knot : *knots ) {
if( knot.x > From && knot.x < To ) {
for( auto const *line : { &Span.offsets, &Span.extents, &Span.inner } ) {
for( auto const &knot : *line ) {
if( knot.x > Span.from && knot.x < Span.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 ] };
for( auto const *line : { &Span.extents, &Span.inner } ) {
for( std::size_t i = 1; i < line->size(); ++i ) {
auto const &a { ( *line )[ i - 1 ] };
auto const &b { ( *line )[ 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 reach { static_cast<double>( Span.side * point.position.x - Span.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 ) {
if( t > 0.0 && t < 1.0 && distance > Span.from && distance < Span.to ) {
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() );
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 };
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 };
for( auto const &knot : Offsets ) {
for( auto const &knot : Span.offsets ) {
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 };
for( auto const &knot : Fit ) {
for( auto const &knot : End.fit ) {
fitmost = std::max( fitmost, std::abs( knot.y ) );
}
return std::abs( Bend ) * ( Fit.empty() ? widest : widest + offsetmost ) + fitmost + 0.001; };
auto const frommargin { margin( Bends.x, Fitfrom ) };
auto const tomargin { margin( Bends.y, Fitto ) };
return std::abs( End.bend ) * ( End.fit.empty() ? widest : widest + offsetmost ) + fitmost + 0.001; };
auto const frommargin { margin( Span.fromend ) };
auto const tomargin { margin( Span.toend ) };
stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) {
return ( Distance > From && Distance < To )
&& ( Distance < From + frommargin || Distance > To - tomargin ); } ), stations.end() );
return ( Distance > Span.from && Distance < Span.to )
&& ( 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 ) {
@@ -660,29 +648,30 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
}
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 ) };
auto const offset { knots::sample( Span.offsets, Distance, 0.0 ) };
auto const extent { knots::sample( Span.extents, Distance, 1e6 ) };
auto const innerextent { knots::sample( Span.inner, Distance, -1e6 ) };
auto const middleextent { knots::sample( Span.extents, Middle, 1e6 ) };
auto const middleinner { knots::sample( Span.inner, Middle, -1e6 ) };
glm::vec3 outerwall { 0.f }, innerwall { 0.f };
if( Walls ) {
if( Span.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 outward { glm::dvec3{ across } * static_cast<double>( Span.side ) };
auto const slope = [&]( knots::line const &Knots, double const Default ) {
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( Span.extents, 1e6 ) ) };
innerwall = glm::vec3{ glm::normalize( tangent * slope( Span.inner, -1e6 ) - outward ) };
}
auto const worn { sample( Ramp, Distance, -1e6 ) };
auto const bend { Distance <= From ? Bends.x : Distance >= To ? Bends.y : 0.0 };
static std::vector<glm::dvec2> const unfitted;
auto const &fit { Distance <= From ? Fitfrom : Distance >= To ? Fitto : unfitted };
auto const worn { knots::sample( Span.ramp, Distance, -1e6 ) };
static loft_end const unshaped;
auto const &shaped { Distance <= Span.from ? Span.fromend : Distance >= Span.to ? Span.toend : unshaped };
auto const bend { shaped.bend };
auto const &fit { shaped.fit };
auto along { glm::vec3 { 0.f } };
auto forward { glm::vec3 { 0.f } };
if( bend != 0.0 || false == fit.empty() ) {
@@ -699,7 +688,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
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 ); };
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 ) {
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 ) {
@@ -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 ); };
auto first { j };
auto last { j };
if( Walls ) {
if( Span.walls ) {
while( first > 0 && twin( first - 1, j ) ) {
--first;
}
@@ -774,25 +763,25 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
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 ) ) };
pt.y += shape.position.y + Lift;
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 + Span.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;
if( Span.wallu >= 0.f ) {
shape.texture.x = Span.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 );
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<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 ) {
auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) };
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 ) {
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 } } );
if( Span.userdata != nullptr ) {
Span.userdata->push_back( { glm::vec4{ wear1[ j ], 0.f, 0.f, 0.f } } );
Span.userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } );
}
}
tv1 = tv2;

View File

@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/geometrybank.h"
#include <glm/glm.hpp>
#include <optional>
#include "world/Knots.h"
struct map_colored_paths {
std::vector<gfx::geometrybank_handle> switches;
@@ -21,6 +22,30 @@ struct map_colored_paths {
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 {
// types
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 );
// 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, 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
void
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;
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 ); };
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 ) {
gfx::vertex_array vertices;
gfx::userdata_array userdata;
if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : 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 ) );
gfx::vertex_array triangles;
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 ) );
}
};
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
std::size_t topfirst { Profile.size() };
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 ) {
return gfx::vertex_array( Profile.begin() + First, Profile.begin() + Last + 1 ); };
auto const shifted = []( std::vector<glm::dvec2> Knots, double const Lateral ) {
for( auto &knot : Knots ) {
knot.y += Lateral;
}
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 crossings = []( knots::line const &A, knots::line const &B, double const Start, double const End, double const Margin, std::vector<double> &Splits ) {
for( auto const distance : knots::meetings( A, B ) ) {
if( distance > Start + Margin && distance < End - Margin ) {
Splits.push_back( distance );
}
}
};
auto const running = [&]( double const Start, double const End, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, std::vector<glm::dvec2> const &Inner, std::vector<glm::dvec2> const &Rolled, bool const Capstart, bool const Capend ) {
gfx::vertex_array vertices;
if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner, 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 ) );
}
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 ) );
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<glm::dvec2> const lowline { { Start, low }, { End, low } };
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() );
auto const shiny { bound( Extents, inner, true ) };
auto const blended { bound( Extents, Rolled, true ) };
auto const shiny { knots::bound( Extents, inner, true ) };
auto const blended { knots::bound( Extents, Rolled, true ) };
for( std::size_t i = 1; i < splits.size(); ++i ) {
auto const middle { 0.5 * ( splits[ i - 1 ] + splits[ i ] ) };
auto const reach { level( Rolled, middle ) };
auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) };
auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) };
auto const reach { knots::sample( Rolled, middle, 0.0 ) };
auto const least { std::max( low, Inner.empty() ? low : knots::sample( Inner, middle, 0.0 ) ) };
auto const most { std::min( high, Extents.empty() ? high : knots::sample( Extents, middle, 0.0 ) ) };
auto const shown = [&]( double const Lowest, double const Highest ) {
return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; };
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 };
if( shown( reach - blend, reach ) ) {
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 ) ) {
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;
}
@@ -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 ) {
current = null_handle;
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 ) );
}
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 ramp = [&]( std::vector<glm::dvec2> const &Knots ) -> std::vector<glm::dvec2> const & {
return marked ? unramped : Knots; };
std::vector<glm::dvec2> centre;
{
std::vector<double> stations;
for( auto const *knots : { &lower, &upper } ) {
for( auto const &knot : *knots ) {
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 ) };
auto const centre { knots::middle( lower, upper ) };
auto const beneath { knots::shifted( lower, -tread_fade ) };
auto const beyond { knots::shifted( upper, tread_fade ) };
auto const lowfade { knots::bound( knots::shifted( lower, tread_fade ), centre, true ) };
auto const highfade { knots::bound( knots::shifted( upper, -tread_fade ), centre, false ) };
std::vector<double> splits { Start, End };
std::vector<glm::dvec2> const lowline { { Start, low }, { End, low } };
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( 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() );
for( std::size_t i = 1; i < splits.size(); ++i ) {
auto const a { splits[ i - 1 ] };
auto const b { splits[ i ] };
auto const middle { 0.5 * ( a + b ) };
auto const from { level( lower, middle ) };
auto const to { level( upper, middle ) };
auto const inner { level( lowfade, middle ) };
auto const outer { level( highfade, middle ) };
auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) };
auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) };
auto const from { knots::sample( lower, middle, 0.0 ) };
auto const to { knots::sample( upper, middle, 0.0 ) };
auto const inner { knots::sample( lowfade, middle, 0.0 ) };
auto const outer { knots::sample( highfade, middle, 0.0 ) };
auto const least { std::max( low, Inner.empty() ? low : knots::sample( Inner, middle, 0.0 ) ) };
auto const most { std::min( high, Extents.empty() ? high : knots::sample( Extents, middle, 0.0 ) ) };
auto const shown = [&]( double const Lowest, double const Highest ) {
return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; };
if( shown( -1e9, from - tread_fade ) ) {
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 ) ) {
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 ) ) {
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 ) ) {
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 ) ) {
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 ) ) {
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 ) ) {
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;
@@ -2107,7 +2059,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i
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 rolled { i < cuts.size() ? knots::bound( rolledfrom, cuts[ i ].endrolled, true ) : rolledfrom };
auto const capstart { start > From ? startcap : Openstart };
auto const capend { end < length ? cuts[ i ].endcap : Openend };
bendfrom = { a, start > From ? startbend : 0.0 };