17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 02:49:19 +02:00

feat(track): rails are cut where they cross other rails: a switch with the key frogs draws its frog, wing rails and check rails from its rails, tracks crossing other tracks get acute frogs with wing rails and check rails, obtuse points, guard rails along the quadrilateral of a crossing between 20 and 45 degrees and broken rails with guard rails at wider angles, the track going through chosen by name; sizes come from the rail profile, only for the profiles default, s60, uic60 and s39, others are laid whole; rail ends left open are closed with their cross-section; switches of older sceneries, with check rails in their includes and no key, are drawn as before

This commit is contained in:
maj00r
2026-10-13 18:30:16 +02:00
parent f067824014
commit 16672e81cc
7 changed files with 1766 additions and 44 deletions

View File

@@ -95,6 +95,7 @@ set(SOURCES
"model/Texture.cpp"
"utilities/Timer.cpp"
"world/Track.cpp"
"world/TrackRails.cpp"
"world/Road.cpp"
"world/Sweep.cpp"
"world/RoadPoint.cpp"

View File

@@ -441,6 +441,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 ));
@@ -486,6 +490,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 ) {
@@ -560,6 +568,150 @@ 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 ) 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() );
auto const ring = [&]( double const Distance, 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 worn { sample( Ramp, Distance, -1e6 ) };
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 ); };
for( std::size_t j = 0; j < count; ++j ) {
auto shape { ShapePoints[ j ] };
auto outside { reach( j ) };
if( outside > extent ) {
for( auto const neighbour : { j - 1, j + 1 } ) {
if( neighbour >= count || reach( neighbour ) > extent ) {
continue;
}
auto const &inner { ShapePoints[ neighbour ] };
auto const t { static_cast<float>( ( extent - reach( neighbour ) ) / std::max( outside - reach( neighbour ), 1e-6 ) ) };
shape.position.y = glm::mix( inner.position.y, shape.position.y, t );
shape.texture.x = glm::mix( inner.texture.x, shape.texture.x, t );
shape.normal = inner.normal;
break;
}
outside = extent;
}
else if( outside < innerextent ) {
for( auto const neighbour : { j - 1, j + 1 } ) {
if( neighbour >= count || reach( neighbour ) < innerextent ) {
continue;
}
auto const &other { ShapePoints[ neighbour ] };
auto const t { static_cast<float>( ( innerextent - reach( neighbour ) ) / 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 );
shape.normal = other.normal;
break;
}
outside = innerextent;
}
auto pt { parallel * static_cast<float>( Side * ( Halfwidth + outside + offset ) ) + position };
pt.y += shape.position.y + Lift;
auto norm { shape.normal.x * parallel };
norm.y += shape.normal.y;
Ring.emplace_back( pt, glm::normalize( norm ), glm::vec2{ shape.texture.x, 0.f } );
Wear.push_back( Rampwidth > 0.0 ? static_cast<float>( std::clamp( ( outside - worn + Rampwidth ) / Rampwidth, 0.0, 1.0 ) ) : 1.f );
}
};
auto const texturelength { static_cast<float>( fTextureLength ) };
std::vector<gfx::basic_vertex> ring1, ring2;
std::vector<float> wear1, wear2;
ring( stations.front(), ring1, wear1 );
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 ) {
ring( stations[ i ], 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 } } );
}
}
std::swap( ring1, ring2 );
std::swap( wear1, wear2 );
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

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Classes.h"
#include "rendering/geometrybank.h"
#include <glm/glm.hpp>
#include <optional>
struct map_colored_paths {
std::vector<gfx::geometrybank_handle> switches;
@@ -58,6 +59,7 @@ class TSegment
glm::dvec3 vB;
glm::dvec3 vC; // współczynniki wielomianów trzeciego stopnia vD==Point1
TTrack *pOwner = nullptr; // wskaźnik na właściciela
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;
@@ -128,6 +130,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 ) 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"
@@ -633,6 +635,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>();
@@ -1353,6 +1361,8 @@ void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
Geometry1.clear();
Geometry2.clear();
GeometryCut1.clear();
GeometryCut2.clear();
if( SwitchExtension ) {
SwitchExtension->Geometry3 = gfx::geometry_handle{};
}
@@ -1363,8 +1373,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() ) {
@@ -1377,6 +1429,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
@@ -1409,12 +1471,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
@@ -1433,11 +1503,28 @@ 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
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 ) );
}
};
// 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 ) );
}
};
if (SwitchExtension->RightSwitch)
{ // nowa wersja z SPKS, ale odwrotnie lewa/prawa
gfx::vertex_array vertices;
@@ -1447,12 +1534,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
// 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 } );
cut_faces( vertices, nnumPts, true, false, GeometryCut1 );
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();
fixed_rail( 0, rpts1, 1, texturelength, Geometry1, GeometryCut1 );
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 );
@@ -1460,9 +1546,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
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();
whole_rail( 0, rpts2, -1, texturelength, Geometry1, GeometryCut1 );
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
@@ -1470,16 +1554,13 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
// 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 } );
cut_faces( vertices, nnumPts, true, false, GeometryCut2 );
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();
fixed_rail( 1, rpts2, -1, texturelength, Geometry2, GeometryCut2 );
// 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();
whole_rail( 1, rpts1, 1, texturelength, Geometry2, GeometryCut2 );
}
}
else
@@ -1491,13 +1572,12 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
// 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 } );
cut_faces( vertices, nnumPts, true, false, GeometryCut1 );
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();
fixed_rail( 0, rpts2, -1, texturelength, Geometry1, GeometryCut1 );
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 );
@@ -1505,9 +1585,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
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();
whole_rail( 0, rpts1, 1, texturelength, Geometry1, GeometryCut1 );
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
@@ -1515,17 +1593,14 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
// 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 } );
cut_faces( vertices, nnumPts, true, false, GeometryCut2 );
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();
fixed_rail( 1, rpts1, 1, texturelength, Geometry2, GeometryCut2 );
// 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));
vertices.clear();
whole_rail( 1, rpts2, -1, texturelength, Geometry2, GeometryCut2 );
}
}
}
@@ -2063,7 +2138,7 @@ 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 bladelength { track_rails::blade_steps( *this ) };
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
if (SwitchExtension->RightSwitch)
{ // nowa wersja z SPKS, ale odwrotnie lewa/prawa
@@ -2073,6 +2148,12 @@ TTrack * TTrack::RaAnimate()
// 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 } );
if( false == GeometryCut1.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, GeometryCut1[0], GL_TRIANGLES);
}
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
@@ -2082,6 +2163,12 @@ TTrack * TTrack::RaAnimate()
// 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 } );
if( false == GeometryCut2.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, GeometryCut2[0], GL_TRIANGLES);
}
GfxRenderer->Replace(vertices, empty_userdata, Geometry2[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
@@ -2093,6 +2180,12 @@ TTrack * TTrack::RaAnimate()
// 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 } );
if( false == GeometryCut1.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, GeometryCut1[0], GL_TRIANGLES);
}
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
@@ -2102,6 +2195,12 @@ TTrack * TTrack::RaAnimate()
// 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 } );
if( false == GeometryCut2.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, GeometryCut2[0], GL_TRIANGLES);
}
GfxRenderer->Replace(vertices, empty_userdata, Geometry2[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
@@ -2441,6 +2540,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 << ' ';
@@ -2579,6 +2682,40 @@ 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.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 ) );
}
}
return result;
}
float
TTrack::texture_length( material_handle const Material ) {
@@ -2710,7 +2847,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
@@ -2720,9 +2857,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 ) },
@@ -2730,9 +2865,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 ) );
@@ -2797,6 +2932,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 ) {
@@ -2811,9 +2965,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 ) },
@@ -2921,9 +3073,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,14 @@ 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
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 +292,80 @@ 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
};
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
};
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 };
};
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 };
};
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 };
};
struct rail_layout {
rail_plan plans[ 2 ][ 2 ]; // path, right side
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 +447,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 +460,15 @@ 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 ];
};
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 );
// members
static profiles_array m_profiles; // shared database of path element profiles
static profiles_map m_profilesmap;

1280
world/TrackRails.cpp Normal file

File diff suppressed because it is too large Load Diff

37
world/TrackRails.h Normal file
View File

@@ -0,0 +1,37 @@
/*
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 rolled_band { 0.1 };
double constexpr nose_tip { 0.012 };
double constexpr middle_angle { 0.35 };
double constexpr wide_angle { 0.785 };
// 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 );
// 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 );
} // namespace track_rails