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:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
445
world/Track.cpp
445
world/Track.cpp
@@ -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 ) },
|
||||
|
||||
158
world/Track.h
158
world/Track.h
@@ -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
2319
world/TrackRails.cpp
Normal file
File diff suppressed because it is too large
Load Diff
47
world/TrackRails.h
Normal file
47
world/TrackRails.h
Normal 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
|
||||
Reference in New Issue
Block a user