17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 04:37:30 +01: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:
maj00r
2026-10-14 03:11:54 +02:00
41 changed files with 11940 additions and 211 deletions

View File

@@ -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;

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include <glm/glm.hpp>
#include <memory>
#include <vector>
#include <optional>
struct map_colored_paths {
std::vector<gfx::geometrybank_handle> switches;
@@ -73,6 +74,7 @@ class TSegment
TTrack *pOwner = nullptr; // wskaźnik na właściciela
std::vector<std::shared_ptr<TSegment>> m_pieces; // parts of a path made of several curves, parametrized by distance
std::vector<double> m_starts; // distance from Point1 to the start of each part
std::optional<std::pair<glm::dvec3, glm::dvec3>> m_joints[ 2 ];
glm::dvec3 GetFirstDerivative(double const fTime) const;
double RombergIntegral(double const fA, double const fB) const;
@@ -152,6 +154,15 @@ public:
bool
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 } ) 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;
void
set_joint( int const End, std::optional<std::pair<glm::dvec3, glm::dvec3>> const &Joint ) {
m_joints[ End ] = Joint; }
/*
void
Render();

View File

@@ -15,7 +15,9 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "world/Track.h"
#include "world/TrackRails.h"
#include "simulation/simulation.h"
#include "scene/scene.h"
#include "utilities/Globals.h"
#include "world/Event.h"
#include "world/MemCell.h"
@@ -203,10 +205,14 @@ void TIsolated::Modify(int i, TDynamicObject *o)
};
// tworzenie nowego odcinka ruchu
TTrack::TTrack( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {}
TTrack::TTrack( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {
track_rails::forget();
}
TTrack::~TTrack()
{ // likwidacja odcinka
track_rails::forget();
if( eType == tt_Cross ) {
delete SwitchExtension->vPoints; // skrzyżowanie może mieć punkty
}
@@ -665,6 +671,12 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
m_profile1 = fetch_track_rail_profile( railprofile );
}
}
else if( str == "frogs" ) {
// frog and check rails made from the rails of the switch
if( eType == tt_Switch ) {
SwitchExtension->m_frogs = true;
}
}
else if( str == "friction" ) {
// memory cell holding friction value modifiers
m_friction.first = parser->getToken<std::string>();
@@ -1430,8 +1442,12 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const
// wypełnianie tablic VBO
void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
track_rails::forget();
Geometry1.clear();
Geometry2.clear();
GeometryCut1.clear();
GeometryCut2.clear();
GeometryTreads.clear();
if( SwitchExtension ) {
SwitchExtension->Geometry3 = gfx::geometry_handle{};
}
@@ -1442,8 +1458,50 @@ void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
create_geometry( Bank );
}
void TTrack::share_joints() {
for( int path = 0; path < ( eType == tt_Switch ? 2 : 1 ); ++path ) {
auto *segment { eType == tt_Switch ? SwitchExtension->Segments[ path ].get() : Segment.get() };
if( segment == nullptr ) {
continue;
}
for( int end = 0; end < 2; ++end ) {
std::optional<std::pair<glm::dvec3, glm::dvec3>> joint;
auto *neighbour { eType == tt_Switch ? ( end ? SwitchExtension->pNexts[ path ] : SwitchExtension->pPrevs[ path ] ) : ( end ? trNext : trPrev ) };
if( neighbour != nullptr && neighbour != this && ( neighbour->iCategoryFlag & 15 ) == ( iCategoryFlag & 15 ) ) {
glm::dvec3 point;
glm::dvec3 across;
segment->plan_frame( end ? segment->GetLength() : 0.0, point, across );
auto closest { 0.2 };
for( int theirpath = 0; theirpath < ( neighbour->eType == tt_Switch ? 2 : 1 ); ++theirpath ) {
auto const *theirs { neighbour->eType == tt_Switch ? neighbour->SwitchExtension->Segments[ theirpath ].get() : neighbour->Segment.get() };
if( theirs == nullptr ) {
continue;
}
for( int theirend = 0; theirend < 2; ++theirend ) {
glm::dvec3 theirpoint;
glm::dvec3 theiracross;
theirs->plan_frame( theirend ? theirs->GetLength() : 0.0, theirpoint, theiracross );
if( theirend == end ) {
theiracross = -theiracross;
}
auto const gap { glm::length( theirpoint - point ) };
if( gap >= closest || glm::dot( across, theiracross ) < 0.9 ) {
continue;
}
closest = gap;
joint.emplace( 0.5 * ( point + theirpoint ), glm::normalize( across + theiracross ) );
}
}
}
segment->set_joint( end, joint );
}
}
}
void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
gfx::userdata_array empty_userdata;
share_joints();
// bake per-instance sleeper transforms now that the owning cell has assigned m_origin.
// safe to call here even if the track has no sleepermodel (early-outs internally).
if( m_sleeper_enabled && m_sleeper_local_transforms.empty() ) {
@@ -1456,6 +1514,16 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
// zwykla szyna: //Ra: czemu główki są asymetryczne na wysokości 0.140?
gfx::vertex_array rpts1, rpts2;
create_track_rail_profile( rpts1, rpts2 );
auto const drawing { make_rail_drawing( Bank ) };
auto const cut_faces = [&]( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, geometryhandle_sequence &Geometry ) {
track_rails::cut_faces( Strip, Ring, Start, End, Bank, Geometry ); };
m_materialcut1 = track_rails::cut_material( m_material1 );
auto const layout { plan_rails( Bank != 0 ) };
if( Bank != 0 ) {
m_crossings = layout.crossed;
}
auto const lay_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
return this->lay_rail( layout, drawing, Path, Profile, Side, Texturelength, From, Openstart, Openend, Geometry, Cuts ); };
switch (eType) // dalej zależnie od typu
{
case tt_Table: // obrotnica jak zwykły tor, tylko animacja dochodzi
@@ -1488,12 +1556,20 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
auto const texturelength { texture_length( m_material1 ) };
gfx::vertex_array vertices;
auto const open { eType == tt_Normal && ( trPrev == nullptr || trNext == nullptr ) };
if( Bank != 0 && true == Geometry1.empty() ) {
Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear(); // reuse the scratchpad
Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
for( auto const side : { 1, -1 } ) {
auto const &profile { side > 0 ? rpts1 : rpts2 };
if( eType == tt_Normal && iTrapezoid == 0 && lay_rail( 0, profile, side, texturelength, 0.0, trPrev == nullptr, trNext == nullptr, Geometry1, GeometryCut1 ) ) {
continue;
}
vertices.clear(); // reuse the scratchpad
Segment->RenderLoft( vertices, m_origin, profile, iTrapezoid > 0, texturelength );
if( open ) {
cut_faces( vertices, nnumPts, trPrev == nullptr, trNext == nullptr, GeometryCut1 );
}
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
}
}
if( Bank == 0 && false == Geometry1.empty() ) {
// special variant, replace existing data for a turntable track
@@ -1512,100 +1588,61 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
create_track_blade_profile( rpts3, rpts4 );
// TODO, TBD: change all track geometry to triangles, to allow packing data in less, larger buffers
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
auto const bladelength { static_cast<int>( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) ) };
auto const bladelength { track_rails::blade_steps( *this ) };
// positive jointlength: the switch is typically used along the main track, negative: along the diverging track
// TODO: determine this from names of textures assigned to the tracks
// auto const jointlength { static_cast<int>( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.15 ) ) };
auto const jointlength { 0 }; // temporary until implementation of the above
if (SwitchExtension->RightSwitch)
{ // nowa wersja z SPKS, ale odwrotnie lewa/prawa
gfx::vertex_array vertices;
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
if( jointlength > 0 ) {
// part of the diverging rail touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, 0, jointlength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
// other rail, full length
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
m_materialcut2 = track_rails::cut_material( m_material2 );
// rail behind the blade
auto const fixed_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
if( false == lay_rail( Path, Profile, Side, Texturelength, track_rails::blade_length( *this, Path ), false, false, Geometry, Cuts ) ) {
gfx::vertex_array vertices;
SwitchExtension->Segments[ Path ]->RenderLoft( vertices, m_origin, Profile, false, Texturelength, 1.0, bladelength );
Geometry.emplace_back( GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// diverging rail, potentially minus part touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, jointlength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
};
// rail along the whole path
auto const whole_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
if( false == lay_rail( Path, Profile, Side, Texturelength, 0.0, false, false, Geometry, Cuts ) ) {
gfx::vertex_array vertices;
SwitchExtension->Segments[ Path ]->RenderLoft( vertices, m_origin, Profile, false, Texturelength, 1.0, jointlength );
Geometry.emplace_back( GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
}
};
gfx::vertex_array vertices;
for( int path = 0; path < 2; ++path ) {
auto const material { path ? m_material2 : m_material1 };
if( material == 0 ) {
continue;
}
auto const side { blade_side( path ) };
loft_blade( path, side > 0 ? rpts1 : rpts2, side > 0 ? rpts3 : rpts4, texture_length( material ), vertices );
cut_faces( vertices, nnumPts, true, false, path ? GeometryCut2 : GeometryCut1 );
( path ? Geometry2 : Geometry1 ).emplace_back( GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
vertices.clear();
}
else
{ // lewa działa lepiej niż prawa
gfx::vertex_array vertices;
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
// prawa szyna za iglicą
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
if( jointlength > 0 ) {
// part of the diverging rail touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, 0, jointlength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
// other rail, full length
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
// lewa szyna za iglicą
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// diverging rail, potentially minus part touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, jointlength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
for( int path = 0; path < 2; ++path ) {
auto const material { path ? m_material2 : m_material1 };
if( material == 0 ) {
continue;
}
auto &geometry { path ? Geometry2 : Geometry1 };
auto &cuts { path ? GeometryCut2 : GeometryCut1 };
auto const texturelength { texture_length( material ) };
auto const side { blade_side( path ) };
auto const &rail { side > 0 ? rpts1 : rpts2 };
// fixed parts
// szyna za iglicą
fixed_rail( path, rail, side, texturelength, geometry, cuts );
if( path == 0 && jointlength > 0 ) {
// part of the diverging rail touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rail, false, texturelength, 1.0, 0, jointlength );
geometry.emplace_back( GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
vertices.clear();
}
// other rail, full length; on the diverging path potentially minus part touched by wheels of vehicle going straight
whole_rail( path, side > 0 ? rpts2 : rpts1, -side, texturelength, geometry, cuts );
}
}
// auto-generated switch trackbed
@@ -2097,6 +2134,20 @@ void TTrack::RaAnimListAdd(TTrack *t)
}
};
int TTrack::blade_side( int const Path ) const {
return ( SwitchExtension->RightSwitch == ( Path == 0 ) ? 1 : -1 );
}
void TTrack::loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::vertex_array const &Blade, float const Texturelength, gfx::vertex_array &Vertices ) const {
auto const offset { blade_side( Path ) * ( Path == 0 ? SwitchExtension->fOffset2 : fMaxOffset - SwitchExtension->fOffset1 ) };
auto const bladelength { track_rails::blade_steps( *this ) };
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ Path ]->RenderLoft( Vertices, m_origin, Blade, true, Texturelength, 1.0, 0, bladelength / 2, { offset, offset / 2 } );
SwitchExtension->Segments[ Path ]->RenderLoft( Vertices, m_origin, Rail, false, Texturelength, 1.0, bladelength / 2, bladelength, { offset / 2, 0.f } );
}
TTrack * TTrack::RaAnimate()
{ // wykonanie rekurencyjne animacji, wywoływane przed wyświetleniem sektora
// zwraca wskaźnik toru wymagającego dalszej animacji
@@ -2142,48 +2193,24 @@ TTrack * TTrack::RaAnimate()
gfx::vertex_array rpts3, rpts4;
create_track_blade_profile( rpts3, rpts4 );
gfx::vertex_array vertices;
auto const bladelength { static_cast<int>( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) ) };
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
if (SwitchExtension->RightSwitch)
{ // nowa wersja z SPKS, ale odwrotnie lewa/prawa
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[0], GL_TRIANGLE_STRIP);
vertices.clear();
auto const replace_blade = [&]( geometryhandle_sequence const &Geometry, geometryhandle_sequence const &Cuts ) {
if( false == Cuts.empty() ) {
gfx::vertex_array triangles;
track_rails::cap_loft( vertices, nnumPts, true, false, triangles );
auto userdata { track_rails::worn_userdata( triangles.size() ) };
GfxRenderer->Replace( triangles, userdata, Cuts[ 0 ], GL_TRIANGLES );
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry2[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
}
else { // lewa działa lepiej niż prawa
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry2[0], GL_TRIANGLE_STRIP);
vertices.clear();
GfxRenderer->Replace( vertices, empty_userdata, Geometry[ 0 ], GL_TRIANGLE_STRIP );
vertices.clear(); };
for( int path = 0; path < 2; ++path ) {
auto const material { path ? m_material2 : m_material1 };
if( material == 0 ) {
continue;
}
auto const side { blade_side( path ) };
loft_blade( path, side > 0 ? rpts1 : rpts2, side > 0 ? rpts3 : rpts4, texture_length( material ), vertices );
replace_blade( path ? Geometry2 : Geometry1, path ? GeometryCut2 : GeometryCut1 );
}
}
}
@@ -2543,6 +2570,10 @@ TTrack::export_as_text_( std::ostream &Output ) const {
}
Output << "trackbed " << texturefile << ' ';
}
if( eType == tt_Switch
&& SwitchExtension->m_frogs ) {
Output << "frogs ";
}
if( iCategoryFlag == 1
&& false == m_profile1.first.empty() ) {
Output << "railprofile " << m_profile1.first << ' ';
@@ -2681,6 +2712,123 @@ TTrack::track_rail_profile( int const Profile ) {
return m_profiles[ Profile ];
}
TTrack::rail_size
TTrack::rail_dimensions() const {
rail_size result;
if( m_profiles.empty() ) {
return result;
}
auto const &profile { track_rail_profile( m_profile1.second ) };
auto const count { profile.size() / 2 };
if( count < 2 ) {
return result;
}
auto top { -1e9 };
auto bottom { 1e9 };
result.low = 1e9;
result.high = -1e9;
for( std::size_t i = 0; i < count; ++i ) {
auto const &point { profile[ i ].position };
top = std::max( top, static_cast<double>( point.y ) );
bottom = std::min( bottom, static_cast<double>( point.y ) );
result.low = std::min( result.low, static_cast<double>( point.x ) );
result.high = std::max( result.high, static_cast<double>( point.x ) );
}
result.height = top - bottom;
result.crest = top;
auto const middle { top - 0.5 * result.height };
auto webfrom { 1e9 };
auto webto { -1e9 };
for( std::size_t i = 0; i + 1 < count; ++i ) {
auto const &a { profile[ i ].position };
auto const &b { profile[ i + 1 ].position };
if( a.y != b.y && ( a.y - middle ) * ( b.y - middle ) <= 0.0 ) {
auto const x { a.x + ( b.x - a.x ) * ( middle - a.y ) / ( b.y - a.y ) };
webfrom = std::min( webfrom, x );
webto = std::max( webto, x );
}
}
if( webfrom < webto ) {
result.web = 0.5 * ( webfrom + webto );
result.neck = top;
for( std::size_t i = 0; i < count; ++i ) {
auto const &point { profile[ i ].position };
if( point.y > middle && ( point.x < webfrom - 0.0005 || point.x > webto + 0.0005 ) ) {
result.neck = std::min( result.neck, static_cast<double>( point.y ) );
}
}
}
result.head = 0.0;
for( std::size_t i = 0; i < count; ++i ) {
auto const &point { profile[ i ].position };
if( point.y > top - 0.3 * result.height ) {
result.head = std::max( result.head, static_cast<double>( point.x ) );
}
}
result.tread = result.head;
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.tread = -1e9;
for( auto const i : heads ) {
if( profile[ i ].position.y > top - 0.0005 ) {
result.tread = std::max( result.tread, static_cast<double>( profile[ i ].position.x ) );
}
}
result.ledge = 1e9;
for( auto const i : lips ) {
if( profile[ i ].position.y > result.liptop - 0.0005 ) {
result.ledge = std::min( result.ledge, static_cast<double>( profile[ i ].position.x ) );
}
}
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;
}
float
TTrack::texture_length( material_handle const Material ) {
@@ -2812,7 +2960,7 @@ TTrack * TTrack::Connected(int s, double &d) const
// creates rail profile data for current track
void
TTrack::create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ) {
TTrack::create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const *Shape ) {
auto const fHTW { 0.5f * std::abs( fTrackWidth ) };
float const fHTW2{((iTrapezoid & 2) != 0 ? // ten bit oznacza, że istnieje odpowiednie pNext
@@ -2822,9 +2970,7 @@ TTrack::create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &
roll1{ 0.f },
roll2{ 0.f };
if( Segment ) {
Segment->GetRolls( roll1, roll2 );
}
profile_rolls( roll1, roll2 );
float const
sin1 { std::sin( roll1 ) },
@@ -2832,9 +2978,9 @@ TTrack::create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &
sin2 { std::sin( roll2 ) },
cos2 { std::cos( roll2 ) };
auto const &railprofile { track_rail_profile( m_profile1.second ) };
auto const &railprofile { Shape != nullptr ? *Shape : track_rail_profile( m_profile1.second ) };
// NOTE: rail profile defines both regular rail and switch blade profiles, so we halve total point count
auto const pointcount { railprofile.size() / 2 };
auto const pointcount { Shape != nullptr ? railprofile.size() : railprofile.size() / 2 };
Right.resize( pointcount * ( iTrapezoid == 0 ? 1 : 2 ) );
Left.resize( pointcount * ( iTrapezoid == 0 ? 1 : 2 ) );
@@ -2899,6 +3045,25 @@ TTrack::create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &
}
}
void
TTrack::profile_rolls( float &Roll1, float &Roll2 ) const {
Roll1 = Roll2 = 0.f;
if( eType == tt_Switch && SwitchExtension ) {
auto sum { 0.f };
for( int path = 0; path < 2; ++path ) {
float roll1, roll2;
SwitchExtension->Segments[ path ]->GetRolls( roll1, roll2 );
sum += roll1 + roll2;
}
Roll1 = Roll2 = 0.25f * sum;
return;
}
if( Segment ) {
Segment->GetRolls( Roll1, Roll2 );
}
}
// creates switch blades profile data for current track
void
TTrack::create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ) {
@@ -2913,9 +3078,7 @@ TTrack::create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array
roll1 { 0.f },
roll2 { 0.f };
if( Segment ) {
Segment->GetRolls( roll1, roll2 );
}
profile_rolls( roll1, roll2 );
float const
sin1 { std::sin( roll1 ) },
@@ -3023,9 +3186,7 @@ TTrack::create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previ
roll1{ 0.f },
roll2{ 0.f };
if( Segment ) {
Segment->GetRolls( roll1, roll2 );
}
profile_rolls( roll1, roll2 );
float const
sin1 { std::sin( roll1 ) },

View File

@@ -99,6 +99,7 @@ class TSwitchExtension
glm::dvec3 vTrans; // docelowa translacja przesuwnicy
material_handle m_material3 = 0; // texture of auto generated switch trackbed
gfx::geometry_handle Geometry3; // geometry of auto generated switch trackbed
bool m_frogs { false }; // the frog and check rails are made from the rails, for switches with no model of them in their includes
gfx::geometrybank_handle map_geometry[6]; // geometry for map visualisation
};
@@ -184,9 +185,21 @@ public:
using geometryhandle_sequence = std::vector<gfx::geometry_handle>;
geometryhandle_sequence Geometry1; // geometry chunks textured with texture 1
geometryhandle_sequence Geometry2; // geometry chunks textured with texture 2
material_handle m_materialcut1 = 0; // matte variant of texture 1, for the cut faces of rails
material_handle m_materialcut2 = 0; // matte variant of texture 2, for the cut faces of rails of the other switch path
geometryhandle_sequence GeometryCut1; // cut faces of rails textured with texture 1, the blade tip first for a switch
geometryhandle_sequence GeometryCut2; // cut faces of rails textured with texture 2, the blade tip first
struct tread_geometry {
material_handle material { 0 };
material_handle matte { 0 };
geometryhandle_sequence geometry;
geometryhandle_sequence cuts;
};
std::vector<tread_geometry> GeometryTreads;
std::vector<segment_data> m_paths; // source data for owned paths
bool m_editorremoved { false };
std::vector<TTrack *> m_crossings; // tracks whose rails cross the rails of this one, found when its geometry was made
scene::basic_node *m_road { nullptr }; // road or road junction which generated this path. such path is one-way, and isn't saved nor edited on its own
std::array<float, 3> m_routeweights { 0.f, 0.f, 0.f }; // odds of each route through a crossroads being picked by a driver, by the number of the route. all zero: even odds
int iterate_stamp = 0;
@@ -286,6 +299,130 @@ public:
int RouteDraw() const;
void Load(cParser *parser, glm::dvec3 const &pOrigin);
void init_segments( bool const Initial );
// where the inner rails of the two paths of a switch cross, found from their gauge lines behind the blades
struct switch_frog {
bool valid { false };
int side[ 2 ] {}; // rail profile of the crossing rail of each path, 1 right, -1 left
double at[ 2 ] {}; // distance of the crossing along each path
double blades[ 2 ] {}; // distance the blades take along each path
double angle { 0.0 };
};
switch_frog frog() const;
// where a rail of this track crosses a rail of another one, found from their gauge lines
struct rail_crossing {
TTrack *track { nullptr };
int path { 0 };
int side { 0 }; // rail profile of the crossing rail, 1 right, -1 left
double at { 0.0 }; // distance of the crossing along the path, past the ends where the track goes on
double angle { 0.0 };
double into { 0.0 }; // the path direction against the gauge normal of the other rail, above zero: going into the other track
double facing { 0.0 }; // the gauge normals of both rails against each other, below zero: an acute frog
bool nose { false }; // at an acute frog the rail goes on as the nose rail, the other one as the rail beside it
int otherpath { 0 };
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
struct rail_size {
double head { 0.0735 }; // how far the head reaches out
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 };
double crest { 0.0 };
double tread { 0.0735 };
double ledge { 0.0 };
double web { 0.0365 };
double neck { -0.0467 };
};
rail_size rail_dimensions() const;
// a check rail beside a rail, how far its ends turn away from the running rail
struct check_rail {
double from { 0.0 };
double to { 0.0 };
double flarefrom { 0.0 };
double flareto { 0.0 };
double frombend { 0.0 };
double tobend { 0.0 };
};
using check_rail_set = std::array<std::array<std::vector<check_rail>, 2>, 2>; // path, right side
// the check rails beside the rails of each path, kept clear of the blades and the rails of other paths running inside them
check_rail_set check_rails( std::vector<rail_crossing> const &Crossings );
// a rail broken where another one crosses it: the part coming from below ends at end, the part going on starts at start
struct rail_cut {
double at { 0.0 };
double end { 0.0 };
double start { 0.0 };
std::vector<glm::dvec2> endoffsets, endextents, startoffsets, startextents;
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 };
double endbend { 0.0 };
double startbend { 0.0 };
std::vector<glm::dvec2> endinner, startinner;
bool endcap { true };
bool startcap { true };
};
struct rail_plan {
std::vector<rail_cut> cuts;
};
// guard rails of a crossing at a wide angle: the rail profile laid inside the running rail across the flangeway, broken like the rails
struct guard_rail {
double from { 0.0 };
double to { 0.0 };
rail_plan plan;
bool flarefrom { true };
bool flareto { true };
glm::dvec2 frommiter { 0.0 };
glm::dvec2 tomiter { 0.0 };
bool through { true };
};
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 };
double lift { 0.0 };
bool web { false };
};
struct rail_tread {
double from { 0.0 };
double to { 0.0 };
material_handle material { 0 };
int anchor { 0 };
std::vector<glm::dvec2> edge;
std::vector<glm::dvec2> gauge;
bool below { 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<glm::dvec2> blocks[ 2 ][ 2 ];
std::vector<rail_tread> treads[ 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;
std::vector<TTrack *> crossed; // tracks whose rails cross the rails of this one
};
rail_layout plan_rails( bool const Crossings );
void update_location();
bool AssignEvents();
bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus);
@@ -367,8 +504,10 @@ private:
float texture_length( material_handle const Material );
// creates profile for a part of current path
void create_switch_trackbed( gfx::vertex_array &Output );
void create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left );
void create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const *Shape = nullptr );
void create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left );
void profile_rolls( float &Roll1, float &Roll2 ) const;
void share_joints();
void create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next );
void create_road_profile( gfx::vertex_array &Output, bool const Forcetransition = false );
void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false );
@@ -378,6 +517,23 @@ private:
/// Safe to call multiple times; clears any previously cached transforms first.
/// </summary>
void build_sleeper_transforms();
struct rail_drawing {
gfx::geometrybank_handle bank;
rail_size size;
float halfwidth { 0.f };
gfx::vertex_array checkshapes[ 2 ];
gfx::vertex_array plates[ 2 ];
gfx::vertex_array fillers[ 2 ];
float platetexture { 0.f };
};
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
bool lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts );
int blade_side( int const Path ) const;
material_handle rail_material( int const Path ) const;
glm::vec2 tread_band() const;
material_handle shared_tread( int const Path, TTrack const &Other, int const Otherpath ) const;
void loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::vertex_array const &Blade, float const Texturelength, gfx::vertex_array &Vertices ) const;
// members
static profiles_array m_profiles; // shared database of path element profiles
static profiles_map m_profilesmap;

2319
world/TrackRails.cpp Normal file

File diff suppressed because it is too large Load Diff

47
world/TrackRails.h Normal file
View File

@@ -0,0 +1,47 @@
/*
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 "world/Track.h"
namespace track_rails {
double constexpr flangeway { 0.045 };
double constexpr blend { 0.015 };
double constexpr tread_fade { 0.02 };
double constexpr wheel_width { 0.14 };
double constexpr back_to_back { 1.36 };
double constexpr rolled_band { wheel_width - ( 1.435 - back_to_back ) / 2.0 };
double constexpr nose_tip { 0.012 };
double constexpr middle_angle { 0.35 };
double constexpr wide_angle { 0.785 };
void forget();
// how far a wing rail reaches past the crossing point of the rails
double wing_reach( double const Slope );
double check_length( double const Slope );
// the rails of the track are cut where they cross other rails: plain tracks, and switches whose frog is made from the rails
bool cut_rails( TTrack const &Track );
int blade_steps( TTrack const &Track );
double blade_length( TTrack const &Track, int const Path );
material_handle cut_material( material_handle const Material );
float tread_brightness( material_handle const Material, glm::vec2 const &Band );
glm::vec3 tread_colour( material_handle const Material, glm::vec2 const &Band );
material_handle tint_material( material_handle const Material, glm::vec3 const &Colour );
// triangles closing the first and/or the last cross-section of a lofted strip, made of pairs of points for each of Ring profile points, both sides of each
void cap_loft( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::vertex_array &Triangles );
gfx::userdata_array worn_userdata( std::size_t const Count );
// the ends of rails left open: at track ends with nothing joined, blade tips, the gap of a frog
void cut_faces( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::geometrybank_handle const &Bank, TTrack::geometryhandle_sequence &Geometry );
glm::dvec3 rail_point( TTrack const &Track, int const Path, int const Side, double const Distance, double const Lateral );
void rail_offset( TTrack const &Track, int const Path, int const Side, glm::dvec3 const &Point, double &Distance, double &Lateral );
} // namespace track_rails