17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 23:59:03 +01:00

Merge remote-tracking branch 'origin/feat/switch-blades' into feat/gl46-migration

This commit is contained in:
2026-10-19 22:13:43 +02:00
169 changed files with 52371 additions and 10816 deletions

65
world/GaugeLine.cpp Normal file
View File

@@ -0,0 +1,65 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/GaugeLine.h"
namespace track_rails {
gauge_line make_gauge_line( TSegment const &Segment, double const Offset, double const From ) {
gauge_line line;
line.segment = &Segment;
line.offset = Offset;
line.from = From;
auto const length { Segment.GetLength() };
auto const add = [&]( double const Distance ) {
glm::dvec3 point, tangent;
line.at( Distance, point, tangent );
line.points.push_back( point );
line.distances.push_back( Distance ); };
auto const count { std::max( { 1, Segment.RaSegCount(), static_cast<int>( std::ceil( ( length - From ) / 5.0 ) ) } ) };
for( int i = 0; i <= count; ++i ) {
add( From + ( length - From ) * i / count );
}
line.low = line.high = plan( line.points.front() );
for( auto const &point : line.points ) {
line.low = glm::min( line.low, plan( point ) );
line.high = glm::max( line.high, plan( point ) );
}
return line;
}
double lateral( gauge_line const &Line, int const Side, glm::dvec3 const &Point, double &Distance ) {
glm::dvec3 point, tangent;
for( int step = 0; step < 8; ++step ) {
Line.at( Distance, point, tangent );
Distance += glm::dot( plan( Point - point ), glm::normalize( plan( tangent ) ) );
}
Line.at( Distance, point, tangent );
return -glm::dot( plan( Point - point ), plan( Line.normal( Distance, Side ) ) );
}
double nearest_distance( gauge_line const &Line, glm::dvec3 const &Point ) {
auto result { Line.distances.front() };
auto nearest { std::numeric_limits<double>::max() };
for( std::size_t i = 0; i < Line.points.size(); ++i ) {
auto const gap { glm::length( plan( Line.points[ i ] - Point ) ) };
if( gap < nearest ) {
nearest = gap;
result = Line.distances[ i ];
}
}
return result;
}
} // namespace track_rails

72
world/GaugeLine.h Normal file
View File

@@ -0,0 +1,72 @@
/*
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/Segment.h"
namespace track_rails {
inline glm::dvec2 plan( glm::dvec3 const &V ) {
return glm::dvec2{ V.x, V.z };
}
inline double cross( glm::dvec2 const &A, glm::dvec2 const &B ) {
return A.x * B.y - A.y * B.x;
}
inline bool meet( glm::dvec2 const &A0, glm::dvec2 const &A, glm::dvec2 const &B0, glm::dvec2 const &B, double const Parallel, double &U, double &V ) {
auto const denominator { cross( A, B ) };
if( std::abs( denominator ) < Parallel ) {
return false;
}
U = cross( B0 - A0, B ) / denominator;
V = cross( B0 - A0, A ) / denominator;
return true;
}
// gauge line of a rail: the running edge of its head, along a path and on past its ends along the tangents
struct gauge_line {
TSegment const *segment { nullptr };
double offset { 0.0 }; // across the path, to the running edge
double from { 0.0 };
std::vector<glm::dvec3> points;
std::vector<double> distances;
glm::dvec2 low { 0.0 };
glm::dvec2 high { 0.0 };
void at( double const Distance, glm::dvec3 &Point, glm::dvec3 &Tangent ) const {
auto const length { segment->GetLength() };
auto const local = [&]( double const D ) {
glm::dvec3 point, across;
segment->plan_frame( D, point, across );
return point + across * offset; };
auto const inside { std::clamp( Distance, 0.0, length ) };
auto const delta { std::min( 0.05, 0.5 * length ) };
auto const a { std::max( inside - delta, 0.0 ) };
auto const b { std::min( inside + delta, length ) };
Tangent = local( b ) - local( a );
Tangent = ( glm::length2( Tangent ) > 0.0 ? glm::normalize( Tangent ) : glm::dvec3{ 1.0, 0.0, 0.0 } );
Point = local( inside ) + Tangent * ( Distance - inside );
}
glm::dvec3 normal( double const Distance, int const Side ) const {
glm::dvec3 point, across;
segment->plan_frame( std::clamp( Distance, 0.0, segment->GetLength() ), point, across );
return across * static_cast<double>( -Side );
}
};
gauge_line make_gauge_line( TSegment const &Segment, double const Offset, double const From );
double lateral( gauge_line const &Line, int const Side, glm::dvec3 const &Point, double &Distance );
double nearest_distance( gauge_line const &Line, glm::dvec3 const &Point );
} // namespace track_rails

151
world/Knots.h Normal file
View File

@@ -0,0 +1,151 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <algorithm>
#include <vector>
#include <glm/glm.hpp>
namespace knots {
using line = std::vector<glm::dvec2>;
inline bool by_position( glm::dvec2 const &A, glm::dvec2 const &B ) {
return A.x < B.x;
}
inline void sort( line &Line ) {
std::sort( Line.begin(), Line.end(), by_position );
}
inline double sample( line const &Line, double const Position, double const Default ) {
if( Line.empty() ) {
return Default;
}
if( Position <= Line.front().x ) {
return Line.front().y;
}
// the first knot at or past the position, after the first one
auto const next { std::lower_bound( Line.begin() + 1, Line.end(), Position, []( glm::dvec2 const &Knot, double const At ) { return Knot.x < At; } ) };
if( next == Line.end() ) {
return Line.back().y;
}
auto const &before { *( next - 1 ) };
return glm::mix( before.y, next->y, ( Position - before.x ) / std::max( next->x - before.x, 1e-6 ) );
}
inline line shifted( line Line, double const Value ) {
for( auto &knot : Line ) {
knot.y += Value;
}
return Line;
}
inline std::vector<double> stations( line const &A, line const &B ) {
std::vector<double> result;
for( auto const *each : { &A, &B } ) {
for( auto const &knot : *each ) {
result.push_back( knot.x );
}
}
std::sort( result.begin(), result.end() );
return result;
}
inline std::vector<double> meetings( line const &A, line const &B ) {
std::vector<double> result;
if( A.empty() || B.empty() ) {
return result;
}
auto const all { stations( A, B ) };
for( std::size_t i = 1; i < all.size(); ++i ) {
auto const before { sample( A, all[ i - 1 ], 0.0 ) - sample( B, all[ i - 1 ], 0.0 ) };
auto const after { sample( A, all[ i ], 0.0 ) - sample( B, all[ i ], 0.0 ) };
if( before * after < 0.0 ) {
result.push_back( all[ i - 1 ] + ( all[ i ] - all[ i - 1 ] ) * before / ( before - after ) );
}
}
return result;
}
inline line bound( line const &A, line const &B, bool const Lower ) {
if( A.empty() || B.empty() ) {
return ( A.empty() ? B : A );
}
auto all { stations( A, B ) };
auto const crossed { meetings( A, B ) };
all.insert( all.end(), crossed.begin(), crossed.end() );
std::sort( all.begin(), all.end() );
line result;
for( auto const position : all ) {
auto const a { sample( A, position, 0.0 ) };
auto const b { sample( B, position, 0.0 ) };
result.emplace_back( position, Lower ? std::min( a, b ) : std::max( a, b ) );
}
return result;
}
inline line middle( line const &A, line const &B ) {
line result;
for( auto const position : stations( A, B ) ) {
result.emplace_back( position, 0.5 * ( sample( A, position, 0.0 ) + sample( B, position, 0.0 ) ) );
}
return result;
}
// both lines added up, each going on flat past its ends
inline line sum( line const &A, line const &B ) {
if( A.empty() || B.empty() ) {
return ( A.empty() ? B : A );
}
line result;
for( auto const position : stations( A, B ) ) {
if( false == result.empty() && position - result.back().x < 1e-9 ) {
continue;
}
result.emplace_back( position, sample( A, position, 0.0 ) + sample( B, position, 0.0 ) );
}
return result;
}
// the line without the knots lying on the straight between the knots kept around them, within Tolerance
inline line simplified( line const &Line, double const Tolerance ) {
if( Line.size() < 3 ) {
return Line;
}
line result { Line.front() };
std::size_t kept { 0 };
for( std::size_t i = 2; i < Line.size(); ++i ) {
auto const &a { Line[ kept ] };
auto const &b { Line[ i ] };
for( std::size_t j = kept + 1; j < i; ++j ) {
auto const t { ( Line[ j ].x - a.x ) / std::max( b.x - a.x, 1e-9 ) };
if( std::abs( glm::mix( a.y, b.y, t ) - Line[ j ].y ) > Tolerance ) {
kept = i - 1;
result.push_back( Line[ kept ] );
break;
}
}
}
result.push_back( Line.back() );
return result;
}
} // namespace knots

1167
world/RailCarve.cpp Normal file

File diff suppressed because it is too large Load Diff

1032
world/RailCrossings.cpp Normal file

File diff suppressed because it is too large Load Diff

1324
world/RailLayer.cpp Normal file

File diff suppressed because it is too large Load Diff

219
world/RailLayout.h Normal file
View File

@@ -0,0 +1,219 @@
/*
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 <array>
#include <vector>
#include <glm/glm.hpp>
#include "utilities/Classes.h"
#include "world/Knots.h"
class TTrack;
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 wheel_play { 0.005 }; // how far the wheels wander across each way, by the play of their flanges in the gauge
double constexpr nose_tip { 0.012 };
double constexpr middle_angle { 0.35 };
double constexpr wide_angle { 0.785 };
double constexpr guard_reach { 1.0 };
double constexpr guard_flare { 0.6 };
double constexpr casting_reach { 0.3 };
double constexpr square_slope { 3.0 };
double constexpr spacer_length { 0.12 };
double constexpr spacer_pitch { 0.5 };
double constexpr check_flangeway { 0.041 };
double constexpr guard_planing { 0.015 }; // how far the ends of guard rails are planed off their face toward the running rail
double constexpr guard_bevel { 1.7320508 }; // how far along the flared ends of guard rails are cut back across them, from their face toward the running rail: 30 degrees to the rail
double constexpr blade_throw { 0.160 };
double constexpr blade_clearance { 0.058 };
double constexpr blade_point { 0.003 };
double constexpr blade_nose { 0.005 };
double constexpr blade_planed { 0.125 };
double constexpr blade_tangent { 0.00175 };
double constexpr blade_mark { 0.010 };
double constexpr blade_gap { 0.0005 };
double constexpr blade_lowered { 0.050 };
double constexpr blade_spacing { 0.5 };
double constexpr blade_runon_margin { 0.2 }; // how far short of the end of the plain track a blade running on into it ends
// 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 };
};
struct switch_blade {
bool valid { false };
bool rigid { false };
double tip { 0.0 };
double full { 0.0 };
double heel { 0.0 };
double drop { 0.0 };
double opening { 0.0 };
double openingat { 0.0 };
std::vector<glm::dvec2> gauge;
std::vector<glm::dvec2> extents;
std::vector<glm::dvec2> drops;
std::vector<glm::dvec2> bends;
};
// 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 };
};
// 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 shoulder { 0.005 }; // how far in from the outer edge of the head its top is rounded off
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 };
};
// a check rail beside a rail, how far its ends are bent 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
inline double check_flare( check_rail const &Check ) {
return std::min( guard_flare, 0.4 * ( Check.to - Check.from ) );
}
// how the end of a part of a rail planed by the rails around it is made: square where it's put, a point where the head is planed down to nothing
// (a wing rail), blunt where it's wide enough for the wheels running at it (a leg of the nose, a point rail), sharp where it's put as it gets wider
// (a leg of a movable nose), mitred against the end of the other rail (an obtuse crossing)
enum class rail_end {
square,
point,
blunt,
sharp,
mitre
};
// 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 };
knots::line endoffsets, endextents, startoffsets, startextents;
knots::line 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 };
knots::line endinner, startinner;
bool endcap { true };
bool startcap { true };
knots::line endworn, startworn; // from how far out from the gauge line the wheels of a rail beside roll on the head, where it's planed flush against that rail
material_handle endwornby { 0 }; // what the wheels rolling on it from beside polish the head to, the brightest of them
material_handle startwornby { 0 };
// what carving the parts against the rails around takes from the design of the crossing
bool carved { false }; // the lines of the parts are left to carving
rail_end endkind { rail_end::square };
rail_end startkind { rail_end::square };
double endanchor { 0.0 }; // from where on a part planed down to its point can end
double startanchor { 0.0 };
double endrunon { 0.0 }; // how far past its end a wing rail can go on to be planed down to its point
double startrunon { 0.0 };
knots::line endcarve, startcarve; // offsets the parts are carved with where they aren't those they're laid with: the legs of a movable nose, as they lie with the first path set
bool throwable { false }; // the part going on moves with the switch: a leg of a movable nose
glm::dvec2 mark { 0.0 }; // where the rails cross, in plan: the parts at an obtuse crossing aren't carved against the other rail's there, they're designed to meet
bool obtuse { false };
};
struct rail_plan {
std::vector<rail_cut> cuts;
};
struct rail_tread {
double from { 0.0 };
double to { 0.0 };
material_handle material { 0 };
int anchor { 0 };
knots::line edge;
knots::line gauge;
bool below { false };
double fade { tread_fade };
};
// 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 };
std::vector<rail_tread> treads;
knots::line castings;
};
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 };
knots::line inner, outer;
bool capfrom { false };
bool capto { false };
double lift { 0.0 };
bool web { 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 ];
knots::line blocks[ 2 ][ 2 ];
std::vector<rail_tread> treads[ 2 ][ 2 ];
std::vector<guard_rail> guards[ 2 ][ 2 ];
knots::line 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
};
} // namespace track_rails

207
world/RailMaterials.cpp Normal file
View File

@@ -0,0 +1,207 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/TrackRails.h"
#include "rendering/renderer.h"
#include "stb/stb_image.h"
namespace track_rails {
material_handle cut_material( material_handle const Material ) {
static auto const unrolled { std::getenv( "EU07_EDITOR_UNROLLED" ) != nullptr };
return unrolled ? GfxRenderer->Fetch_Material( "editor_unrolled" ) : GfxRenderer->Matte_Material( Material );
}
material_handle tint_material( material_handle const Material, glm::vec3 const &Colour ) {
return GfxRenderer->Tinted_Material( Material, Colour );
}
namespace {
struct colour_mean {
double sum { 0.0 };
glm::dvec3 colour { 0.0 };
std::size_t count { 0 };
void add( float const Red, float const Green, float const Blue ) {
sum += 0.2126 * Red + 0.7152 * Green + 0.0722 * Blue;
colour += glm::pow( glm::dvec3{ Red, Green, Blue } / 255.0, glm::dvec3{ 2.2 } );
++count; }
};
std::pair<int, int> band_columns( glm::vec2 const &Band, int const Width ) {
auto const first { std::clamp( static_cast<int>( std::floor( std::min( Band.x, Band.y ) * Width ) ), 0, Width - 1 ) };
auto const last { std::clamp( static_cast<int>( std::ceil( std::max( Band.x, Band.y ) * Width ) ) - 1, first, Width - 1 ) };
return { first, last };
}
glm::vec3 unpack_565( unsigned const Color ) {
return glm::vec3{ ( ( Color >> 11 ) & 31 ) * 255.f / 31.f, ( ( Color >> 5 ) & 63 ) * 255.f / 63.f, ( Color & 31 ) * 255.f / 31.f };
}
void add_dxt_block( unsigned char const *Block, bool const Alpha, int const Column, int const First, int const Last, colour_mean &Mean ) {
auto const low { static_cast<unsigned>( Block[ 0 ] | Block[ 1 ] << 8 ) };
auto const top { static_cast<unsigned>( Block[ 2 ] | Block[ 3 ] << 8 ) };
auto const opaque { Alpha || low > top };
glm::vec3 const colors[ 4 ] {
unpack_565( low ),
unpack_565( top ),
opaque ? ( 2.f * unpack_565( low ) + unpack_565( top ) ) / 3.f : 0.5f * ( unpack_565( low ) + unpack_565( top ) ),
opaque ? ( unpack_565( low ) + 2.f * unpack_565( top ) ) / 3.f : glm::vec3{ 0.f } };
auto const bits { static_cast<std::uint32_t>( Block[ 4 ] ) | static_cast<std::uint32_t>( Block[ 5 ] ) << 8 | static_cast<std::uint32_t>( Block[ 6 ] ) << 16 | static_cast<std::uint32_t>( Block[ 7 ] ) << 24 };
for( int texel = 0; texel < 16; ++texel ) {
auto const x { Column * 4 + texel % 4 };
auto const index { ( bits >> ( 2 * texel ) ) & 3 };
if( x < First || x > Last || ( false == opaque && index == 3 ) ) {
continue;
}
Mean.add( colors[ index ].r, colors[ index ].g, colors[ index ].b );
}
}
void sample_dds( std::string const &File, glm::vec2 const &Band, colour_mean &Mean ) {
std::ifstream input( File, std::ios::binary );
std::array<unsigned char, 128> header {};
if( !input.read( reinterpret_cast<char *>( header.data() ), header.size() ) || false == std::equal( header.begin(), header.begin() + 4, "DDS " ) ) {
return;
}
auto const word = [&]( std::size_t const Offset ) {
return static_cast<std::uint32_t>( header[ Offset ] ) | static_cast<std::uint32_t>( header[ Offset + 1 ] ) << 8 | static_cast<std::uint32_t>( header[ Offset + 2 ] ) << 16 | static_cast<std::uint32_t>( header[ Offset + 3 ] ) << 24; };
auto const height { static_cast<int>( word( 12 ) ) };
auto const width { static_cast<int>( word( 16 ) ) };
auto const fourcc { word( 84 ) };
auto const blocksize { fourcc == 0x31545844u ? 8 : ( fourcc == 0x33545844u || fourcc == 0x35545844u ) ? 16 : 0 };
if( blocksize == 0 || width <= 0 || height <= 0 ) {
return;
}
auto const wide { ( width + 3 ) / 4 };
auto const high { ( height + 3 ) / 4 };
std::vector<unsigned char> data( static_cast<std::size_t>( wide ) * high * blocksize );
if( !input.read( reinterpret_cast<char *>( data.data() ), data.size() ) ) {
return;
}
auto const [ first, last ] { band_columns( Band, width ) };
for( int row = 0; row < high; ++row ) {
for( int column = first / 4; column <= last / 4; ++column ) {
add_dxt_block( &data[ ( static_cast<std::size_t>( row ) * wide + column ) * blocksize + ( blocksize - 8 ) ], blocksize == 16, column, first, last, Mean );
}
}
}
void sample_image( std::string const &File, glm::vec2 const &Band, colour_mean &Mean ) {
int width, height, components;
auto *pixels { stbi_load( File.c_str(), &width, &height, &components, 4 ) };
if( pixels == nullptr ) {
return;
}
auto const [ first, last ] { band_columns( Band, width ) };
for( int y = 0; y < height; ++y ) {
for( int x = first; x <= last; ++x ) {
auto const *pixel { &pixels[ ( static_cast<std::size_t>( y ) * width + x ) * 4 ] };
Mean.add( pixel[ 0 ], pixel[ 1 ], pixel[ 2 ] );
}
}
stbi_image_free( pixels );
}
} // namespace
std::pair<float, glm::vec3> const &tread_sample( material_handle const Material, glm::vec2 const &Band ) {
static std::map<std::tuple<material_handle, float, float>, std::pair<float, glm::vec3>> samples;
auto const key { std::make_tuple( Material, Band.x, Band.y ) };
if( auto const lookup { samples.find( key ) }; lookup != samples.end() ) {
return lookup->second;
}
auto &result { samples[ key ] };
result = { -1.f, glm::vec3{ 0.f } };
if( Material == null_handle ) {
return result;
}
auto const texture { GfxRenderer->Material( Material )->GetTexture( 0 ) };
if( texture == null_handle ) {
return result;
}
auto const &image { GfxRenderer->Texture( texture ) };
auto const type { std::string( image.get_type() ) };
auto const file { std::string( image.get_name() ) + type };
colour_mean mean;
if( type == ".dds" ) {
sample_dds( file, Band, mean );
}
else {
sample_image( file, Band, mean );
}
if( mean.count > 0 ) {
result = { static_cast<float>( mean.sum / mean.count ), glm::vec3{ mean.colour / static_cast<double>( mean.count ) } };
}
return result;
}
float tread_brightness( material_handle const Material, glm::vec2 const &Band ) {
return tread_sample( Material, Band ).first;
}
glm::vec3 tread_colour( material_handle const Material, glm::vec2 const &Band ) {
return tread_sample( Material, Band ).second;
}
} // namespace track_rails
material_handle TTrack::rail_material( int const Path ) const {
return ( Path == 1 && eType == tt_Switch ? m_material2 : m_material1 );
}
glm::vec2 TTrack::tread_band() const {
glm::vec2 band { 0.45f, 0.55f };
if( m_profiles.empty() ) {
return band;
}
auto const &outline { track_rail_profile( m_profile1.second ) };
auto const count { outline.size() / 2 };
auto top { -1e9f };
for( std::size_t i = 0; i < count; ++i ) {
top = std::max( top, outline[ i ].position.y );
}
glm::vec2 found { 1e9f, -1e9f };
for( std::size_t i = 0; i < count; ++i ) {
if( outline[ i ].position.y > top - 0.0005f ) {
found = { std::min( found.x, outline[ i ].texture.s ), std::max( found.y, outline[ i ].texture.s ) };
}
}
if( found.x > found.y ) {
return band;
}
auto const middle { 0.5f * ( found.x + found.y ) };
auto const half { std::max( 0.5f * ( found.y - found.x ), 0.01f ) };
return { middle - half, middle + half };
}
material_handle TTrack::shared_tread( int const Path, TTrack const &Other, int const Otherpath ) const {
auto const own { rail_material( Path ) };
auto const theirs { Other.rail_material( Otherpath ) };
if( theirs == null_handle || theirs == own ) {
return own;
}
return ( track_rails::tread_brightness( theirs, Other.tread_band() ) > track_rails::tread_brightness( own, tread_band() ) ? theirs : own );
}

824
world/RailPlan.cpp Normal file
View File

@@ -0,0 +1,824 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/TrackRails.h"
using namespace track_rails;
namespace {
// a wing rail ending right beside a check rail of the rail it runs along goes on in its place up to the far end of it, past the end of the wing
std::vector<glm::dvec2> wing_extension( TTrack &Track, track_rails::check_rail_set const &Checks, int const Path, int const Side, double const At, double const Toward, double const Slope, TTrack &Partner, int const Partnerpath, int const Partnerside, double const Partnerat, double const Partnerdirection ) {
std::vector<glm::dvec2> knots;
auto const partnerchecks { &Partner == &Track ? Checks : rail_checks( Partner ) };
auto const wingend { Partnerat + Partnerdirection * wing_reach( Slope ) };
auto farend { 0.0 };
if( false == replaced_check( partnerchecks[ Partnerpath ][ Partnerside > 0 ], wingend, Partnerdirection, farend ) ) {
return knots;
}
auto const theirs { make_gauge_line( path_segment( Partner, Partnerpath ), Partnerside * ( 0.5 * std::abs( Partner.fTrackWidth ) - flangeway ), 0.0 ) };
auto const ours { make_gauge_line( path_segment( Track, Path ), Side * ( 0.5f * std::abs( Track.fTrackWidth ) ), 0.0 ) };
auto s { At - Toward * wing_reach( Slope ) };
auto const count { std::max( 1, static_cast<int>( std::ceil( std::abs( farend - wingend ) / 0.5 ) ) ) };
for( int i = 1; i <= count; ++i ) {
glm::dvec3 target, tangent, point;
theirs.at( glm::mix( wingend, farend, static_cast<double>( i ) / count ), target, tangent );
for( int step = 0; step < 4; ++step ) {
ours.at( s, point, tangent );
s += glm::dot( plan( target - point ), glm::normalize( plan( tangent ) ) );
}
ours.at( s, point, tangent );
knots.emplace_back( s, -glm::dot( plan( target - point ), plan( ours.normal( s, Side ) ) ) );
}
return knots;
}
// a wing rail bent out along the flangeway of the other rail of its frog, from where that closes in on ours on to Wingend
knots::line flangeway_wing( TTrack const &Track, track_rails::rail_crossing const &Crossing, double const Throat, double const Wingend ) {
auto const &ours { rail_line( Track, Crossing.path, Crossing.side > 0, false ) };
auto const &theirs { rail_line( *Crossing.track, Crossing.otherpath, Crossing.otherside > 0, false ) };
auto const direction { Wingend > Throat ? 1.0 : -1.0 };
auto const start { Throat - direction * 2.0 };
auto const count { std::max( 2, static_cast<int>( std::ceil( std::abs( Wingend - start ) / 0.05 ) ) ) };
glm::dvec3 point, tangent;
ours.at( start, point, tangent );
auto distance { nearest_distance( theirs, point ) };
knots::line wing;
for( int i = 0; i <= count; ++i ) {
auto const at { glm::mix( start, Wingend, static_cast<double>( i ) / count ) };
ours.at( at, point, tangent );
auto const outwards { -glm::normalize( plan( ours.normal( at, Crossing.side ) ) ) };
auto offset { 0.0 };
for( int iteration = 0; iteration < 3; ++iteration ) {
glm::dvec3 const shifted { point.x + outwards.x * offset, point.y, point.z + outwards.y * offset };
auto const across { lateral( theirs, Crossing.otherside, shifted, distance ) };
auto const facing { glm::dot( outwards, -glm::normalize( plan( theirs.normal( distance, Crossing.otherside ) ) ) ) };
if( std::abs( facing ) < 0.1 ) {
break;
}
offset += ( -flangeway - across ) / facing;
}
wing.emplace_back( at, offset );
}
// from where the flangeway of the other rail reaches the gauge line of ours
knots::line result;
std::size_t first { 0 };
while( first + 1 < wing.size() && wing[ first + 1 ].y <= 0.0 ) {
++first;
}
if( wing[ first ].y <= 0.0 && first + 1 < wing.size() ) {
auto const &a { wing[ first ] };
auto const &b { wing[ first + 1 ] };
result.emplace_back( glm::mix( a.x, b.x, a.y / ( a.y - b.y ) ), 0.0 );
++first;
}
else {
result.emplace_back( start, 0.0 );
}
for( auto i { first }; i < wing.size(); ++i ) {
result.emplace_back( wing[ i ].x, std::max( wing[ i ].y, 0.0 ) );
}
knots::sort( result );
return knots::simplified( result, 0.0001 );
}
knots::line rolled_wing( knots::line const &Wing ) {
knots::line rolled;
for( auto const &knot : Wing ) {
rolled.emplace_back( knot.x, rolled_band - knot.y );
}
return rolled;
}
// acute frog as designed for carving: the wing rail bent out along the flangeway of the other rail ends in a point, or run on a bit past its end into
// what planes it to one; the leg of the nose begins blunt past where the rails cross
void designed_acute( track_rails::rail_plan &Plan, TTrack const &Track, track_rails::rail_crossing const &Crossing, double const Toward, double const Slope, double const Wingend ) {
auto const throat { Crossing.at + Toward * flangeway / Slope };
auto const wing { flangeway_wing( Track, Crossing, throat, Wingend ) };
track_rails::rail_cut cut;
cut.at = Crossing.at;
cut.carved = true;
if( Toward < 0.0 ) {
cut.end = Wingend;
cut.endoffsets = wing;
cut.endrolled = rolled_wing( wing );
cut.endkind = track_rails::rail_end::point;
cut.endanchor = throat;
cut.endrunon = 1.0;
cut.start = Crossing.at;
cut.startkind = track_rails::rail_end::blunt;
}
else {
cut.end = Crossing.at;
cut.endkind = track_rails::rail_end::blunt;
cut.start = Wingend;
cut.startoffsets = wing;
cut.startrolled = rolled_wing( wing );
cut.startkind = track_rails::rail_end::point;
cut.startanchor = throat;
cut.startrunon = 1.0;
}
Plan.cuts.push_back( cut );
}
// obtuse crossing as designed for carving: the rail ends mitred where it meets the other one, the knuckle, and starts again where its point keeps
// out of the flangeway of the other rail
void designed_obtuse( track_rails::rail_plan &Plan, double const At, double const Into, glm::dvec2 const &Mark ) {
auto const bend { knuckle_bend( Into ) };
track_rails::rail_cut cut;
cut.at = At;
cut.end = At;
cut.start = At;
cut.carved = true;
cut.obtuse = true;
cut.mark = Mark;
if( Into > 0.0 ) {
cut.endbend = -bend;
cut.endkind = track_rails::rail_end::mitre;
cut.startkind = track_rails::rail_end::blunt;
}
else {
cut.endkind = track_rails::rail_end::blunt;
cut.startbend = bend;
cut.startkind = track_rails::rail_end::mitre;
}
Plan.cuts.push_back( cut );
}
// a guard rail of a crossing at a middle angle gives way to the running rail and the flangeway of the other track
struct wide_cut {
double from { 0.0 };
double to { 0.0 };
double inner { 0.0 };
double outer { 0.0 };
};
void wide( track_rails::rail_plan &Plan, track_rails::rail_crossing const &Crossing, wide_cut const &Cut ) {
auto const at { Crossing.at };
auto const into { Crossing.into };
auto const facing { Crossing.facing };
auto const low { ( Cut.from + std::min( Cut.inner * facing, Cut.outer * facing ) - 0.005 ) / into };
auto const high { ( Cut.to + std::max( Cut.inner * facing, Cut.outer * facing ) + 0.005 ) / into };
track_rails::rail_cut cut;
cut.at = at;
cut.end = at + std::min( low, high );
cut.start = at + std::max( low, high );
cut.endmiter = { at + ( into > 0.0 ? Cut.from - 0.005 : Cut.to + 0.005 ) / into, facing / into };
cut.startmiter = { at + ( into > 0.0 ? Cut.to + 0.005 : Cut.from - 0.005 ) / into, facing / into };
Plan.cuts.push_back( cut );
}
track_rails::rail_cut corner_cut( track_rails::rail_crossing const &Crossing, double const Face, double const Toward, double const Head, double const Other ) {
auto const mitre = [&]( double const Edge, double const Flange ) {
auto const slope { Flange * Other / ( Toward * Head ) };
return glm::dvec2 { Crossing.at + ( Edge - Face * slope ) / Crossing.into, ( slope + Crossing.facing ) / Crossing.into }; };
auto const rail { mitre( 0.0, 1.0 ) };
auto const guard { mitre( flangeway, -1.0 ) };
auto const rising { Crossing.into > 0.0 };
track_rails::rail_cut cut;
cut.at = Crossing.at;
cut.endmiter = ( rising ? rail : guard );
cut.startmiter = ( rising ? guard : rail );
cut.end = cut.endmiter.x + cut.endmiter.y * Face;
cut.start = cut.startmiter.x + cut.startmiter.y * Face;
cut.endbend = cut.endmiter.y;
cut.startbend = cut.startmiter.y;
return cut;
}
std::vector<track_rails::rail_fill> spacer_fills( track_rails::rail_fill const &Whole, double const From, double const To, knots::line const &Castings ) {
knots::line pieces;
for( auto const &casting : Castings ) {
if( false == pieces.empty() && casting.x <= pieces.back().y ) {
pieces.back().y = std::max( pieces.back().y, casting.y );
}
else {
pieces.push_back( casting );
}
}
knots::line spacers;
auto const place = [&]( double const Start, double const End, double const Step ) {
auto const count { static_cast<int>( std::floor( ( End - Start - 0.5 * spacer_length ) / spacer_pitch ) ) };
for( int i = 1; i <= count; ++i ) {
auto const at { ( Step > 0.0 ? Start : End ) + Step * i * spacer_pitch };
spacers.emplace_back( at - 0.5 * spacer_length, at + 0.5 * spacer_length );
} };
auto const first { From + spacer_length };
auto const last { To - spacer_length };
if( pieces.empty() ) {
place( first - spacer_pitch, last, 1.0 );
}
else {
place( first, pieces.front().x, -1.0 );
for( std::size_t i = 1; i < pieces.size(); ++i ) {
auto const middle { 0.5 * ( pieces[ i - 1 ].y + pieces[ i ].x ) };
place( pieces[ i - 1 ].y, middle, 1.0 );
place( middle, pieces[ i ].x, -1.0 );
}
place( pieces.back().y, last, 1.0 );
}
pieces.insert( pieces.end(), spacers.begin(), spacers.end() );
std::vector<track_rails::rail_fill> fills;
for( auto const &piece : pieces ) {
auto fill { Whole };
fill.from = std::max( piece.x, From );
fill.to = std::min( piece.y, To );
fill.capfrom = true;
fill.capto = true;
if( fill.to - fill.from > 0.01 ) {
fills.push_back( fill );
}
}
return fills;
}
std::vector<track_rails::rail_fill> guard_fills( track_rails::guard_rail const &Guard, track_rails::rail_size const &Size ) {
auto const offset { Size.web - ( flangeway + Size.head ) };
track_rails::rail_fill whole;
whole.web = true;
whole.lift = ( Guard.through ? 0.0 : -0.001 );
whole.inner = { { Guard.from, offset }, { Guard.to, offset } };
whole.outer = { { Guard.from, Size.web }, { Guard.to, Size.web } };
return spacer_fills( whole, Guard.from, Guard.to, Guard.castings );
}
std::vector<track_rails::rail_fill> check_fills( track_rails::check_rail const &Check, track_rails::rail_size const &Size ) {
auto const from { Check.from + check_flare( Check ) };
auto const to { Check.to - check_flare( Check ) };
track_rails::rail_fill whole;
whole.web = true;
whole.inner = { { from, -check_flangeway }, { to, -check_flangeway } };
whole.outer = { { from, Size.web }, { to, Size.web } };
return spacer_fills( whole, from, to, {} );
}
bool goes_through( TTrack const &Track, TTrack const &Other ) {
if( Track.name() != Other.name() ) {
return Track.name() < Other.name();
}
auto const here { Track.location() };
auto const there { Other.location() };
return ( here.x != there.x ? here.x < there.x : here.z < there.z );
}
track_rails::rail_band groove_band( double const At, double const Into, double const Facing, bool const Through, track_rails::rail_size const &Other ) {
auto const seam { 0.001 };
track_rails::rail_band band { At, Into, Facing };
band.whole = ( false == Through );
band.from = ( Through ? 0.0 : seam - Other.tread );
band.to = ( Through ? ( Other.grooved ? -Other.lip : flangeway ) : -Other.ledge - seam );
return band;
}
void vee_fill( std::vector<track_rails::rail_fill> &Fills, double const At, double const Into, double const Facing, track_rails::rail_size const &Own, track_rails::rail_size const &Other, bool const Outwards ) {
auto const seam { 0.001 };
auto const sign { Outwards ? 1.0 : -1.0 };
auto const edge { Outwards ? std::max( Own.head, Own.crown ) : Own.rim };
auto const flat { Outwards ? Own.tread : Own.ledge };
auto const inside { Outwards == ( Facing > 0.0 ) };
auto const closest { inside ? -Other.rim : -std::max( Other.head, Other.crown ) };
auto const rounded { inside ? Other.ledge - Other.rim : std::max( Other.head, Other.crown ) - Other.tread };
auto const own { Outwards ? Own.high - edge : edge - Own.low };
auto const other { inside ? Other.rim - Other.low : Other.high - std::max( Other.head, Other.crown ) };
auto const slant { std::max( std::abs( Facing ), 0.1 ) };
auto const gap { own + other / slant };
auto const meets = [&]( double const Width ) {
return At + ( closest + Facing * ( edge + sign * Width ) ) / Into; };
auto const apex { meets( 0.0 ) };
auto const end { meets( gap ) };
auto const reach { seam + rounded / slant };
track_rails::rail_fill fill;
fill.from = std::min( apex, end );
fill.to = std::max( apex, end );
fill.capfrom = ( end < apex );
fill.capto = ( end > apex );
if( Own.grooved ) {
fill.lift = std::min( Outwards ? Own.crest : Own.liptop, inside ? Other.liptop : Other.crest ) - Own.liptop;
}
std::vector<glm::dvec2> line { { apex, edge + sign * reach }, { end, edge + sign * ( gap + reach ) } };
knots::sort( line );
std::vector<glm::dvec2> fixed { { fill.from, flat - sign * seam }, { fill.to, flat - sign * seam } };
fill.outer = ( Outwards ? line : fixed );
fill.inner = ( Outwards ? fixed : line );
Fills.push_back( fill );
}
track_rails::rail_tread wide_tread( track_rails::rail_crossing const &Crossing, material_handle const Material, double const Base, double const Head, double const Reach ) {
auto const facing { std::abs( Crossing.facing ) > 0.02 ? Crossing.facing : std::copysign( 0.02, Crossing.facing ) };
auto const slope { Crossing.into / facing };
track_rails::rail_tread tread { 1e9, -1e9, Material };
if( std::abs( slope ) > square_slope ) {
for( auto const edge : { 0.0, -Reach } ) {
auto const distance { Crossing.at + ( edge + Crossing.facing * ( Base + 0.5 * Head ) ) / Crossing.into };
tread.from = std::min( tread.from, distance );
tread.to = std::max( tread.to, distance );
}
return tread;
}
for( auto const across : { Base, Base + Head } ) {
for( auto const edge : { tread_fade, -Reach - tread_fade } ) {
auto const distance { Crossing.at + ( edge + Crossing.facing * across ) / Crossing.into };
tread.from = std::min( tread.from, distance - 0.01 );
tread.to = std::max( tread.to, distance + 0.01 );
}
}
auto const line = [&]( double const Edge ) {
auto const across = [&]( double const Distance ) {
return ( ( Distance - Crossing.at ) * Crossing.into - Edge ) / facing - Base; };
return knots::line { { tread.from, across( tread.from ) }, { tread.to, across( tread.to ) } }; };
tread.gauge = line( 0.0 );
tread.edge = line( -Reach );
tread.below = ( facing > 0.0 );
tread.fade = tread_fade * std::sqrt( 1.0 + slope * slope );
return tread;
}
std::vector<glm::dvec2> tread_edge( TTrack const &Track, int const Path, int const Side, TTrack const &Other, int const Otherpath, int const Otherside, double const From, double const To, bool &Below, std::vector<glm::dvec2> &Gauge ) {
auto const &ours { rail_line( Track, Path, Side > 0, false ) };
auto const &theirs { rail_line( Other, Otherpath, Otherside > 0, false ) };
glm::dvec3 point, tangent;
ours.at( From, point, tangent );
auto otherat { nearest_distance( theirs, point ) };
std::vector<glm::dvec2> knots;
auto below { 0 };
auto const count { std::max( 2, static_cast<int>( std::ceil( ( To - From ) / 0.1 ) ) + 1 ) };
for( int i = 0; i < count; ++i ) {
auto distance { glm::mix( From, To, static_cast<double>( i ) / ( count - 1 ) ) };
ours.at( distance, point, tangent );
lateral( theirs, Otherside, point, otherat );
glm::dvec3 line;
theirs.at( otherat, line, tangent );
auto const normal { theirs.normal( otherat, Otherside ) };
auto at { distance };
auto const gauge { lateral( ours, Side, line, at ) };
Gauge.emplace_back( at, gauge );
at = distance;
auto const edge { lateral( ours, Side, line - normal * rolled_band, at ) };
below += ( gauge < edge ? 1 : -1 );
knots.emplace_back( at, edge );
}
knots::sort( knots );
knots::sort( Gauge );
Below = ( below > 0 );
return knots;
}
} // namespace
namespace {
void merge_guard( std::vector<track_rails::guard_rail> &Guards, track_rails::guard_rail const &Guard ) {
for( auto &span : Guards ) {
if( Guard.from < span.to + 2.0 && Guard.to > span.from - 2.0 ) {
span.from = std::min( span.from, Guard.from );
span.to = std::max( span.to, Guard.to );
span.plan.cuts.insert( span.plan.cuts.end(), Guard.plan.cuts.begin(), Guard.plan.cuts.end() );
span.through = ( span.through && Guard.through );
span.treads.insert( span.treads.end(), Guard.treads.begin(), Guard.treads.end() );
span.castings.insert( span.castings.end(), Guard.castings.begin(), Guard.castings.end() );
knots::sort( span.castings );
return;
}
}
Guards.push_back( Guard );
}
void join_opposite_guards( std::vector<track_rails::guard_rail> ( &Guards )[ 2 ] ) {
knots::line spans[ 2 ];
for( int side = 0; side < 2; ++side ) {
for( auto const &guard : Guards[ side ] ) {
spans[ side ].emplace_back( guard.from, guard.to );
}
}
for( int side = 0; side < 2; ++side ) {
for( auto &guard : Guards[ side ] ) {
for( auto const &opposite : spans[ 1 - side ] ) {
if( opposite.x < guard.to && opposite.y > guard.from ) {
guard.from = std::min( guard.from, opposite.x );
guard.to = std::max( guard.to, opposite.y );
}
}
}
}
}
bool framed( track_rails::rail_crossing const &Corner, track_rails::rail_crossing const &Other, int const Side ) {
return Other.track == Corner.track && Other.path == Corner.path && Other.side == Side && Other.otherpath == Corner.otherpath;
}
} // namespace
class TTrack::rail_planner {
public:
// constructors
rail_planner( TTrack &Track, bool const Crossings, bool const Carve );
// methods
rail_layout lay_out();
private:
// methods
void add_crossing( rail_crossing const &Crossing );
void cross_grooved( rail_crossing const &Crossing, rail_size const &Other );
void cross_wide( rail_crossing const &Crossing, rail_size const &Other );
void acute_frog( rail_crossing const &Crossing, double const Toward );
void movable_frog( rail_crossing const &Crossing, double const Push );
void add_vee( rail_crossing const &Crossing, rail_size const &Other, bool const Filled );
void frame_middle_crossings();
track_rails::guard_rail frame_guard( rail_crossing const &Start, rail_crossing const &End ) const;
void switch_frog();
void add_tread( int const Path, int const Side, glm::dvec2 const &Span, TTrack const &Other, int const Otherpath, int const Anchor = 0, int const Otherside = 0 );
glm::dvec2 shared_span( rail_crossing const &Crossing, rail_size const &Other ) const;
rail_plan &plan_of( rail_crossing const &Crossing ) {
return m_layout.plans[ Crossing.path ][ Crossing.side > 0 ]; }
// members
TTrack &m_track;
bool const m_crossings;
bool const m_carve; // the parts of the rails at crossings carved out of what lies around them, or only designed
rail_size const m_size;
double const m_guardoffset;
rail_layout m_layout;
std::vector<rail_crossing> m_corners;
};
TTrack::rail_planner::rail_planner( TTrack &Track, bool const Crossings, bool const Carve ) :
m_track { Track },
m_crossings { Crossings },
m_carve { Carve },
m_size { Track.rail_dimensions() },
m_guardoffset { -( flangeway + m_size.head ) }
{}
rail_layout TTrack::rail_planner::lay_out() {
if( m_carve && m_crossings ) {
if( auto const *designed { designed_layout( m_track ) } ) {
m_layout = *designed;
carve_plans( m_track, m_layout );
return m_layout;
}
}
std::vector<rail_crossing> crossings;
if( m_crossings ) {
crossings = m_track.rail_crossings();
}
m_layout.checks = ( m_crossings ? rail_checks( m_track ) : m_track.check_rails( crossings ) );
if( m_crossings ) {
for( auto const &crossing : crossings ) {
add_crossing( crossing );
}
for( auto &pathguards : m_layout.guards ) {
join_opposite_guards( pathguards );
}
for( int path = 0; path < 2; ++path ) {
for( int side = 0; side < 2; ++side ) {
for( auto const &guard : m_layout.guards[ path ][ side ] ) {
auto const fills { guard_fills( guard, m_size ) };
m_layout.fills[ path ][ side ].insert( m_layout.fills[ path ][ side ].end(), fills.begin(), fills.end() );
}
}
}
frame_middle_crossings();
}
if( m_track.eType == tt_Switch && cut_rails( m_track ) ) {
for( int path = 0; path < 2; ++path ) {
add_tread( path, -m_track.blade_side( path ), { 0.0, blade_length( m_track, 1 - path ) }, m_track, 1 - path, 0, -m_track.blade_side( path ) );
}
}
if( m_track.eType == tt_Switch ) {
switch_frog();
}
for( int path = 0; path < 2; ++path ) {
for( int side = 0; side < 2; ++side ) {
for( auto const &check : m_layout.checks[ path ][ side ] ) {
if( false == replaced_by_wing( check, m_layout.wingends[ path ][ side ] ) ) {
auto const fills { check_fills( check, m_size ) };
m_layout.fills[ path ][ side ].insert( m_layout.fills[ path ][ side ].end(), fills.begin(), fills.end() );
}
}
}
}
if( m_carve && m_crossings ) {
carve_plans( m_track, m_layout );
}
return m_layout;
}
void TTrack::rail_planner::add_crossing( rail_crossing const &Crossing ) {
if( std::find( m_layout.crossed.begin(), m_layout.crossed.end(), Crossing.track ) == m_layout.crossed.end() ) {
m_layout.crossed.push_back( Crossing.track );
}
auto const theirs { Crossing.track->rail_dimensions() };
if( m_size.grooved || theirs.grooved ) {
cross_grooved( Crossing, theirs );
return;
}
if( Crossing.angle > middle_angle && Crossing.angle <= wide_angle ) {
m_corners.push_back( Crossing );
}
if( Crossing.angle > wide_angle ) {
cross_wide( Crossing, theirs );
}
else if( Crossing.facing < 0.0 ) {
acute_frog( Crossing, Crossing.into > 0.0 ? 1.0 : -1.0 );
}
else {
glm::dvec3 point, tangent;
rail_line( m_track, Crossing.path, Crossing.side > 0, false ).at( Crossing.at, point, tangent );
designed_obtuse( plan_of( Crossing ), Crossing.at, Crossing.into, plan( point ) );
auto const reach { ( rolled_band + m_size.head + tread_fade ) / std::max( std::tan( Crossing.angle ), 0.01 ) + 0.2 };
add_tread( Crossing.path, Crossing.side, { Crossing.at - reach, Crossing.at + reach }, *Crossing.track, Crossing.otherpath, 0, Crossing.otherside );
}
}
void TTrack::rail_planner::cross_grooved( rail_crossing const &Crossing, rail_size const &Other ) {
auto const through { m_size.grooved != Other.grooved ? m_size.grooved : goes_through( Crossing.owner ? *Crossing.owner : m_track, *Crossing.track ) };
m_layout.bands[ Crossing.path ][ Crossing.side > 0 ].push_back( groove_band( Crossing.at, Crossing.into, Crossing.facing, through, Other ) );
if( m_size.grooved && Other.grooved && Crossing.angle <= middle_angle ) {
add_vee( Crossing, Other, through );
}
else if( through ) {
add_tread( Crossing.path, Crossing.side, shared_span( Crossing, Other ), *Crossing.track, Crossing.otherpath );
}
}
void TTrack::rail_planner::cross_wide( rail_crossing const &Crossing, rail_size const &Other ) {
plan_of( Crossing ).cuts.push_back( corner_cut( Crossing, 0.0, -1.0, m_size.head, Other.head ) );
auto const material { m_track.shared_tread( Crossing.path, *Crossing.track, Crossing.otherpath ) };
if( material != m_track.rail_material( Crossing.path ) ) {
m_layout.treads[ Crossing.path ][ Crossing.side > 0 ].push_back( wide_tread( Crossing, material, 0.0, m_size.head, rolled_band ) );
}
auto const reach { ( flangeway + m_size.head + guard_reach ) / std::abs( Crossing.into ) };
track_rails::guard_rail guard { Crossing.at - reach, Crossing.at + reach };
guard.through = goes_through( m_track, *Crossing.track );
auto const casting { ( flangeway + Other.head + casting_reach ) / std::abs( Crossing.into ) };
guard.castings.emplace_back( Crossing.at - casting, Crossing.at + casting );
guard.plan.cuts.push_back( corner_cut( Crossing, -flangeway, 1.0, m_size.head, Other.head ) );
auto const theirs { Crossing.track->rail_material( Crossing.otherpath ) };
guard.treads.push_back( wide_tread( Crossing, theirs != null_handle ? theirs : m_track.rail_material( Crossing.path ), m_guardoffset, m_size.head, rolled_band ) );
guard.treads.back().fade = 0.0;
merge_guard( m_layout.guards[ Crossing.path ][ Crossing.side > 0 ], guard );
}
void TTrack::rail_planner::acute_frog( rail_crossing const &Crossing, double const Toward ) {
auto &plan { plan_of( Crossing ) };
auto const slope { std::tan( Crossing.angle ) };
m_layout.wingends[ Crossing.path ][ Crossing.side > 0 ].emplace_back( Crossing.at - Toward * wing_reach( slope ), -Toward );
auto const extension { wing_extension( m_track, m_layout.checks, Crossing.path, Crossing.side, Crossing.at, Toward, slope, *Crossing.track, Crossing.otherpath, Crossing.otherside, Crossing.otherat, Crossing.aligned ? -Toward : Toward ) };
designed_acute( plan, m_track, Crossing, Toward, slope, extension.empty() ? Crossing.at - Toward * wing_reach( slope ) : extension.back().x );
// the nose is carved yet, it ends near where its legs get as wide as its tip
auto const half { slope / ( 1.0 + std::sqrt( 1.0 + slope * slope ) ) };
auto const nose { Crossing.at - Toward * ( 0.5 * nose_tip / half - 0.1 ) };
auto const shared { Crossing.at - Toward * ( rolled_band + m_size.head + tread_fade ) / slope };
add_tread( Crossing.path, Crossing.side, { std::min( nose, shared ), std::max( nose, shared ) }, *Crossing.track, Crossing.otherpath, Toward > 0.0 ? 1 : -1, Crossing.otherside );
}
void TTrack::rail_planner::add_vee( rail_crossing const &Crossing, rail_size const &Other, bool const Filled ) {
std::vector<rail_fill> vee;
vee_fill( vee, Crossing.at, Crossing.into, Crossing.facing, m_size, Other, true );
vee_fill( vee, Crossing.at, Crossing.into, Crossing.facing, m_size, Other, false );
glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) };
m_layout.blocks[ Crossing.path ][ Crossing.side > 0 ].push_back( block );
add_tread( Crossing.path, Crossing.side, block, *Crossing.track, Crossing.otherpath );
if( Filled ) {
auto &fills { m_layout.fills[ Crossing.path ][ Crossing.side > 0 ] };
fills.insert( fills.end(), vee.begin(), vee.end() );
}
}
// crossings at a middle angle: guard rails along the sides of the quadrilateral between the tracks, glued at its corners to the guard rails of the other track
void TTrack::rail_planner::frame_middle_crossings() {
std::sort( m_corners.begin(), m_corners.end(), []( rail_crossing const &A, rail_crossing const &B ) { return A.at < B.at; } );
for( std::size_t i = 0; i < m_corners.size(); ++i ) {
auto const &start { m_corners[ i ] };
if( start.into <= 0.0 ) {
continue;
}
for( std::size_t j = i + 1; j < m_corners.size(); ++j ) {
auto const &end { m_corners[ j ] };
if( false == framed( start, end, start.side ) ) {
continue;
}
if( end.into < 0.0 ) {
auto const guard { frame_guard( start, end ) };
if( guard.to - guard.from > 0.1 ) {
m_layout.guards[ start.path ][ start.side > 0 ].push_back( guard );
}
}
break;
}
}
}
track_rails::guard_rail TTrack::rail_planner::frame_guard( rail_crossing const &Start, rail_crossing const &End ) const {
auto const glued = [&]( rail_crossing const &Corner ) {
return Corner.at + ( flangeway + std::max( m_guardoffset * Corner.facing, ( m_guardoffset + m_size.head ) * Corner.facing ) ) / Corner.into; };
auto const gluedmiter = [&]( rail_crossing const &Corner ) {
return glm::dvec2 { Corner.at + flangeway / Corner.into, Corner.facing / Corner.into }; };
track_rails::guard_rail guard { glued( Start ), glued( End ) };
guard.flarefrom = false;
guard.flareto = false;
guard.frommiter = gluedmiter( Start );
guard.tomiter = gluedmiter( End );
for( auto const *corner : { &Start, &End } ) {
if( corner->facing < 0.0 ) {
continue;
}
auto const forward { corner == &End };
auto reach { forward ? guard.to : guard.from };
for( auto const &opposite : m_corners ) {
if( false == framed( Start, opposite, -Start.side ) ) {
continue;
}
auto const margin { ( flangeway + m_size.head + 0.3 ) / std::abs( opposite.into ) };
reach = forward ? std::max( reach, opposite.at + margin ) : std::min( reach, opposite.at - margin );
}
if( reach == ( forward ? guard.to : guard.from ) ) {
continue;
}
wide( guard.plan, *corner, { -corner->track->rail_dimensions().head, flangeway, m_guardoffset, m_guardoffset + m_size.head } );
auto &cut { guard.plan.cuts.back() };
if( forward ) {
cut.end = guard.to;
cut.endmiter = guard.tomiter;
guard.to = reach;
guard.tomiter = {};
guard.flareto = true;
}
else {
cut.start = guard.from;
cut.startmiter = guard.frommiter;
guard.from = reach;
guard.frommiter = {};
guard.flarefrom = true;
}
}
return guard;
}
void TTrack::rail_planner::switch_frog() {
auto const frog { m_track.frog() };
if( false == frog.valid ) {
return;
}
glm::dvec3 point, tangents[ 2 ];
glm::dvec2 normals[ 2 ];
for( int path = 0; path < 2; ++path ) {
auto const &line { rail_line( m_track, path, frog.side[ path ] > 0, false ) };
line.at( frog.at[ path ], point, tangents[ path ] );
normals[ path ] = glm::normalize( plan( line.normal( frog.at[ path ], frog.side[ path ] ) ) );
}
auto const facing { glm::dot( normals[ 0 ], normals[ 1 ] ) };
for( int path = 0; path < 2; ++path ) {
rail_crossing crossing;
crossing.track = &m_track;
crossing.path = path;
crossing.side = frog.side[ path ];
crossing.at = frog.at[ path ];
crossing.angle = frog.angle;
crossing.into = glm::dot( glm::normalize( plan( tangents[ path ] ) ), normals[ 1 - path ] );
crossing.facing = facing;
crossing.otherpath = 1 - path;
crossing.otherside = frog.side[ 1 - path ];
crossing.otherat = frog.at[ 1 - path ];
if( m_size.grooved ) {
m_layout.bands[ path ][ crossing.side > 0 ].push_back( groove_band( crossing.at, crossing.into, facing, path == 0, m_size ) );
add_vee( crossing, m_size, path == 0 );
continue;
}
if( m_track.SwitchExtension->m_movablenose ) {
auto const nosethrow { std::max( 0.0, m_track.SwitchExtension->m_nosethrow ) };
movable_frog( crossing, path == 0 ? nosethrow : 1.0 - nosethrow );
continue;
}
acute_frog( crossing, -1.0 );
}
}
// the frog of a switch with a movable nose: the nose reaches back to the throat and lies against the wing rail of the track set,
// its leg of the other track pushed over the flangeway and bending back to its place towards the heel; no gap on the way of the wheels.
// Push: how much of the flangeway the leg of this path is pushed over, 0 when its track is set, 1 when the other one is
void TTrack::rail_planner::movable_frog( rail_crossing const &Crossing, double const Push ) {
auto &plan { plan_of( Crossing ) };
auto const slope { std::tan( Crossing.angle ) };
auto const throat { Crossing.at - flangeway / slope };
auto const length { movable_nose_length( slope ) };
// how far the leg of the track not set is pushed over: the whole flangeway at the tip, nothing at the heel, smooth at both
auto const pushed = [&]( double const At ) {
auto const t { std::clamp( ( At - throat ) / length, 0.0, 1.0 ) };
return flangeway * ( 1.0 - t * t * ( 3.0 - 2.0 * t ) ); };
// the legs are carved as they lie with the first path set, the leg of the second one pushed over: the same whichever is set
auto const flare { std::max( Crossing.at + wing_reach( slope ), throat + length + 0.5 ) };
auto const wing { flangeway_wing( m_track, Crossing, throat, flare ) };
knots::line carved, offsets;
for( int i = 0; i <= static_cast<int>( std::ceil( length / 0.1 ) ); ++i ) {
auto const at { std::min( throat + 0.1 * i, throat + length ) };
carved.emplace_back( at, -pushed( at ) );
offsets.emplace_back( at, -Push * pushed( at ) );
}
m_layout.wingends[ Crossing.path ][ Crossing.side > 0 ].emplace_back( flare, 1.0 );
track_rails::rail_cut cut;
cut.at = Crossing.at;
cut.carved = true;
cut.end = flare;
cut.endoffsets = wing;
cut.endrolled = rolled_wing( wing );
cut.endkind = rail_end::point;
cut.endanchor = throat;
cut.start = throat;
cut.startkind = rail_end::sharp;
cut.throwable = true;
if( Push > 0.0 ) {
cut.startoffsets = offsets;
}
if( Crossing.path == 1 ) {
cut.startcarve = carved;
}
plan.cuts.push_back( cut );
if( Push <= 0.0 ) {
auto const shared { Crossing.at + ( rolled_band + m_size.head + tread_fade ) / slope };
add_tread( Crossing.path, Crossing.side, { throat, shared }, *Crossing.track, Crossing.otherpath, -1, Crossing.otherside );
}
}
void TTrack::rail_planner::add_tread( int const Path, int const Side, glm::dvec2 const &Span, TTrack const &Other, int const Otherpath, int const Anchor, int const Otherside ) {
auto const material { m_track.shared_tread( Path, Other, Otherpath ) };
if( material == m_track.rail_material( Path ) || Span.y <= Span.x ) {
return;
}
auto &treads { m_layout.treads[ Path ][ Side > 0 ] };
if( Otherside == 0 ) {
treads.push_back( { Span.x, Span.y, material, Anchor } );
return;
}
rail_tread part { Span.x, Span.y, material, Anchor };
part.edge = tread_edge( m_track, Path, Side, Other, Otherpath, Otherside, Span.x, Span.y, part.below, part.gauge );
auto from { 1e9 };
auto to { -1e9 };
for( std::size_t i = 0; i < part.edge.size() && i < part.gauge.size(); ++i ) {
auto const low { std::min( part.edge[ i ].y, part.gauge[ i ].y ) };
auto const high { std::max( part.edge[ i ].y, part.gauge[ i ].y ) };
if( low - tread_fade < m_size.head && high + tread_fade > 0.0 ) {
from = std::min( from, part.edge[ i ].x );
to = std::max( to, part.edge[ i ].x );
}
}
part.from = std::max( Span.x, from - 0.1 );
part.to = std::min( Span.y, to + 0.1 );
if( part.to > part.from ) {
treads.push_back( part );
}
}
glm::dvec2 TTrack::rail_planner::shared_span( rail_crossing const &Crossing, rail_size const &Other ) const {
glm::dvec2 span { 1e9, -1e9 };
for( auto const theirs : { Other.grooved ? Other.ledge : 0.0, Other.grooved ? Other.tread : Other.head } ) {
for( auto const ours : { m_size.grooved ? m_size.ledge : 0.0, m_size.grooved ? m_size.tread : m_size.head } ) {
auto const distance { Crossing.at + ( Crossing.facing * ours - theirs ) / Crossing.into };
span = { std::min( span.x, distance ), std::max( span.y, distance ) };
}
}
return span;
}
track_rails::rail_layout TTrack::plan_rails( bool const Crossings, bool const Carve ) {
return rail_planner( *this, Crossings, Carve ).lay_out();
}

136
world/RailProfile.cpp Normal file
View File

@@ -0,0 +1,136 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/TrackRails.h"
namespace track_rails {
rail_size measure_rail( gfx::vertex_array const &Profile ) {
rail_size result;
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;
auto flat { -1e9 };
for( std::size_t i = 0; i < count; ++i ) {
auto const &point { Profile[ i ].position };
if( point.y > top - 0.0005 ) {
flat = std::max( flat, static_cast<double>( point.x ) );
}
}
result.shoulder = std::clamp( result.head - flat, 0.0, 0.5 * 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;
}
} // namespace track_rails

View File

@@ -33,6 +33,16 @@ segment_data::deserialize( cParser &Input, glm::dvec3 const &Offset ) {
}
void
segment_data::translate( glm::dvec3 const &Offset ) {
points[ segment_data::point::start ] += Offset;
points[ segment_data::point::end ] += Offset;
for( auto &piece : pieces ) {
piece.translate( Offset );
}
}
TSegment::TSegment(TTrack *owner) :
pOwner( owner )
{}
@@ -62,6 +72,8 @@ bool TSegment::Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep,
bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1, double fNewRoll2, bool bIsCurve)
{ // wersja uniwersalna (dla krzywej i prostego)
m_pieces.clear();
m_starts.clear();
Point1 = NewPoint1;
CPointOut = NewCPointOut;
CPointIn = NewCPointIn;
@@ -133,6 +145,45 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N
return true;
}
bool TSegment::InitChain(std::vector<std::shared_ptr<TSegment>> Pieces)
{
if( Pieces.empty() )
return false;
m_pieces = std::move( Pieces );
m_starts.clear();
fLength = 0.0;
iSegCount = 0;
for( auto const &piece : m_pieces ) {
m_starts.push_back( fLength );
fLength += piece->fLength;
iSegCount += piece->iSegCount;
}
auto const &first { *m_pieces.front() };
auto const &last { *m_pieces.back() };
Point1 = first.Point1;
CPointOut = first.CPointOut;
CPointIn = last.CPointIn;
Point2 = last.Point2;
fRoll1 = first.fRoll1;
fRoll2 = last.fRoll2;
bCurve = true;
fDirection = -std::atan2(Point2.x - Point1.x, Point2.z - Point1.z);
fStoop = std::atan2(Point2.y - Point1.y, fLength);
fStep = fLength / iSegCount;
fTsBuffer.resize( iSegCount + 1 );
for( int i = 0; i <= iSegCount; ++i ) {
fTsBuffer[ i ] = static_cast<double>( i ) / iSegCount;
}
fTsSlope.assign( iSegCount + 1, fStep / fLength );
return true;
}
std::size_t TSegment::piece_at(double const s) const
{
auto const next { std::upper_bound( std::begin( m_starts ), std::end( m_starts ), s ) };
return next == std::begin( m_starts ) ? 0 : static_cast<std::size_t>( std::distance( std::begin( m_starts ), next ) - 1 );
}
void TSegment::InitTFromSInterpolateData()
{
// pre-compute the curve parameter t at equal distance intervals
@@ -235,6 +286,8 @@ double TSegment::FastGetTFromS(double const s) const
// NOTE: for s outside <0, fLength> the result is extrapolated
// outside <0, 1>, which is the expected behavior (see e.g. usage
// in GetDirection())
if( false == m_pieces.empty() )
return s / fLength;
int const i = std::clamp(static_cast<int>(s / fStep), 0, iSegCount - 1); // interval index
double const u = (s - i * fStep) / fStep; // position within interval (possibly outside)
double const u2 = u * u;
@@ -263,6 +316,8 @@ double TSegment::ComputeLength() const // McZapkie-150503: dlugosc miedzy punkta
// Ra: koniec rekurencji jeśli po podziale suma długości nie różni się więcej niż 0.5mm od
// poprzedniej
// Ra: ewentualnie rozpoznać łuk okręgu płaskiego i liczyć ze wzoru na długość łuku
if( false == m_pieces.empty() )
return fLength;
double t;
double l = 0;
glm::dvec3 last{0, 0, 0}; // długość liczona po przesunięciu odcinka do początku układu
@@ -366,7 +421,11 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) const
*/
void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const {
if (bCurve) {
if( false == m_pieces.empty() ) {
auto const piece { piece_at( fDistance ) };
m_pieces[ piece ]->RaPositionGet( fDistance - m_starts[ piece ], position, rotation );
}
else if (bCurve) {
// można by wprowadzić uproszczony wzór dla okręgów płaskich
auto const t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t)
position = FastGetPoint( t );
@@ -393,6 +452,12 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::
glm::dvec3 TSegment::FastGetPoint(double const t) const
{
// return (bCurve?std::lerp(t,Point1,CPointOut,CPointIn,Point2):((1.0-t)*Point1+(t)*Point2));
if( false == m_pieces.empty() ) {
auto const distance { t * fLength };
auto const index { piece_at( distance ) };
auto const &piece { *m_pieces[ index ] };
return piece.FastGetPoint( piece.FastGetTFromS( distance - m_starts[ index ] ) );
}
return true == bCurve || iSegCount != 1 ? RaInterpolate(t) : glm::mix(Point1, Point2, t);
}
@@ -441,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 ));
@@ -486,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 ) {
@@ -560,6 +633,310 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
return true;
};
bool TSegment::RenderLoftSteps( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, double fTextureLength, int const Steps ) const
{
if( fTsBuffer.empty() || Steps < 1 || fLength <= 0.0 )
return false;
auto const shapepoints { ShapePoints.size() / 2 };
auto const texturelength { static_cast<float>( fTextureLength ) };
auto const step { static_cast<float>( fLength / Steps ) };
auto const offset { 0.1 / fLength };
// point and the unit vector across the path at the k-th of the Steps stations
auto const station = [&]( int const k, glm::vec3 &Position, glm::vec3 &Parallel ) {
if( k == 0 && m_joints[ 0 ] ) {
Position = glm::vec3{ m_joints[ 0 ]->first - Origin };
Parallel = glm::vec3{ m_joints[ 0 ]->second };
return; }
if( k == Steps && m_joints[ 1 ] ) {
Position = glm::vec3{ m_joints[ 1 ]->first - Origin };
Parallel = glm::vec3{ m_joints[ 1 ]->second };
return; }
auto const t { k == 0 ? 0.0 : k == Steps ? 1.0 : FastGetTFromS( fLength * k / Steps ) };
Position = glm::vec3{ FastGetPoint( t ) - Origin };
auto const dir { FastGetDirection( t, offset ) };
Parallel = glm::vec3{ -dir.z, 0.0, dir.x };
if( glm::length2( Parallel ) == 0.f ) {
// the same workaround as in RenderLoft for control points placed above endpoints
Parallel = glm::vec3{ FastGetPoint_1() - FastGetPoint_0() };
}
Parallel = glm::normalize( Parallel ); };
// a vertex of the profile blended Share of the way from its start to its end shape
auto const vertex = [&]( std::size_t const j, float const Share, glm::vec3 const &Position, glm::vec3 const &Parallel, float const V ) {
auto const &from { ShapePoints[ j ] };
auto const &to { ShapePoints[ j + shapepoints ] };
auto const rest { 1.f - Share };
auto point { Parallel * ( rest * from.position.x + Share * to.position.x ) + Position };
point.y += rest * from.position.y + Share * to.position.y;
auto normal { ( rest * from.normal.x + Share * to.normal.x ) * Parallel };
normal.y += rest * from.normal.y + Share * to.normal.y;
Output.emplace_back( point, glm::normalize( normal ), glm::vec2 { rest * from.texture.x + Share * to.texture.x, V } ); };
glm::vec3 pos1, pos2, parallel1, parallel2;
station( 0, pos1, parallel1 );
auto tv1 { 1.f };
for( int k = 1; k <= Steps; ++k ) {
station( k, pos2, parallel2 );
tv1 = clamp_circular( tv1, 1.0f );
auto const tv2 { tv1 - step / texturelength };
auto const m1 { static_cast<float>( k - 1 ) / Steps };
auto const m2 { static_cast<float>( k ) / Steps };
for( std::size_t j = 0; j < shapepoints; ++j ) {
vertex( j, m1, pos1, parallel1, tv1 );
vertex( j, m2, pos2, parallel2, tv2 );
}
pos1 = pos2;
parallel1 = parallel2;
tv1 = tv2;
}
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, loft_span const &Span ) const {
if( fTsBuffer.empty() || Span.to - Span.from < 0.001 || ShapePoints.empty() )
return false;
std::vector<double> stations { Span.from, Span.to };
auto const step { std::max( fStep, 0.2 ) };
for( auto distance { std::ceil( Span.from / step ) * step }; distance < Span.to; distance += step ) {
stations.push_back( distance );
}
for( auto distance { std::ceil( Span.from / 0.5 ) * 0.5 }; distance < std::min( Span.to, m_dense ); distance += 0.5 ) {
stations.push_back( distance );
}
for( auto const *line : { &Span.offsets, &Span.extents, &Span.inner, &Span.drops } ) {
for( auto const &knot : *line ) {
if( knot.x > Span.from && knot.x < Span.to ) {
stations.push_back( knot.x );
}
}
}
for( auto const *line : { &Span.extents, &Span.inner } ) {
for( std::size_t i = 1; i < line->size(); ++i ) {
auto const &a { ( *line )[ i - 1 ] };
auto const &b { ( *line )[ i ] };
if( std::abs( b.y - a.y ) < 1e-9 ) {
continue;
}
for( auto const &point : ShapePoints ) {
auto const reach { static_cast<double>( Span.side * point.position.x - Span.halfwidth ) };
auto const t { ( reach - a.y ) / ( b.y - a.y ) };
auto const distance { glm::mix( a.x, b.x, t ) };
if( t > 0.0 && t < 1.0 && distance > Span.from && distance < Span.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( Span.fromend.bend != 0.0 || Span.toend.bend != 0.0 ) {
auto widest { 0.0 };
for( auto const &point : ShapePoints ) {
widest = std::max( widest, std::abs( static_cast<double>( Span.side * point.position.x - Span.halfwidth ) ) );
}
auto const frommargin { std::abs( Span.fromend.bend ) * widest + 0.001 };
auto const tomargin { std::abs( Span.toend.bend ) * widest + 0.001 };
stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) {
return ( Distance > Span.from && Distance < Span.to )
&& ( Distance < Span.from + frommargin || Distance > Span.to - tomargin ); } ), stations.end() );
}
auto const ring = [&]( double const Distance, double const Middle, std::vector<gfx::basic_vertex> &Ring, std::vector<float> &Wear ) {
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 { knots::sample( Span.offsets, Distance, 0.0 ) };
auto const inward { Distance + std::copysign( 1e-6, Middle - Distance ) };
auto const extent { knots::sample( Span.extents, inward, 1e6 ) };
auto const innerextent { knots::sample( Span.inner, inward, -1e6 ) };
auto const middleextent { knots::sample( Span.extents, Middle, 1e6 ) };
auto const middleinner { knots::sample( Span.inner, Middle, -1e6 ) };
glm::vec3 outerwall { 0.f }, innerwall { 0.f };
if( Span.walls ) {
auto const delta { 0.001 };
glm::dvec3 ahead, behind, unused;
plan_frame( std::min( Distance + delta, static_cast<double>( fLength ) ), ahead, unused );
plan_frame( std::max( Distance - delta, 0.0 ), behind, unused );
auto tangent { glm::dvec3{ ahead.x - behind.x, 0.0, ahead.z - behind.z } };
tangent = ( glm::length2( tangent ) > 0.0 ? glm::normalize( tangent ) : glm::dvec3{ 0.0 } );
auto const outward { glm::dvec3{ across } * static_cast<double>( Span.side ) };
auto const slope = [&]( knots::line const &Knots, double const Default ) {
return ( knots::sample( Knots, Distance + delta, Default ) - knots::sample( Knots, Distance - delta, Default ) + knots::sample( Span.offsets, Distance + delta, 0.0 ) - knots::sample( Span.offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); };
outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Span.extents, 1e6 ) ) };
innerwall = glm::vec3{ glm::normalize( tangent * slope( Span.inner, -1e6 ) - outward ) };
}
auto const worn { knots::sample( Span.ramp, Distance, -1e6 ) };
auto const drop { knots::sample( Span.drops, Distance, 0.0 ) };
static loft_end const unshaped;
auto const &shaped { Distance <= Span.from ? Span.fromend : Distance >= Span.to ? Span.toend : unshaped };
auto const bend { shaped.bend };
auto 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 ) } * static_cast<float>( bend );
}
}
Ring.clear();
Wear.clear();
auto const count { ShapePoints.size() };
auto const reach = [&]( std::size_t const Index ) {
return static_cast<double>( Span.side * ShapePoints[ Index ].position.x - Span.halfwidth ); };
auto const twin = [&]( std::size_t const A, std::size_t const B ) {
return ShapePoints[ A ].position.x == ShapePoints[ B ].position.x && ShapePoints[ A ].position.y == ShapePoints[ B ].position.y; };
auto const clamped = [&]( std::size_t const Index ) {
auto const outside { reach( Index ) };
return ( outside > middleextent ? 1 : outside < middleinner ? -1 : 0 ); };
auto nearest { 1e6 };
auto farthest { -1e6 };
for( std::size_t j = 0; j < count; ++j ) {
nearest = std::min( nearest, reach( j ) );
farthest = std::max( farthest, reach( j ) );
}
if( middleextent <= middleinner || middleextent < nearest || middleinner > farthest ) {
// nothing of the profile between the limits there: the ring shrinks to one point, the step draws nothing
auto const outside { std::clamp( 0.5 * ( std::min( extent, farthest ) + std::max( innerextent, nearest ) ), nearest, farthest ) };
auto pt { parallel * static_cast<float>( Span.side * ( Span.halfwidth + outside + offset ) ) + position };
pt.y += ShapePoints.front().position.y + Span.lift;
for( std::size_t j = 0; j < count; ++j ) {
Ring.emplace_back( pt, glm::vec3{ 0.f, 1.f, 0.f }, glm::vec2{ ShapePoints[ j ].texture.x, 0.f } );
Wear.push_back( 1.f );
}
return;
}
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( Span.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 { std::clamp( static_cast<float>( ( limit - reach( neighbour ) ) / ( side > 0 ? std::max( outside - reach( neighbour ), 1e-6 ) : std::min( outside - reach( neighbour ), -1e-6 ) ) ), 0.f, 1.f ) };
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 = ( side > 0 ? std::min( outside, limit ) : std::max( outside, limit ) );
auto const onwall { paired ? ( false == open( partner ) && clamped( partner ) == side ) : moved == count };
if( onwall ) {
wall = ( side > 0 ? outerwall : innerwall );
}
}
if( drop > 0.0 && shape.position.y > Span.head.x && Span.head.y > Span.head.x ) {
shape.position.y -= static_cast<float>( drop * ( shape.position.y - Span.head.x ) / ( Span.head.y - Span.head.x ) );
}
auto pt { parallel * static_cast<float>( Span.side * ( Span.halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) };
pt.y += shape.position.y + Span.lift;
auto norm { shape.normal.x * parallel };
norm.y += shape.normal.y;
if( glm::length2( wall ) > 0.f ) {
norm = wall;
if( Span.wallu >= 0.f ) {
shape.texture.x = Span.wallu;
}
}
Ring.emplace_back( pt, glm::normalize( norm ), glm::vec2{ shape.texture.x, 0.f } );
Wear.push_back( Span.rampwidth != 0.0 ? static_cast<float>( std::clamp( ( Span.rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Span.rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f );
}
};
auto const texturelength { static_cast<float>( Span.texturelength ) };
std::vector<gfx::basic_vertex> ring1, ring2;
std::vector<float> wear1, wear2;
float tv1 { clamp_circular( 1.f - static_cast<float>( std::fmod( Span.from, Span.texturelength ) / Span.texturelength ), 1.0f ) };
for( std::size_t i = 1; i < stations.size(); ++i ) {
auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) };
ring( stations[ i - 1 ], middle, ring1, wear1 );
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( Span.userdata != nullptr ) {
Span.userdata->push_back( { glm::vec4{ wear1[ j ], 0.f, 0.f, 0.f } } );
Span.userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } );
}
}
tv1 = tv2;
}
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,10 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Classes.h"
#include "rendering/geometrybank.h"
#include <glm/glm.hpp>
#include <memory>
#include <vector>
#include <optional>
#include "world/Knots.h"
struct map_colored_paths {
std::vector<gfx::geometrybank_handle> switches;
@@ -20,6 +24,31 @@ struct map_colored_paths {
std::vector<gfx::geometrybank_handle> highlighted;
};
struct loft_end {
double bend { 0.0 };
};
struct loft_span {
int side { 1 };
float halfwidth { 0.f };
double texturelength { 1.0 };
double from { 0.0 };
double to { 0.0 };
knots::line offsets;
knots::line extents;
knots::line inner;
float lift { 0.f };
gfx::userdata_array *userdata { nullptr };
knots::line ramp;
double rampwidth { 0.0 };
bool walls { false };
float wallu { -1.f };
loft_end fromend;
loft_end toend;
knots::line drops;
glm::dvec2 head { 0.0 };
};
struct segment_data {
// types
enum point {
@@ -32,10 +61,21 @@ struct segment_data {
std::array<glm::dvec3, 4> points {};
std::array<float, 2> rolls {};
float radius {};
std::vector<segment_data> pieces; // consecutive parts of the path, which then is their approximation for older versions
// constructors
segment_data() = default;
// methods
void deserialize( cParser &Input, glm::dvec3 const &Offset );
void translate( glm::dvec3 const &Offset );
// maps the ends of the path and of its pieces with Point and their control vectors with Vector
template <typename Pointmap, typename Vectormap>
void transform( Pointmap const &Point, Vectormap const &Vector ) {
for( auto const index : { point::start, point::end } ) {
points[ index ] = Point( points[ index ] ); }
for( auto const index : { point::control1, point::control2 } ) {
points[ index ] = Vector( points[ index ] ); }
for( auto &piece : pieces ) {
piece.transform( Point, Vector ); } }
};
class TSegment
@@ -58,6 +98,10 @@ 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::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 ];
double m_dense { 0.0 };
glm::dvec3 GetFirstDerivative(double const fTime) const;
double RombergIntegral(double const fA, double const fB) const;
@@ -66,6 +110,7 @@ class TSegment
double FastGetTFromS(double const s) const;
glm::dvec3 RaInterpolate(double const t) const;
glm::dvec3 RaInterpolate0(double const t) const;
std::size_t piece_at(double const s) const;
public:
bool bCurve = false;
@@ -73,6 +118,15 @@ public:
explicit TSegment(TTrack *owner);
bool Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0);
bool Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0, bool bIsCurve = true);
bool InitChain(std::vector<std::shared_ptr<TSegment>> Pieces);
inline
std::size_t
PieceCount() const {
return m_pieces.size(); };
inline
double
PieceStart( std::size_t const Piece ) const {
return m_starts[ Piece ]; };
double
ComputeLength() const; // McZapkie-150503
// finds point on segment closest to specified point in 3d space. returns: point on segment as value in range 0-1
@@ -118,6 +172,9 @@ public:
inline
float
GetRoll(double const Distance) const {
if( false == m_pieces.empty() ) {
auto const piece { piece_at( Distance ) };
return m_pieces[ piece ]->GetRoll( Distance - m_starts[ piece ] ); }
return std::lerp( fRoll1, fRoll2, static_cast<float>(Distance / fLength) ); }
inline
void
@@ -128,6 +185,21 @@ 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 );
// a transition profile lofted in Steps pieces of equal length, so two paths of different make-up line up piece by piece
bool
RenderLoftSteps( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, double fTextureLength, int const Steps ) const;
// 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, loft_span const &Span ) const;
// point of the path and the unit vector across it towards the right rail profile, at a distance from Point1
void
plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const;
void
set_joint( int const End, std::optional<std::pair<glm::dvec3, glm::dvec3>> const &Joint ) {
m_joints[ End ] = Joint; }
void
set_dense( double const Distance ) {
m_dense = Distance; }
/*
void
Render();

255
world/SleeperFeatures.cpp Normal file
View File

@@ -0,0 +1,255 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/SleeperFeatures.h"
#include "world/SleeperGeometry.h"
#include "world/SleeperRules.h"
#include "world/Track.h"
namespace track_sleepers {
namespace {
// how far past the timbers the sleepers turn back square to the tracks, by the angle the timbers are turned off them
double turn_length( double const Angle ) {
return ( Angle <= turn_max ? 0.05 * pitch : pitch * std::ceil( Angle / turn_max ) );
}
// the frog of the switch: where its paths are a gauge apart, from the samples of the diverging path against the main one
void find_frog( sampled_path const &Main, sampled_path const &Diverging, std::vector<feature> &Features ) {
auto previousgap { 0.0 };
for( std::size_t index = 1; index < Diverging.points.size(); ++index ) {
double gap;
glm::dvec2 direction;
if( false == beside( Main, Diverging.points[ index ], Main.gauge + 1.0, gap, direction ) ) {
return;
}
if( gap < Main.gauge ) {
previousgap = gap;
continue;
}
// between the samples, where the paths are a gauge apart
auto const share { ( Main.gauge - previousgap ) / std::max( 1e-6, gap - previousgap ) };
auto const distance { glm::mix( Diverging.distances[ index - 1 ], Diverging.distances[ index ], share ) };
glm::dvec3 point;
glm::vec3 angles;
Diverging.segment->RaPositionGet( distance, point, angles );
glm::dvec2 mainpoint, maindirection;
auto const mainat { nearest_on( Main, plan( point ), mainpoint, maindirection ) };
auto const divergingdirection { heading( Diverging, distance ) };
if( glm::dot( maindirection, divergingdirection ) < 0.0 ) {
maindirection = -maindirection;
}
auto const halfangle { 0.5 * angle_between( maindirection, divergingdirection ) };
if( halfangle < 0.002 ) {
return;
}
feature frog;
// the frog lies midway between the paths
frog.point = 0.5 * ( mainpoint + plan( point ) );
frog.bisector = glm::normalize( maindirection + divergingdirection );
frog.sine = std::sin( halfangle );
frog.spacing = Main.gauge;
frog.from = -frog_ramp;
frog.full = 0.0;
// till the plain sleepers of the paths going apart wouldn't touch any more
frog.until = std::max( 0.0, ( Main.half + Diverging.half + end_gap - Main.gauge ) / ( 2.0 * frog.sine ) );
frog.back = frog.until + turn_length( halfangle );
frog.paths = { &Main, &Diverging };
frog.at = { mainat, distance };
frog.frog = true;
Features.push_back( frog );
return;
}
}
// where two paths of different tracks cross, from their samples
void find_crossing( sampled_path const &Left, sampled_path const &Right, std::vector<feature> &Features ) {
if( Left.low.x > Right.high.x || Right.low.x > Left.high.x || Left.low.y > Right.high.y || Right.low.y > Left.high.y ) {
return;
}
for( std::size_t left = 1; left < Left.points.size(); ++left ) {
auto const a0 { plan( Left.points[ left - 1 ] ) };
auto const a { plan( Left.points[ left ] ) - a0 };
if( std::max( a0.x, a0.x + a.x ) < Right.low.x || std::min( a0.x, a0.x + a.x ) > Right.high.x
|| std::max( a0.y, a0.y + a.y ) < Right.low.y || std::min( a0.y, a0.y + a.y ) > Right.high.y ) {
continue;
}
for( std::size_t right = 1; right < Right.points.size(); ++right ) {
auto const b0 { plan( Right.points[ right - 1 ] ) };
auto const b { plan( Right.points[ right ] ) - b0 };
auto const denominator { cross( a, b ) };
if( std::abs( denominator ) < 1e-9 ) {
continue;
}
auto const u { cross( b0 - a0, b ) / denominator };
auto const v { cross( b0 - a0, a ) / denominator };
if( u < 0.0 || u >= 1.0 || v < 0.0 || v >= 1.0 ) {
continue;
}
auto const leftat { glm::mix( Left.distances[ left - 1 ], Left.distances[ left ], u ) };
auto const rightat { glm::mix( Right.distances[ right - 1 ], Right.distances[ right ], v ) };
if( leftat < 0.05 || leftat > Left.length - 0.05 || rightat < 0.05 || rightat > Right.length - 0.05 ) {
// tracks joined end to end, or one ending on the other
continue;
}
auto const levels { glm::mix( Left.points[ left - 1 ].y, Left.points[ left ].y, u ) - glm::mix( Right.points[ right - 1 ].y, Right.points[ right ].y, v ) };
if( std::abs( levels ) > level_tolerance ) {
continue;
}
auto const leftdirection { glm::normalize( a ) };
auto rightdirection { glm::normalize( b ) };
if( glm::dot( leftdirection, rightdirection ) < 0.0 ) {
rightdirection = -rightdirection;
}
auto const angle { angle_between( leftdirection, rightdirection ) };
if( angle < 0.01 ) {
continue;
}
feature crossing;
crossing.point = a0 + a * u;
crossing.bisector = glm::normalize( leftdirection + rightdirection );
crossing.sine = std::sin( 0.5 * angle );
crossing.until = std::max( touch_spacing( Left, Right, angle ), touch_spacing( Right, Left, angle ) ) / ( 2.0 * crossing.sine );
crossing.back = crossing.until + turn_length( 0.5 * angle );
crossing.from = -crossing.back;
crossing.full = -crossing.until;
crossing.paths = { &Left, &Right };
crossing.at = { leftat, rightat };
crossing.grid = ( angle >= grid_angle );
Features.push_back( crossing );
}
}
}
// whether another path goes on from the end of the path, so the beams there go on too
bool goes_on( sampled_path const &Path, glm::dvec3 const &End, std::vector<sampled_path> const &Around ) {
for( auto const &other : Around ) {
if( other.track == Path.track ) {
continue;
}
if( glm::length( plan( other.points.front() - End ) ) < 0.05 || glm::length( plan( other.points.back() - End ) ) < 0.05 ) {
return true;
}
}
return false;
}
// the stretch of the timbers square to the bisector ends where either path ends with nothing going on from it
void clamp_to_ends( feature &Feature, std::vector<sampled_path> const &Around ) {
for( auto const *path : Feature.paths ) {
for( auto const *end : { &path->points.front(), &path->points.back() } ) {
if( goes_on( *path, *end, Around ) ) {
continue;
}
auto const along { glm::dot( plan( *end ) - Feature.point, Feature.bisector ) };
if( along > 0.0 ) {
Feature.until = std::max( Feature.full, std::min( Feature.until, along ) );
}
else if( false == Feature.frog ) {
Feature.full = std::min( Feature.until, std::max( Feature.full, along ) );
}
}
}
auto const turn { turn_length( std::asin( Feature.sine ) ) };
Feature.back = Feature.until + turn;
if( false == Feature.frog ) {
Feature.from = Feature.full - turn;
}
}
} // namespace
// the frogs of the switches and the crossings of the paths around
std::vector<feature> find_features( std::vector<sampled_path> const &Around ) {
std::vector<feature> result;
for( std::size_t index = 0; index < Around.size(); ++index ) {
auto const &path { Around[ index ] };
if( path.track->eType == tt_Switch && path.path == 0 && index + 1 < Around.size() && Around[ index + 1 ].track == path.track ) {
find_frog( path, Around[ index + 1 ], result );
}
for( std::size_t other = index + 1; other < Around.size(); ++other ) {
if( Around[ other ].track != path.track ) {
find_crossing( path, Around[ other ], result );
}
}
}
for( auto &feature : result ) {
clamp_to_ends( feature, Around );
}
// the frogs of the switches of a crossover between tracks running alongside each other: the diverging tracks of both lie on one
// line, the main ones side by side
auto const way = []( sampled_path const &Path, double const Distance ) {
return heading( Path, Distance ); };
for( auto &left : result ) {
for( auto &right : result ) {
if( &left == &right || false == left.frog || false == right.frog || left.paths[ 0 ]->track == right.paths[ 0 ]->track ) {
continue;
}
auto const leftmain { way( *left.paths[ 0 ], left.at[ 0 ] ) };
auto const rightmain { way( *right.paths[ 0 ], right.at[ 0 ] ) };
auto const leftdiverging { way( *left.paths[ 1 ], left.at[ 1 ] ) };
auto const rightdiverging { way( *right.paths[ 1 ], right.at[ 1 ] ) };
auto const apart { std::abs( cross( leftmain, right.point - left.point ) ) };
if( angle_between( leftmain, rightmain ) < 0.035 && angle_between( leftdiverging, rightdiverging ) < 0.035 && apart > 3.0 && apart < 8.0 ) {
left.crossover = true;
right.crossover = true;
}
}
}
return result;
}
// which way the beam at the point lies square to: the path laying it, turned toward the bisectors of the frogs and crossings
// about the point. this depends only on where the point is, so the beams of paths laid one after another turn smoothly
glm::dvec2 beam_direction( glm::dvec3 const &Point, glm::dvec2 const &Forward, std::vector<feature> const &Features ) {
auto const point { plan( Point ) };
auto weight { 0.0 };
glm::dvec2 bisectors { 0.0 };
for( auto const &feature : Features ) {
if( feature.grid || feature.crossover ) {
continue;
}
auto const offset { point - feature.point };
auto const along { glm::dot( offset, feature.bisector ) };
if( along <= feature.from || along >= feature.back ) {
continue;
}
auto turned {
along < feature.full ? glm::smoothstep( feature.from, feature.full, along ) :
along > feature.until ? 1.0 - glm::smoothstep( feature.until, feature.back, along ) :
1.0 };
// only the beams under the paths of the frog or the crossing turn, not those of the tracks beside them
auto const spacing { feature.spacing + 2.0 * feature.sine * ( feature.frog ? std::max( 0.0, along ) : std::abs( along ) ) };
auto const reach { 0.5 * spacing + feature.paths[ 0 ]->reach + 0.3 };
auto const lateral { std::abs( cross( feature.bisector, offset ) ) };
turned *= 1.0 - glm::smoothstep( reach, reach + 0.5, lateral );
if( turned <= 0.0 ) {
continue;
}
weight = std::max( weight, turned );
bisectors += turned * ( glm::dot( feature.bisector, Forward ) >= 0.0 ? feature.bisector : -feature.bisector );
}
if( weight <= 0.0 || glm::length( bisectors ) < 1e-9 ) {
return Forward;
}
return glm::normalize( Forward * ( 1.0 - weight ) + glm::normalize( bisectors ) * weight );
}
} // namespace track_sleepers

47
world/SleeperFeatures.h Normal file
View File

@@ -0,0 +1,47 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <array>
#include <vector>
#include <glm/glm.hpp>
#include "world/SleeperPaths.h"
// the frogs of the switches and the crossings of the tracks, which the timbers about them turn to
namespace track_sleepers {
// where two paths meet at an angle, the timbers which carry both lie square to the bisector of the angle: past the frog of
// a switch, where the paths go apart, and about the point where two paths cross. the timbers turn to the bisector before
// and back square to the tracks after, by the distance along the bisector from the point
struct feature {
glm::dvec2 point; // the frog, or where the paths cross, in plan
glm::dvec2 bisector; // unit
double sine { 0.0 }; // of half the angle between the paths
double spacing { 0.0 }; // how far apart the paths run at the point
double from { 0.0 }; // along the bisector, where the timbers start turning to it
double full { 0.0 }; // where they're square to it
double until { 0.0 }; // till where they stay so
double back { 0.0 }; // where they're square to the tracks again
std::array<sampled_path const *, 2> paths {};
std::array<double, 2> at {}; // the distance of the point along each path
bool frog { false }; // the paths go apart from the point, rather than cross there
bool grid { false }; // the paths cross so wide the timbers keep square to each
bool crossover { false }; // the frog of a switch whose diverging track leads to another switch of a track alongside: the timbers keep square to the tracks
};
// the frogs of the switches and the crossings of the paths around
std::vector<feature> find_features( std::vector<sampled_path> const &Around );
// which way the beam at the point lies square to: the path laying it, turned toward the bisectors of the frogs and crossings
// about the point. this depends only on where the point is, so the beams of paths laid one after another turn smoothly
glm::dvec2 beam_direction( glm::dvec3 const &Point, glm::dvec2 const &Forward, std::vector<feature> const &Features );
} // namespace track_sleepers

148
world/SleeperGeometry.h Normal file
View File

@@ -0,0 +1,148 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <map>
#include <utility>
#include <vector>
#include <glm/glm.hpp>
#include "world/SleeperRules.h"
#include "world/TrackSleepers.h"
// geometry in plan, x and z of the world, the sleepers are laid out in
namespace track_sleepers {
inline glm::dvec2 plan( glm::dvec3 const &Vector ) {
return { Vector.x, Vector.z };
}
inline double cross( glm::dvec2 const &Left, glm::dvec2 const &Right ) {
return Left.x * Right.y - Left.y * Right.x;
}
// the angle between two directions in plan, either way along them
inline double angle_between( glm::dvec2 const &Left, glm::dvec2 const &Right ) {
return std::acos( std::min( 1.0, std::abs( glm::dot( Left, Right ) ) ) );
}
// the direction turned by the angle, counterclockwise from x to z
inline glm::dvec2 rotated( glm::dvec2 const &Direction, double const Angle ) {
return { Direction.x * std::cos( Angle ) - Direction.y * std::sin( Angle ), Direction.x * std::sin( Angle ) + Direction.y * std::cos( Angle ) };
}
// the angle the direction is turned off the forward way, either way along it
inline double turn_off( glm::dvec2 const &Forward, glm::dvec2 Direction ) {
if( glm::dot( Forward, Direction ) < 0.0 ) {
Direction = -Direction;
}
return std::atan2( cross( Forward, Direction ), glm::dot( Forward, Direction ) );
}
// a beam in plan, by its middle, the unit vector along it and half its length
struct beam_box {
glm::dvec2 centre { 0.0 };
glm::dvec2 axis { 1.0, 0.0 };
double half { 0.0 };
};
inline beam_box box_of( beam const &Beam ) {
auto const span { plan( Beam.to - Beam.from ) };
auto const length { glm::length( span ) };
return { plan( 0.5 * ( Beam.from + Beam.to ) ), length > 1e-9 ? span / length : glm::dvec2 { 1.0, 0.0 }, 0.5 * length };
}
// whether two beams keep clear of each other, with a little room left between them: an axis of either keeps them apart
inline bool apart( beam_box const &Left, beam_box const &Right ) {
auto const offset { Right.centre - Left.centre };
for( auto const &axis : { Left.axis, glm::dvec2 { -Left.axis.y, Left.axis.x }, Right.axis, glm::dvec2 { -Right.axis.y, Right.axis.x } } ) {
auto const reach = [&]( beam_box const &Box ) {
return Box.half * std::abs( glm::dot( Box.axis, axis ) ) + ( half_width - 0.01 ) * std::abs( cross( Box.axis, axis ) ); };
if( std::abs( glm::dot( offset, axis ) ) > reach( Left ) + reach( Right ) ) {
return true;
}
}
return false;
}
// whether the point lies on the beam
inline bool holds( beam_box const &Box, glm::dvec2 const &Point ) {
auto const offset { Point - Box.centre };
return std::abs( glm::dot( offset, Box.axis ) ) <= Box.half && std::abs( cross( Box.axis, offset ) ) <= half_width;
}
// values put in the square cells of a grid in plan that a square about a point reaches into, to find those near a place
template <typename Value>
class plan_grid {
public:
using cell = std::pair<std::int64_t, std::int64_t>;
explicit plan_grid( double const Size ) :
m_size( Size ) {}
// the value in every cell the square of the reach about the centre touches
void insert( glm::dvec2 const &Centre, double const Reach, Value const &Inserted ) {
auto const low { key( Centre - glm::dvec2 { Reach } ) };
auto const high { key( Centre + glm::dvec2 { Reach } ) };
for( auto x { low.first }; x <= high.first; ++x ) {
for( auto z { low.second }; z <= high.second; ++z ) {
m_cells[ { x, z } ].push_back( Inserted );
}
}
}
// the values in the cell of the point, null where there are none
std::vector<Value> const *at( glm::dvec2 const &Point ) const {
auto const found { m_cells.find( key( Point ) ) };
return ( found != m_cells.end() ? &found->second : nullptr );
}
// whether the test holds for every value in the cells the square of the reach about the centre touches
template <typename Test>
bool all_about( glm::dvec2 const &Centre, double const Reach, Test const &Holds ) const {
auto const low { key( Centre - glm::dvec2 { Reach } ) };
auto const high { key( Centre + glm::dvec2 { Reach } ) };
for( auto x { low.first }; x <= high.first; ++x ) {
for( auto z { low.second }; z <= high.second; ++z ) {
auto const found { m_cells.find( { x, z } ) };
if( found != m_cells.end() && false == std::all_of( found->second.begin(), found->second.end(), Holds ) ) {
return false;
}
}
}
return true;
}
std::map<cell, std::vector<Value>> const &cells() const {
return m_cells;
}
private:
cell key( glm::dvec2 const &Point ) const {
return { static_cast<std::int64_t>( std::floor( Point.x / m_size ) ), static_cast<std::int64_t>( std::floor( Point.y / m_size ) ) };
}
double m_size;
std::map<cell, std::vector<Value>> m_cells;
};
} // namespace track_sleepers

709
world/SleeperLayout.cpp Normal file
View File

@@ -0,0 +1,709 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/SleeperLayout.h"
#include "world/SleeperFeatures.h"
#include "world/SleeperGeometry.h"
#include "world/SleeperLines.h"
#include "world/SleeperPaths.h"
#include "world/SleeperRules.h"
#include "world/Track.h"
#include <optional>
namespace track_sleepers {
namespace {
// whether the plain sleepers of the two paths would meet anywhere, so they lie on timbers together there
bool share_anywhere( sampled_path const &Left, sampled_path const &Right ) {
auto const margin { Left.half + Right.half + half_width + end_gap };
if( Left.low.x > Right.high.x + margin || Right.low.x > Left.high.x + margin || Left.low.y > Right.high.y + margin || Right.low.y > Left.high.y + margin ) {
return false;
}
for( auto const &pair : { std::make_pair( &Left, &Right ), std::make_pair( &Right, &Left ) } ) {
auto const &path { *pair.first };
for( std::size_t index = 0; index < path.points.size(); ++index ) {
if( shares_with( path, path.points[ index ], heading( path, path.distances[ index ] ), *pair.second ) ) {
return true;
}
}
}
return false;
}
// the stretches of the path where another path of the cluster runs so near that their plain sleepers would meet
std::vector<glm::dvec2> zones_of( sampled_path const &Path, std::vector<sampled_path> const &Paths, std::vector<feature> const &Crossings ) {
return stretches_where( Path, [&]( glm::dvec3 const &Point, glm::dvec2 const &Direction ) {
return std::any_of( Paths.begin(), Paths.end(), [&]( sampled_path const &Other ) {
if( &Other == &Path ) {
return false;
}
// tracks crossing share the timbers where their sleepers would meet, others only where they're too close to part them
auto const crossed { std::any_of( Crossings.begin(), Crossings.end(), [&]( feature const &Feature ) {
return ( Feature.paths[ 0 ] == &Path && Feature.paths[ 1 ] == &Other ) || ( Feature.paths[ 0 ] == &Other && Feature.paths[ 1 ] == &Path ); } ) };
if( crossed ) {
return shares_with( Path, Point, Direction, Other );
}
double gap;
glm::dvec2 otherdirection { Direction };
return beside( Other, Point, shared_spacing( Path, Other ), gap, otherdirection ) && gap < shared_spacing( Path, Other ); } ); } );
}
// the stretches left of the first ones once the second ones are taken out, those shorter than the least length left out too
std::vector<glm::dvec2> without( std::vector<glm::dvec2> const &Stretches, std::vector<glm::dvec2> const &Taken, double const Least ) {
std::vector<glm::dvec2> result { Stretches };
for( auto const &taken : Taken ) {
std::vector<glm::dvec2> left;
for( auto const &stretch : result ) {
if( stretch.y <= taken.x || stretch.x >= taken.y ) {
left.push_back( stretch );
continue;
}
if( stretch.x < taken.x ) {
left.emplace_back( stretch.x, taken.x );
}
if( stretch.y > taken.y ) {
left.emplace_back( taken.y, stretch.y );
}
}
result = std::move( left );
}
result.erase( std::remove_if( result.begin(), result.end(), [&]( glm::dvec2 const &Stretch ) { return Stretch.y - Stretch.x < Least; } ), result.end() );
return result;
}
// the beams of a cluster of tracks, laid together: lines of timbers led along the paths near other paths one after another, each
// line across all the rails of the cluster it reaches, and the plain sleepers of every path where no line carries it, in step
// with the last timber carrying it
class cluster_layout {
public:
cluster_layout( std::vector<TTrack *> const &Tracks, std::uint64_t const Epoch ) :
tracks( Tracks ), epoch( Epoch ) {}
std::shared_ptr<cluster_beams> lay() {
auto result { std::make_shared<cluster_beams>() };
result->epoch = epoch;
for( auto *track : tracks ) {
result->beams[ track ];
for( int path = 0; path < path_count( *track ); ++path ) {
paths.emplace_back( sample( *track, path ) );
}
}
paths.erase( std::remove_if( paths.begin(), paths.end(), []( sampled_path const &Path ) { return Path.points.size() < 2; } ), paths.end() );
for( auto *track : tracks ) {
for( auto &other : paths_around( *track ) ) {
if( std::find( tracks.begin(), tracks.end(), other.track ) != tracks.end()
|| std::any_of( context.begin(), context.end(), [&]( sampled_path const &Known ) { return Known.track == other.track && Known.path == other.path; } ) ) {
continue;
}
context.emplace_back( std::move( other ) );
}
}
features = find_features( paths );
for( auto const &feature : features ) {
if( false == feature.frog && false == feature.grid ) {
crossings.push_back( feature );
}
}
zones.resize( paths.size() );
records.resize( paths.size() );
done.resize( paths.size() );
for( std::size_t index = 0; index < paths.size(); ++index ) {
zones[ index ] = ( tracks.size() > 1 || paths.size() > 1 ? zones_of( paths[ index ], paths, crossings ) : std::vector<glm::dvec2> {} );
}
// the lines of timbers, led along the path first by priority with a stretch near another path no line carries yet
for( std::size_t round = 0; round < 1000; ++round ) {
auto best { paths.size() };
glm::dvec2 stretch { 0.0 };
for( std::size_t index = 0; index < paths.size(); ++index ) {
auto const left { without( zones[ index ], covered( index ), 0.3 ) };
if( left.empty() ) {
continue;
}
if( best == paths.size() || before( paths[ index ], paths[ best ] ) ) {
best = index;
stretch = *std::max_element( left.begin(), left.end(), []( glm::dvec2 const &Left, glm::dvec2 const &Right ) { return Left.y - Left.x < Right.y - Right.x; } );
}
}
if( best == paths.size() ) {
break;
}
lead( best, stretch );
done[ best ].push_back( stretch );
}
// the plain sleepers of each path, between the timbers carrying it and its ends
for( std::size_t index = 0; index < paths.size(); ++index ) {
lay_plain( index );
}
keep_apart();
fill_gaps();
keep_apart();
for( auto const &laid : beams ) {
result->beams[ laid.owner ].push_back( laid.value );
}
return result;
}
private:
struct owned {
TTrack const *owner;
beam value;
};
std::vector<TTrack *> const &tracks;
std::uint64_t const epoch; // of the layout the tracks are laid out for
std::vector<sampled_path> paths; // of the tracks of the cluster
std::vector<sampled_path> context; // of the tracks about them, whose rails and sleepers the beams keep clear of
std::vector<feature> features;
std::vector<feature> crossings; // the features where tracks cross, without the frogs
std::vector<std::vector<glm::dvec2>> zones; // of each path, where its plain sleepers would meet those of another one
// a line of timbers carrying a path: where along the path, and which way the line runs in plan
struct record {
double at;
glm::dvec2 along;
};
std::vector<std::vector<record>> records; // the lines of timbers carrying each path
std::vector<std::vector<glm::dvec2>> done; // the stretches of each path lines were led along
std::vector<owned> beams;
// the stretches of the path the lines laid so far carry it on, and those lines were led along
std::vector<glm::dvec2> covered( std::size_t const Index ) const {
auto result { done[ Index ] };
auto const at { distances( Index ) };
if( at.empty() ) {
return result;
}
auto start { at.front() };
auto last { at.front() };
for( std::size_t index = 1; index <= at.size(); ++index ) {
if( index < at.size() && at[ index ] - last <= 1.6 * pitch ) {
last = at[ index ];
continue;
}
result.emplace_back( start - 0.5 * pitch, last + 0.5 * pitch );
if( index < at.size() ) {
start = last = at[ index ];
}
}
return result;
}
// the pins of the lines led along the path: the rails of a path crossing this one so wide it's laid on a grid of beams; elsewhere
// the lines are spread evenly
std::vector<double> pins( sampled_path const &Path ) const {
std::vector<double> result;
for( auto const &feature : features ) {
if( false == feature.grid ) {
continue;
}
for( int side = 0; side < 2; ++side ) {
if( feature.paths[ side ] != &Path ) {
continue;
}
// under the rails of the other path, which runs across the beams
auto const *other { feature.paths[ 1 - side ] };
auto const sine { std::max( 0.5, std::sin( 2.0 * std::asin( feature.sine ) ) ) };
result.push_back( feature.at[ side ] - 0.5 * other->gauge / sine );
result.push_back( feature.at[ side ] + 0.5 * other->gauge / sine );
}
}
std::sort( result.begin(), result.end() );
return result;
}
// the line of the timbers carrying the path nearest to the distance on the side, between the distance and the reach from it
std::optional<record> record_near( std::size_t const Index, double const Distance, double const Side, double const Reach ) const {
std::optional<record> result;
for( auto const &line : records[ Index ] ) {
auto const along { ( line.at - Distance ) * Side };
if( along < -0.25 * pitch || along > Reach ) {
continue;
}
if( false == result.has_value() || std::abs( line.at - Distance ) < std::abs( result->at - Distance ) ) {
result = line;
}
}
return result;
}
// where along the path the lines of timbers carry it, in order
std::vector<double> distances( std::size_t const Index ) const {
std::vector<double> result;
for( auto const &line : records[ Index ] ) {
result.push_back( line.at );
}
std::sort( result.begin(), result.end() );
result.erase( std::unique( result.begin(), result.end(), []( double const Left, double const Right ) { return Right - Left < 0.05; } ), result.end() );
return result;
}
// where the path is at the distance and which way it runs in plan; false where it can't tell
bool frame_at( sampled_path const &Path, double const Distance, glm::dvec3 &Point, glm::vec3 &Angles, glm::dvec2 &Forward ) const {
Path.segment->RaPositionGet( Distance, Point, Angles );
glm::dvec3 behind, ahead;
glm::vec3 unused;
Path.segment->RaPositionGet( std::max( 0.0, Distance - 0.1 ), behind, unused );
Path.segment->RaPositionGet( std::min( Path.length, Distance + 0.1 ), ahead, unused );
Forward = plan( ahead - behind );
if( glm::length( Forward ) < 1e-6 ) {
return false;
}
Forward = glm::normalize( Forward );
return true;
}
// the lines at the distances along the path, turned as the frogs and crossings about them ask, but by no more than the rate
// from one line to the next, nor from the lines carrying the path at either end, so the ends of long timbers keep their spacing
void lay_lines( std::size_t const Index, std::vector<double> const &Stations, std::optional<record> const &First, std::optional<record> const &Last, double const Rate, bool const Led ) {
auto const &path { paths[ Index ] };
std::vector<double> distances;
std::vector<glm::dvec2> forwards;
std::vector<double> turns; // of the lines off square to the path, in plan
for( auto const station : Stations ) {
glm::dvec3 point;
glm::vec3 angles;
glm::dvec2 forward;
if( false == frame_at( path, station, point, angles, forward ) ) {
continue;
}
distances.push_back( station );
forwards.push_back( forward );
// the plain sleepers lie square to their track, turned only to meet the timbers next to them
auto along { Led ? beam_direction( point, forward, features ) : forward };
if( Led ) {
// the timbers about a crossing lie square to the mean way of the tracks they carry, the frogs about it don't turn them:
// square to the main tracks of a scissors crossing, to the bisector of a diamond
if( auto const mean { whole_at( path, point, forward, along ) }; mean.has_value() ) {
along = *mean;
}
}
turns.push_back( turn_off( forward, along ) );
}
if( distances.empty() ) {
return;
}
auto const limit = [&]( std::size_t const From, std::size_t const To, std::optional<record> const &Seed ) {
auto const step { To > From ? 1 : -1 };
std::optional<double> previous;
auto rate { Rate };
if( Seed.has_value() ) {
previous = turn_off( forwards[ From ], Seed->along );
if( false == Led ) {
// square after three sleepers
rate = std::max( 0.004, std::abs( *previous ) / 3.0 );
}
}
for( auto index { From }; ; index += step ) {
if( previous.has_value() ) {
turns[ index ] = std::clamp( turns[ index ], *previous - rate, *previous + rate );
}
previous = turns[ index ];
if( index == To ) {
break;
}
} };
limit( 0, distances.size() - 1, First );
limit( distances.size() - 1, 0, Last );
for( std::size_t index = 0; index < distances.size(); ++index ) {
lay_line( Index, distances[ index ], Led, rotated( forwards[ index ], turns[ index ] ) );
}
}
// the lines of timbers led along the stretch of the path, spread between the lines carrying it at either end, or the ends of the
// stretch, and the pins on it
void lead( std::size_t const Index, glm::dvec2 const &Stretch ) {
auto const &path { paths[ Index ] };
auto const back { record_near( Index, Stretch.x, -1.0, 2.0 * pitch ) };
auto const fore { record_near( Index, Stretch.y, 1.0, 2.0 * pitch ) };
anchor const start { back.has_value() ? anchor { back->at, 1.0 } : anchor { Stretch.x <= 0.01 ? 0.0 : Stretch.x, 0.5 } };
anchor const end { fore.has_value() ? anchor { fore->at, 1.0 } : anchor { Stretch.y >= path.length - 0.01 ? path.length : Stretch.y, 0.5 } };
if( end.at - start.at <= 0.0 ) {
return;
}
std::vector<anchor> anchors { start };
for( auto const pin : pins( path ) ) {
if( pin > start.at + 0.5 * pitch_min && pin < end.at - 0.5 * pitch_min && pin - anchors.back().at >= pitch_min ) {
anchors.push_back( { pin, 0.0 } );
}
}
anchors.push_back( end );
std::vector<double> stations;
for( std::size_t index = 1; index < anchors.size(); ++index ) {
spread( anchors[ index - 1 ], anchors[ index ], stations );
}
lay_lines( Index, stations, back, fore, timber_turn, true );
}
// where the timber led along the path through the point, lying the given way, carries both tracks of a crossing: the way it lies
// then, square to the middle between the ways of the tracks it carries turned furthest either way
std::optional<glm::dvec2> whole_at( sampled_path const &Path, glm::dvec3 const &Point, glm::dvec2 const &Forward, glm::dvec2 const &Along ) const {
if( crossings.empty() ) {
return std::nullopt;
}
std::vector<crossing> members;
find_crossings( Path, Point, Along, glm::dvec2 { -Along.y, Along.x }, 0.5 * length_cap + Path.reach, paths, members );
carry( Path, Forward, Along, members, false, false );
if( false == whole_with( Path, members ) ) {
return std::nullopt;
}
// the outermost tracks it carries, either way along it: the main tracks of a scissors crossing, the crossing ones of a diamond
std::pair<double, glm::dvec2> low { 0.0, Forward };
std::pair<double, glm::dvec2> high { 0.0, Forward };
for( auto const &crossing : members ) {
if( false == crossing.carried ) {
continue;
}
if( crossing.across < low.first ) {
low = { crossing.across, crossing.direction };
}
if( crossing.across > high.first ) {
high = { crossing.across, crossing.direction };
}
}
return rotated( Forward, 0.5 * ( turn_off( Forward, low.second ) + turn_off( Forward, high.second ) ) );
}
bool whole_with( sampled_path const &Path, std::vector<crossing> const &Members ) const {
auto const carries = [&]( sampled_path const *Carried ) {
return Carried == &Path || std::any_of( Members.begin(), Members.end(), [&]( crossing const &Crossing ) { return Crossing.carried && Crossing.path == Carried; } ); };
return std::any_of( crossings.begin(), crossings.end(), [&]( feature const &Feature ) {
return carries( Feature.paths[ 0 ] ) && carries( Feature.paths[ 1 ] ); } );
}
// the plain sleepers of the path on the stretches no line of timbers carries it, in step with the timbers at their ends
void lay_plain( std::size_t const Index ) {
auto const &path { paths[ Index ] };
auto const at { distances( Index ) };
std::vector<std::pair<anchor, anchor>> stretches;
if( at.empty() ) {
stretches.push_back( { { 0.0, 0.5 }, { path.length, 0.5 } } );
}
else {
stretches.push_back( { { 0.0, 0.5 }, { at.front(), 1.0 } } );
for( std::size_t index = 1; index < at.size(); ++index ) {
if( at[ index ] - at[ index - 1 ] > 1.6 * pitch ) {
stretches.push_back( { { at[ index - 1 ], 1.0 }, { at[ index ], 1.0 } } );
}
}
stretches.push_back( { { at.back(), 1.0 }, { path.length, 0.5 } } );
}
for( auto const &stretch : stretches ) {
auto const span { stretch.second.at - stretch.first.at };
if( span <= 0.0 || span < ( stretch.first.offset + stretch.second.offset - 1.0 ) * pitch + 0.5 * pitch_min ) {
continue;
}
std::vector<double> stations;
spread( stretch.first, stretch.second, stations );
// turned back square to the path from the timbers at the ends of the stretch
auto const first { stretch.first.offset == 1.0 ? record_near( Index, stretch.first.at, 1.0, 0.05 ) : std::optional<record> {} };
auto const last { stretch.second.offset == 1.0 ? record_near( Index, stretch.second.at, -1.0, 0.05 ) : std::optional<record> {} };
lay_lines( Index, stations, first, last, sleeper_turn, false );
}
}
// the beams on the line through the point of the path at the distance: the timber of the line led along it, taking the rails of
// the paths of the cluster it reaches, or the plain sleeper of the path alone
void lay_line( std::size_t const Index, double const Distance, bool const Led, glm::dvec2 const &Along ) {
auto const &path { paths[ Index ] };
glm::dvec3 point;
glm::vec3 angles;
glm::dvec2 forward;
if( false == frame_at( path, Distance, point, angles, forward ) ) {
return;
}
auto const along { Along };
glm::dvec2 const across { -along.y, along.x };
auto const reach { 0.5 * length_cap + path.reach };
std::vector<crossing> members, others;
find_crossings( path, point, along, across, reach, paths, members );
find_crossings( path, point, along, across, reach, context, others );
if( Led ) {
// about a crossing the timbers take all the tracks whose sleepers would meet, elsewhere only those too close to part
carry( path, forward, along, members, false );
if( false == whole_with( path, members ) ) {
for( auto &crossing : members ) {
crossing.carried = false;
}
carry( path, forward, along, members, true );
}
}
std::vector<band> bands { band_of( path, 0.0, std::max( crossing_sine_min, std::abs( glm::dot( forward, along ) ) ), forward ) };
for( auto const *crossings : { &members, &others } ) {
for( auto const &crossing : *crossings ) {
if( crossing.carried ) {
bands.push_back( band_of( *crossing.path, crossing.across, crossing.sine, crossing.direction ) );
}
else if( crossing.sine >= crossing_sine_min && angle_between( crossing.direction, forward ) < grid_angle ) {
bands.push_back( band_of( *crossing.path, crossing.across, crossing.sine, crossing.direction, false ) );
}
}
}
// a timber carrying both tracks of a crossing lies whole under all the tracks it carries
auto const whole { Led && whole_with( path, members ) };
std::vector<piece> pieces;
emit_line( bands, pieces, whole );
cut_long( bands, pieces );
// the parts of a timber parted between tracks going apart turn square to their own track, the more the further apart the
// tracks run, till square where their plain sleepers wouldn't touch any more
std::vector<glm::dvec2> turned( pieces.size(), along );
for( std::size_t index = 0; index < pieces.size() && pieces.size() > 1; ++index ) {
auto const *own { pieces[ index ].single };
if( own == nullptr ) {
continue;
}
band const *nearest { nullptr };
for( auto const &other : bands ) {
if( &other != own && other.carried && ( nearest == nullptr || std::abs( other.centre - own->centre ) < std::abs( nearest->centre - own->centre ) ) ) {
nearest = &other;
}
}
if( nearest == nullptr ) {
continue;
}
auto const spacing { std::abs( nearest->centre - own->centre ) * std::min( own->sine, nearest->sine ) };
auto const parted { shared_spacing( *own->path, *nearest->path ) };
auto const apart { own->path->half + nearest->path->half + end_gap };
auto const share { apart > parted ? std::clamp( ( spacing - parted ) / ( apart - parted ), 0.0, 1.0 ) : 1.0 };
turned[ index ] = rotated( along, share * turn_off( along, own->direction ) );
}
auto const turned_for = [&]( sampled_path const *Path ) {
for( std::size_t index = 0; index < pieces.size(); ++index ) {
if( pieces[ index ].single != nullptr && pieces[ index ].single->path == Path ) {
return turned[ index ];
}
}
return along; };
if( Led ) {
records[ Index ].push_back( { Distance, turned_for( &path ) } );
for( auto const &crossing : members ) {
if( crossing.carried ) {
records[ crossing.path - paths.data() ].push_back( { crossing.at, turned_for( crossing.path ) } );
}
}
}
// the beam lies tilted with the roll of the path, as the ballast is
glm::dvec3 const flatacross { across.x, 0.0, across.y };
glm::dvec3 const up { 0.0, 1.0, 0.0 };
auto const roll { static_cast<double>( angles.x ) };
auto const rolledacross { flatacross * std::cos( roll ) - up * std::sin( roll ) };
auto const rolledup { flatacross * std::sin( roll ) + up * std::cos( roll ) };
auto const top { point - rolledup * static_cast<double>( path.track->rail_height() ) };
// a sleeper of a plain track running alone and square to it, the rest are the timbers of the switches and crossings
auto const carried { std::count_if( bands.begin(), bands.end(), []( band const &Band ) { return Band.carried; } ) };
auto const plain { path.track->eType == tt_Normal && carried == 1 && pieces.size() == 1 && false == pieces.front().clipped && std::abs( glm::dot( forward, along ) ) > 0.9998 };
for( std::size_t index = 0; index < pieces.size(); ++index ) {
auto const &piece { pieces[ index ] };
auto const &way { turned[ index ] };
if( way == along ) {
beams.push_back( { path.track, { top + rolledacross * piece.from, top + rolledacross * piece.to, glm::dvec3 { along.x, 0.0, along.y }, plain, piece.corefrom - piece.from, piece.coreto - piece.from } } );
continue;
}
// turned about where the middle of its track crosses the line
auto const centre { top + rolledacross * piece.single->centre };
glm::dvec3 const turnedacross { glm::dvec3 { -way.y, 0.0, way.x } * std::cos( roll ) - up * std::sin( roll ) };
beams.push_back( { path.track, { centre + turnedacross * ( piece.from - piece.single->centre ), centre + turnedacross * ( piece.to - piece.single->centre ), glm::dvec3 { way.x, 0.0, way.y }, false, piece.corefrom - piece.from, piece.coreto - piece.from } } );
}
}
// a sleeper of the path put in where the rails of it go further without a beam under them than the pitch lets them, turned
// as the beams about it are: where beams were left out or lines of timbers led along different paths meet
void fill_gaps() {
auto const count { beams.size() };
// the beams by the cells of a grid they reach into
plan_grid<std::size_t> grid { 2.0 };
for( std::size_t laid = 0; laid < count; ++laid ) {
auto const &beam { beams[ laid ].value };
grid.insert( plan( 0.5 * ( beam.from + beam.to ) ), 0.5 * glm::length( plan( beam.to - beam.from ) ) + half_width, laid );
}
for( std::size_t index = 0; index < paths.size(); ++index ) {
auto const &path { paths[ index ] };
// where along the path beams lie under each of its rails, and which way they run
auto const steps { std::max( 2, static_cast<int>( std::ceil( path.length / 0.05 ) ) ) };
std::vector<double> gaps;
std::vector<glm::dvec2> turns;
for( auto const side : { -1.0, 1.0 } ) {
std::optional<std::pair<double, glm::dvec2>> last;
for( int step = 0; step <= steps; ++step ) {
auto const distance { path.length * step / steps };
glm::dvec3 point, across;
path.segment->plan_frame( distance, point, across );
auto const rail { plan( point + across * ( side * 0.5 * path.gauge ) ) };
std::optional<glm::dvec2> found;
auto const *cell { grid.at( rail ) };
if( cell == nullptr ) {
continue;
}
for( auto const laid : *cell ) {
auto const box { box_of( beams[ laid ].value ) };
if( 2.0 * box.half >= 0.1 && holds( box, rail ) ) {
found = glm::dvec2 { -box.axis.y, box.axis.x };
break;
}
}
if( false == found.has_value() ) {
continue;
}
if( last.has_value() && distance - last->first > 0.06 && distance - last->first + 2.0 * half_width > 1.25 * pitch_max ) {
// the middle of the stretch without one, and the way the beams at its ends run
gaps.push_back( 0.5 * ( distance + last->first ) );
auto along { last->second + ( glm::dot( last->second, *found ) < 0.0 ? -*found : *found ) };
turns.push_back( glm::length( along ) > 1e-9 ? glm::normalize( along ) : heading( path, gaps.back() ) );
}
last = std::make_pair( distance, *found );
}
}
// a sleeper in each, those of both rails about one place put in once
for( std::size_t gap = 0; gap < gaps.size(); ++gap ) {
if( std::any_of( gaps.begin(), gaps.begin() + gap, [&]( double const Other ) { return std::abs( Other - gaps[ gap ] ) < 0.3; } ) ) {
continue;
}
lay_line( index, gaps[ gap ], false, turns[ gap ] );
}
}
}
// a beam over one laid before it is left out: where tracks cross too wide for one timber to take both, or the lines of two
// stretches meet at an angle
void keep_apart() {
plan_grid<beam_box> grid { 3.0 };
std::vector<owned> kept;
auto const clear_of_laid = [&]( beam_box const &Box ) {
return grid.all_about( Box.centre, Box.half + half_width, [&]( beam_box const &Other ) { return apart( Box, Other ); } ); };
for( auto laid : beams ) {
auto const length { glm::length( laid.value.to - laid.value.from ) };
if( length < 0.1 ) {
continue;
}
if( false == clear_of_laid( box_of( laid.value ) ) ) {
// shortened from either end or both, down to the least overhang past the rails it carries, till it's clear
auto const direction { ( laid.value.to - laid.value.from ) / length };
auto const most { std::max( 0.0, laid.value.corefrom - overhang_min ) }; // off the from end
auto const mostto { std::max( 0.0, length - laid.value.coreto - overhang_min ) };
auto found { false };
for( int step = 1; step <= 20 && false == found; ++step ) {
auto const share { step / 20.0 };
for( auto const ends : { glm::dvec2 { 0.0, mostto * share }, glm::dvec2 { most * share, 0.0 }, glm::dvec2 { most * share, mostto * share } } ) {
auto shortened { laid.value };
shortened.from = laid.value.from + direction * ends.x;
shortened.to = laid.value.to - direction * ends.y;
shortened.corefrom -= ends.x;
shortened.coreto -= ends.x;
if( clear_of_laid( box_of( shortened ) ) ) {
shortened.plain = false;
laid.value = shortened;
found = true;
break;
}
}
}
if( false == found ) {
++faults().dropped;
continue;
}
++faults().clipped;
}
auto const box { box_of( laid.value ) };
grid.insert( box.centre, box.half + half_width, box );
kept.push_back( laid );
}
beams = std::move( kept );
}
};
} // namespace
// the tracks whose timbers are laid together with those of the track: all those reached from it through paths whose plain
// sleepers would meet, in the order of their names
std::vector<TTrack *> cluster_of( TTrack &Track ) {
std::vector<TTrack *> result { &Track };
for( std::size_t index = 0; index < result.size() && result.size() < cluster_cap; ++index ) {
auto const around { paths_around( *result[ index ] ) };
for( auto const &path : around ) {
if( path.track != result[ index ] ) {
continue;
}
for( auto const &other : around ) {
if( other.track == result[ index ] || std::find( result.begin(), result.end(), other.track ) != result.end() ) {
continue;
}
if( share_anywhere( path, other ) ) {
result.push_back( other.track );
}
}
}
}
std::sort( result.begin(), result.end(), []( TTrack const *Left, TTrack const *Right ) {
return ( Left->name() != Right->name() ? Left->name() < Right->name() : Left < Right ); } );
return result;
}
std::shared_ptr<cluster_beams> lay_out( std::vector<TTrack *> const &Tracks, std::uint64_t const Epoch ) {
return cluster_layout( Tracks, Epoch ).lay();
}
// how far the paths of the switch stand off one plane where they share the timbers of its panel, and where along the diverging
// path; the switch itself is out of shape for its timbers where it's more than the tolerance
std::pair<double, double> panel_off_plane( TTrack &Track ) {
std::pair<double, double> result { 0.0, 0.0 };
if( Track.eType != tt_Switch ) {
return result;
}
auto const main { sample( Track, 0 ) };
auto const diverging { sample( Track, 1 ) };
if( main.points.size() < 2 || diverging.points.size() < 2 ) {
return result;
}
auto alongside { false };
for( std::size_t index = 0; index < diverging.points.size(); ++index ) {
double gap;
glm::dvec2 direction;
if( false == beside( main, diverging.points[ index ], shared_spacing( diverging, main ), gap, direction ) ) {
if( alongside ) {
// apart far enough to lay their own
break;
}
// at the start, which both paths share
continue;
}
alongside = true;
auto const off { off_plane( diverging, diverging.points[ index ], main ) };
if( off > result.first ) {
result = { off, diverging.distances[ index ] };
}
}
return result;
}
} // namespace track_sleepers

38
world/SleeperLayout.h Normal file
View File

@@ -0,0 +1,38 @@
/*
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 <cstdint>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "world/TrackSleepers.h"
// the beams of a cluster of tracks laid out together
namespace track_sleepers {
// the beams of a cluster of tracks laid together, by the track each is laid with
struct cluster_beams {
std::uint64_t epoch { 0 };
std::map<TTrack const *, std::vector<beam>> beams;
};
// the tracks whose timbers are laid together with those of the track: all those reached from it through paths whose plain
// sleepers would meet, in the order of their names
std::vector<TTrack *> cluster_of( TTrack &Track );
// the beams of the tracks of a cluster, laid out together for the layout of the given epoch
std::shared_ptr<cluster_beams> lay_out( std::vector<TTrack *> const &Tracks, std::uint64_t const Epoch );
// how far the paths of the switch stand off one plane where they share the timbers of its panel, and where along the diverging
// path; the switch itself is out of shape for its timbers where it's more than the tolerance
std::pair<double, double> panel_off_plane( TTrack &Track );
} // namespace track_sleepers

276
world/SleeperLines.cpp Normal file
View File

@@ -0,0 +1,276 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/SleeperLines.h"
#include "world/SleeperGeometry.h"
#include "world/SleeperRules.h"
namespace track_sleepers {
// the beams spread evenly between two anchors, the pin at the first one included and the one at the last left to the next stretch
void spread( anchor const &From, anchor const &To, std::vector<double> &Stations ) {
auto const span { To.at - From.at };
if( span <= 0.0 ) {
return;
}
auto count { static_cast<int>( std::round( span / pitch + 1.0 - From.offset - To.offset ) ) };
if( From.offset == 0.5 && To.offset == 0.5 ) {
// a track between two others gets a beam however short it is
count = std::max( 1, count );
}
// the rails are carried at most so far apart, the beams pulled closer where it takes one more
while( span / ( count - 1 + From.offset + To.offset ) > pitch_max ) {
++count;
}
auto const intervals { count - 1 + From.offset + To.offset };
if( count < 1 || intervals <= 0.0 ) {
if( From.offset == 0.0 ) {
Stations.push_back( From.at );
}
return;
}
auto const step { span / intervals };
auto const last { To.offset == 0.0 ? count - 1 : count };
for( int index = 0; index < last; ++index ) {
Stations.push_back( From.at + ( From.offset + index ) * step );
}
}
void find_crossings( sampled_path const &Path, glm::dvec3 const &Point, glm::dvec2 const &Along, glm::dvec2 const &Across, double const Reach, std::vector<sampled_path> const &Around, std::vector<crossing> &Crossings ) {
for( auto const &other : Around ) {
if( &other == &Path || false == near_bounds( other, Point, Reach ) ) {
continue;
}
for( std::size_t index = 0; index + 1 < other.points.size(); ++index ) {
auto const &from { other.points[ index ] };
auto const &to { other.points[ index + 1 ] };
auto const fromside { glm::dot( plan( from - Point ), Along ) };
auto const toside { glm::dot( plan( to - Point ), Along ) };
if( ( fromside < 0.0 ) == ( toside < 0.0 ) ) {
continue;
}
auto const share { fromside / ( fromside - toside ) };
auto const point { from + ( to - from ) * share };
auto const across { glm::dot( plan( point - Point ), Across ) };
if( std::abs( across ) > Reach || std::abs( point.y - Point.y ) > level_tolerance ) {
continue;
}
auto const direction { plan( to - from ) };
if( glm::dot( direction, direction ) < 1e-12 ) {
continue;
}
auto const unit { glm::normalize( direction ) };
auto const at { glm::mix( other.distances[ index ], other.distances[ index + 1 ], share ) };
Crossings.push_back( { &other, point, unit, at, across, std::abs( glm::dot( unit, Along ) ), false } );
}
}
}
// the paths whose rails the line of the beam through the point of the path takes, marked among the paths of its cluster crossing
// the line: those whose plain sleepers would meet the sleepers of the path or of any the line takes already, at a narrow enough
// angle to the line, and in one plane with the path unless they run too close to be parted
// the timber takes the rails of a path whose plain sleepers would meet those of a path it takes already; where Tight, only of one so
// close that their sleepers couldn't be laid apart even cut short
void carry( sampled_path const &Path, glm::dvec2 const &Forward, glm::dvec2 const &Along, std::vector<crossing> &Crossings, bool const Tight, bool const Count ) {
struct carried_path {
double across;
double half;
double rails;
double sine;
};
std::vector<carried_path> carried { { 0.0, Path.half, Path.rails, std::max( crossing_sine_min, std::abs( glm::dot( Forward, Along ) ) ) } };
for( auto added { true }; added; ) {
added = false;
for( auto &crossing : Crossings ) {
if( crossing.carried || crossing.sine < crossing_sine_min || angle_between( crossing.direction, Forward ) >= grid_angle ) {
continue;
}
auto const close { std::any_of( carried.begin(), carried.end(), [&]( carried_path const &Carried ) {
auto const reach { Tight ? crossing.path->rails + Carried.rails + 2.0 * overhang_min + end_gap : crossing.path->half + Carried.half + end_gap };
return std::abs( crossing.across - Carried.across ) * std::min( crossing.sine, Carried.sine ) < reach; } ) };
if( false == close ) {
continue;
}
if( off_plane( *crossing.path, crossing.point, Path ) > coplanar_tolerance ) {
if( std::abs( crossing.across ) * crossing.sine >= shared_spacing( *crossing.path, Path ) ) {
continue;
}
// too close to the path to be parted, out of the plane of its rails
if( Count ) {
++faults().offplane;
}
}
crossing.carried = true;
added = true;
carried.push_back( { crossing.across, crossing.path->half, crossing.path->rails, crossing.sine } );
}
}
// the line doesn't carry the rails of a track past those of one it doesn't, that one lays its own beams there
auto const blocks = [&]( crossing const &Other ) {
return false == Other.carried && Other.sine >= crossing_sine_min && angle_between( Other.direction, Forward ) < grid_angle; };
for( auto demoted { true }; demoted; ) {
demoted = false;
for( auto &crossing : Crossings ) {
if( false == crossing.carried ) {
continue;
}
if( std::any_of( Crossings.begin(), Crossings.end(), [&]( struct crossing const &Other ) {
return blocks( Other ) && Other.across * crossing.across > 0.0 && std::abs( Other.across ) < std::abs( crossing.across ); } ) ) {
crossing.carried = false;
demoted = true;
}
}
}
}
band band_of( sampled_path const &Path, double const Across, double const Sine, glm::dvec2 const &Direction, bool const Carried ) {
return { Across - Path.rails / Sine, Across + Path.rails / Sine, overhang / Sine, overhang_min / Sine, Carried, &Path, Across, Direction, Sine };
}
// the beams on the line through the point: one timber under all the rails it carries, the ends keeping clear of the rails of the
// tracks the line doesn't carry and of the sleepers under them. the timber is parted between the rails of two tracks where each
// keeps at least its least overhang past its rails, unless it's whole: about a crossing of the tracks, as in a scissors crossing
void emit_line( std::vector<band> &Bands, std::vector<piece> &Pieces, bool const Whole ) {
std::sort( Bands.begin(), Bands.end(), []( band const &Left, band const &Right ) { return Left.from < Right.from; } );
auto open { false };
piece current;
auto end { 0.0 }, endoverhang { 0.0 }, endoverhangmin { 0.0 };
// the far side of the last rails not carried, and of the sleepers under them
auto fenced { false };
auto fencerails { 0.0 }, fencereach { 0.0 };
for( auto const &next : Bands ) {
if( false == next.carried ) {
if( open ) {
auto const kept { std::max( 0.0, std::clamp( next.from - next.overhang - end - end_gap, std::min( endoverhangmin, next.from - end - end_gap ), endoverhang ) ) };
current.clipped = current.clipped || kept < endoverhang;
if( kept < endoverhangmin ) {
++faults().clipped;
}
current.to = end + kept;
current.coreto = end;
Pieces.push_back( current );
open = false;
}
if( false == fenced || next.to > fencerails ) {
fencerails = next.to;
fencereach = next.to + next.overhang;
}
fenced = true;
continue;
}
if( false == open ) {
current = piece();
auto startoverhang { next.overhang };
if( fenced ) {
startoverhang = std::max( 0.0, std::clamp( next.from - fencereach - end_gap, std::min( next.overhangmin, next.from - fencerails - end_gap ), next.overhang ) );
current.clipped = startoverhang < next.overhang;
if( startoverhang < next.overhangmin ) {
++faults().clipped;
}
}
current.from = next.from - startoverhang;
current.corefrom = next.from;
current.single = &next;
end = next.to;
endoverhang = next.overhang;
endoverhangmin = next.overhangmin;
open = true;
continue;
}
if( false == Whole && next.from - end >= endoverhangmin + next.overhangmin + end_gap ) {
// the room between them shared by their overhangs
auto const room { next.from - end - end_gap };
auto const ours { std::min( endoverhang, room * endoverhang / ( endoverhang + next.overhang ) ) };
auto const theirs { std::min( next.overhang, room - ours ) };
current.to = end + ours;
current.coreto = end;
current.clipped = current.clipped || ours < endoverhang;
Pieces.push_back( current );
current = piece();
current.from = next.from - theirs;
current.corefrom = next.from;
current.clipped = theirs < next.overhang;
current.single = &next;
end = next.to;
endoverhang = next.overhang;
endoverhangmin = next.overhangmin;
continue;
}
// one timber under the rails of all the tracks it carries
current.single = nullptr;
if( next.to > end ) {
end = next.to;
endoverhang = next.overhang;
endoverhangmin = next.overhangmin;
}
}
if( open ) {
current.to = end + endoverhang;
current.coreto = end;
Pieces.push_back( current );
}
}
// a timber longer than the cap cut in two where the rails of the tracks it carries are furthest apart, the ends sharing the
// room between them, till every piece is short enough or there's nowhere to cut
void cut_long( std::vector<band> const &Bands, std::vector<piece> &Pieces ) {
for( std::size_t index = 0; index < Pieces.size(); ) {
auto &whole { Pieces[ index ] };
if( whole.to - whole.from <= length_cap ) {
++index;
continue;
}
// the widest gap between the rails under the timber
band const *left { nullptr };
band const *right { nullptr };
band const *last { nullptr };
auto widest { 0.0 };
auto end { std::numeric_limits<double>::lowest() };
for( auto const &band : Bands ) {
if( false == band.carried || band.from < whole.from || band.to > whole.to ) {
continue;
}
if( last != nullptr && band.from - end > widest ) {
widest = band.from - end;
left = last;
right = &band;
}
if( band.to > end ) {
end = band.to;
last = &band;
}
}
if( left == nullptr ) {
++index;
continue;
}
auto const room { std::max( 0.0, widest - end_gap ) };
auto const ours { std::min( left->overhang, room * left->overhang / ( left->overhang + right->overhang ) ) };
auto const theirs { std::min( right->overhang, room - ours ) };
if( ours < left->overhangmin || theirs < right->overhangmin ) {
++faults().clipped;
}
piece second { right->from - theirs, whole.to, true, right->from, whole.coreto, nullptr };
whole.to = left->to + ours;
whole.coreto = left->to;
whole.clipped = true;
Pieces.insert( Pieces.begin() + index + 1, second );
}
}
} // namespace track_sleepers

82
world/SleeperLines.h Normal file
View File

@@ -0,0 +1,82 @@
/*
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 <vector>
#include <glm/glm.hpp>
#include "world/SleeperPaths.h"
// the lines of beams across the tracks: where the beams go along the paths, which rails a line takes and where the beams on it
// part and end
namespace track_sleepers {
// a place along a path the beams are spread from: a pin with a beam at it, a free end of the track with the first beam half
// a step from it, so the beams keep the pitch over the joints, or a timber of another path which carries the rails of this
// one past it, with the first beam of this one a step from it
struct anchor {
double at { 0.0 };
double offset { 0.5 }; // how many steps the first beam is from the anchor: 0 pin, 0.5 free end, 1 timber of another path
};
// where another path crosses the line of a beam
struct crossing {
sampled_path const *path { nullptr };
glm::dvec3 point;
glm::dvec2 direction; // of the path, in plan
double at { 0.0 }; // the distance of the point along the path
double across { 0.0 }; // along the beam, from the path laying it
double sine { 1.0 }; // of the angle between the path and the beam
bool carried { false };
};
// the stretch of a beam line the rails of a path take, and how far the beam may reach past them
struct band {
double from { 0.0 };
double to { 0.0 };
double overhang { 0.0 };
double overhangmin { 0.0 };
bool carried { true }; // the rails are laid on the line, else the beam keeps clear of them and of the sleepers under them
sampled_path const *path { nullptr };
double centre { 0.0 }; // where the middle of the path crosses the line
glm::dvec2 direction { 0.0 }; // of the path there, in plan
double sine { 1.0 }; // of the angle between the path and the line
};
// a beam on a line, by where its ends are along the line
struct piece {
double from { 0.0 };
double to { 0.0 };
bool clipped { false }; // an end cut short to keep clear of the rails of another track
double corefrom { 0.0 }; // the stretch the feet of the rails it carries take
double coreto { 0.0 };
band const *single { nullptr }; // the rails of the only track it carries, null where it carries more
};
// the beams spread evenly between two anchors, the pin at the first one included and the one at the last left to the next stretch
void spread( anchor const &From, anchor const &To, std::vector<double> &Stations );
void find_crossings( sampled_path const &Path, glm::dvec3 const &Point, glm::dvec2 const &Along, glm::dvec2 const &Across, double const Reach, std::vector<sampled_path> const &Around, std::vector<crossing> &Crossings );
// the paths whose rails the line of the beam through the point of the path takes, marked among the paths of its cluster crossing
// the line: those whose plain sleepers would meet the sleepers of the path or of any the line takes already, at a narrow enough
// angle to the line, and in one plane with the path unless they run too close to be parted
// the timber takes the rails of a path whose plain sleepers would meet those of a path it takes already; where Tight, only of one so
// close that their sleepers couldn't be laid apart even cut short
void carry( sampled_path const &Path, glm::dvec2 const &Forward, glm::dvec2 const &Along, std::vector<crossing> &Crossings, bool const Tight, bool const Count = true );
band band_of( sampled_path const &Path, double const Across, double const Sine, glm::dvec2 const &Direction, bool const Carried = true );
// the beams on the line through the point: one timber under all the rails it carries, the ends keeping clear of the rails of the
// tracks the line doesn't carry and of the sleepers under them. the timber is parted between the rails of two tracks where each
// keeps at least its least overhang past its rails, unless it's whole: about a crossing of the tracks, as in a scissors crossing
void emit_line( std::vector<band> &Bands, std::vector<piece> &Pieces, bool const Whole );
// a timber longer than the cap cut in two where the rails of the tracks it carries are furthest apart, the ends sharing the
// room between them, till every piece is short enough or there's nowhere to cut
void cut_long( std::vector<band> const &Bands, std::vector<piece> &Pieces );
} // namespace track_sleepers

229
world/SleeperModels.cpp Normal file
View File

@@ -0,0 +1,229 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/TrackSleepers.h"
#include "world/TrackFeatures.h"
#include "world/SleeperRules.h"
#include "world/Track.h"
#include "model/Model3d.h"
#include "model/MdlMngr.h"
#include "rendering/renderer.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include <glm/gtc/type_ptr.hpp>
#include <filesystem>
#include <fstream>
#include <iterator>
namespace track_sleepers {
namespace {
// the models of the beams found so far, by their names and skins and seats
std::map<std::string, beam_model> &known_models() {
static std::map<std::string, beam_model> models;
return models;
}
// the vertices of the submodel in the space of the model, through its own transform
template <typename Visit>
void vertices_of( TSubModel *Submodel, Visit Visitor ) {
glm::mat4 transform { 1.f };
if( ( Submodel->iFlags & 0xC000 ) && ( Submodel->GetMatrix() != nullptr ) ) {
transform = glm::make_mat4( Submodel->GetMatrix()->readArray() );
}
auto const &vertices { Submodel->m_geometry.handle != null_handle ? GfxRenderer->Vertices( Submodel->m_geometry.handle ) : Submodel->Vertices };
for( auto const &vertex : vertices ) {
Visitor( glm::vec3 { transform * glm::vec4 { vertex.position, 1.f } } );
}
}
// the model of the beams of the name and skin, loaded and measured on the first use; null when it can't be loaded. the feet of
// the rails go to the given seat in it, or to its top where the seat is below zero
beam_model const *find_model( std::string const &Name, std::string const &Skinname, float const Seat ) {
if( Name.empty() ) {
return nullptr;
}
auto const Skin { Skinname == "none" ? std::string {} : Skinname };
auto const key { Name + '|' + Skin + '|' + std::to_string( Seat ) };
auto &models { known_models() };
auto found { models.find( key ) };
if( found == models.end() ) {
auto &model { models[ key ] };
model.name = Name;
model.skinname = Skin;
model.model = TModelsManager::GetModel( Name, false );
if( model.model == nullptr ) {
ErrorLog( "Bad sleepers3d: model \"" + Name + "\" failed to load" );
return nullptr;
}
if( false == Skin.empty() ) {
model.skin = GfxRenderer->Fetch_Material( Skin );
}
// a timber put together from its parts, with a part between the ends stretched and repeated to the length
std::array<char const *, part_count> const names { "begin", "main", "end" };
model.stretchable = true;
for( int part = 0; part < part_count; ++part ) {
model.parts[ part ] = model.model->GetFromName( names[ part ] );
model.stretchable = model.stretchable && model.parts[ part ] != nullptr;
}
auto top { std::numeric_limits<float>::lowest() };
model.span = { std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest() };
if( model.stretchable ) {
for( int part = 0; part < part_count; ++part ) {
glm::vec2 span { std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest() };
vertices_of( model.parts[ part ], [&]( glm::vec3 const &Position ) {
span = { std::min( span.x, Position.x ), std::max( span.y, Position.x ) };
top = std::max( top, Position.y ); } );
model.spans[ part ] = span;
model.span = { std::min( model.span.x, span.x ), std::max( model.span.y, span.y ) };
}
model.stretchable = model.spans[ part_main ].y > model.spans[ part_main ].x;
}
else {
// a whole model, measured on its most detailed submodels, the others being the same beam seen from further
model.parts = {};
for( auto *submodel { model.model->Root }; submodel != nullptr; submodel = submodel->Next ) {
if( submodel->fSquareMinDist > 0.f ) {
continue;
}
vertices_of( submodel, [&]( glm::vec3 const &Position ) {
model.span = { std::min( model.span.x, Position.x ), std::max( model.span.y, Position.x ) };
top = std::max( top, Position.y ); } );
}
}
// the levels of detail, by the distances they're seen from
for( auto *submodel { model.model->Root }; submodel != nullptr; submodel = submodel->Next ) {
if( std::find( model.bands.begin(), model.bands.end(), submodel->fSquareMinDist ) == model.bands.end() ) {
model.bands.push_back( submodel->fSquareMinDist );
}
}
std::sort( model.bands.begin(), model.bands.end() );
// the rails sit on the seat of the sleeper, below the fastenings sticking up over it
model.seat = ( Seat >= 0.f ? Seat : top );
if( model.length() < 0.5f ) {
ErrorLog( "Bad sleepers3d: model \"" + Name + "\" has no beam along x" );
model.model = nullptr;
return nullptr;
}
WriteLog(
"sleepers3d: model \"" + Name + "\"" + ( model.stretchable ? std::string { " of parts" } : std::string { " whole" } )
+ ", " + to_string( model.span.x, 3 ) + ".." + to_string( model.span.y, 3 )
+ ( model.stretchable ?
", main " + to_string( model.spans[ part_main ].x, 3 ) + ".." + to_string( model.spans[ part_main ].y, 3 ) :
std::string {} )
+ ", seat " + to_string( model.seat, 3 ) );
return &model;
}
return ( found->second.model != nullptr ? &found->second : nullptr );
}
} // namespace
beam_model const *timber_model() {
if( false == track_features::sleepers ) {
return nullptr;
}
return find_model( Global.Sleepers3D, std::string {}, -1.f );
}
beam_model const *plain_model() {
return find_model( Global.Sleepers3DPlain, Global.Sleepers3DPlainSkin, Global.Sleepers3DPlainSeat );
}
beam_model const *timber_model( TTrack const &Track ) {
if( false == Track.m_timber_model_name.empty() ) {
if( auto const *model { find_model( Track.m_timber_model_name, Track.m_timber_skin_name, -1.f ) }; model != nullptr ) {
return model;
}
}
return timber_model();
}
beam_model const *plain_model( TTrack const &Track ) {
if( Track.m_sleeper_enabled && false == Track.m_sleeper_model_name.empty() ) {
// the sleepers of the track drawn as they are in the model, their tops at the feet of the rails
if( auto const *model { find_model( Track.m_sleeper_model_name, Track.m_sleeper_skin_name, -1.f ) }; model != nullptr ) {
return model;
}
}
return plain_model();
}
double plain_length( TTrack const &Track ) {
if( auto const *model { plain_model( Track ) }; model != nullptr ) {
return model->length();
}
return 2.0 * ( 0.5 * std::abs( Track.fTrackWidth ) + rail_reach + overhang );
}
std::vector<pack_model> const &pack_models() {
static std::vector<pack_model> models;
static auto scanned { false };
if( scanned ) {
return models;
}
scanned = true;
namespace fs = std::filesystem;
std::error_code error;
auto const root { fs::path { paths::models } };
for( fs::recursive_directory_iterator entry { root, fs::directory_options::follow_directory_symlink | fs::directory_options::skip_permission_denied, error }, end;
entry != end; entry.increment( error ) ) {
if( error ) {
break;
}
if( false == entry->is_regular_file( error ) ) {
continue;
}
auto const extension { ToLower( entry->path().extension().string() ) };
if( extension != ".t3d" && extension != ".e3d" ) {
continue;
}
auto name { ToLower( fs::relative( entry->path(), root, error ).replace_extension().generic_string() ) };
if( error || name.empty() ) {
continue;
}
if( name.find( "podklad" ) == std::string::npos && name.find( "podrozjazd" ) == std::string::npos
&& name.find( "sleeper" ) == std::string::npos && name.find( "timber" ) == std::string::npos ) {
continue;
}
// a timber model has the parts it's put together from, told by their names in the file
std::ifstream file { entry->path(), std::ios::binary };
std::string const content { std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>() };
auto const has = [&]( std::string const &Part ) {
return (
extension == ".t3d" ?
content.find( "Name: " + Part + "\n" ) != std::string::npos || content.find( "Name: " + Part + " " ) != std::string::npos
|| content.find( "Name: " + Part + "\r" ) != std::string::npos :
content.find( std::string { '\0' } + Part + '\0' ) != std::string::npos ); };
auto const timber { has( "begin" ) && has( "main" ) && has( "end" ) };
if( std::none_of( models.begin(), models.end(), [&]( pack_model const &Model ) { return Model.name == name; } ) ) {
models.push_back( { name, timber } );
}
}
std::sort( models.begin(), models.end(), []( pack_model const &Left, pack_model const &Right ) {
return ( Left.timber != Right.timber ? Left.timber : Left.name < Right.name ); } );
return models;
}
} // namespace track_sleepers

384
world/SleeperPaths.cpp Normal file
View File

@@ -0,0 +1,384 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/SleeperPaths.h"
#include "world/SleeperGeometry.h"
#include "world/SleeperRules.h"
#include "world/TrackSleepers.h"
#include "world/Track.h"
#include "simulation/simulation.h"
#include "scene/scene.h"
namespace track_sleepers {
int path_count( TTrack const &Track ) {
return ( Track.eType == tt_Switch ? 2 : 1 );
}
TSegment const *path_segment( TTrack const &Track, int const Path ) {
return ( Track.eType == tt_Switch ? Track.SwitchExtension->Segments[ Path ].get() : Track.Segment.get() );
}
// how long the line of tracks joined end to end the plain track is part of, so a line keeps laying the timbers where it runs
// past the tracks of another, from one track of it to the next. the line goes on through the main path of a switch, and ends
// where it comes to its diverging path
namespace {
double line_length( TTrack const &Track ) {
if( Track.eType != tt_Normal || Track.Segment == nullptr ) {
return 0.0;
}
auto result { Track.Segment->GetLength() };
for( auto *next : { Track.trPrev, Track.trNext } ) {
TTrack const *previous { &Track };
auto const *current { next };
for( int count = 0; current != nullptr && current != &Track && count < 1000; ++count ) {
TTrack const *following { nullptr };
if( current->eType == tt_Normal && current->Segment != nullptr ) {
result += current->Segment->GetLength();
following = ( current->trPrev == previous ? current->trNext : current->trPrev );
}
else if( current->eType == tt_Switch && current->SwitchExtension != nullptr && current->SwitchExtension->Segments[ 0 ] ) {
auto const &links { *current->SwitchExtension };
if( links.pNexts[ 0 ] == previous ) {
following = links.pPrevs[ 0 ];
}
else if( links.pPrevs[ 0 ] == previous || links.pPrevs[ 1 ] == previous ) {
following = links.pNexts[ 0 ];
}
else {
// come to the diverging path
break;
}
result += links.Segments[ 0 ]->GetLength();
}
else {
break;
}
previous = current;
current = following;
}
}
return result;
}
} // namespace
sampled_path sample( TTrack &Track, int const Path ) {
sampled_path result;
result.track = &Track;
result.path = Path;
result.segment = path_segment( Track, Path );
if( result.segment == nullptr ) {
return result;
}
result.length = result.segment->GetLength();
result.gauge = std::abs( Track.fTrackWidth );
result.rails = 0.5 * result.gauge + rail_reach;
result.reach = result.rails + overhang;
result.half = 0.5 * plain_length( Track );
result.line = line_length( Track );
auto const count { std::max( 1, static_cast<int>( std::ceil( result.length / sample_step ) ) ) };
result.low = glm::dvec2 { std::numeric_limits<double>::max() };
result.high = glm::dvec2 { std::numeric_limits<double>::lowest() };
for( int step = 0; step <= count; ++step ) {
auto const distance { result.length * step / count };
glm::dvec3 point;
glm::vec3 angles;
result.segment->RaPositionGet( distance, point, angles );
if( std::abs( angles.x ) > 1e-6 || ( false == result.points.empty() && std::abs( point.y - result.points.front().y ) > 1e-4 ) ) {
result.level = false;
}
result.points.emplace_back( point );
result.distances.emplace_back( distance );
result.low = glm::min( result.low, plan( point ) );
result.high = glm::max( result.high, plan( point ) );
}
result.feet = result.points.front().y - Track.rail_height();
return result;
}
// the direction of the path in plan by its samples about the distance
glm::dvec2 heading( sampled_path const &Path, double const Distance ) {
auto const count { Path.points.size() };
auto index { static_cast<std::size_t>( std::upper_bound( Path.distances.begin(), Path.distances.end(), Distance ) - Path.distances.begin() ) };
index = std::clamp<std::size_t>( index, 1, count - 1 );
auto const direction { plan( Path.points[ index ] - Path.points[ index - 1 ] ) };
auto const length { glm::length( direction ) };
return ( length > 1e-9 ? direction / length : glm::dvec2 { 0.0, 1.0 } );
}
// which of two paths lays the beams where they're merged: a switch before a plain track, the main path of a switch
// before the diverging one, a plain track of a longer line before one of a shorter, the longer before the shorter, and
// then by the name
bool before( sampled_path const &Left, sampled_path const &Right ) {
auto const leftswitch { Left.track->eType == tt_Switch };
auto const rightswitch { Right.track->eType == tt_Switch };
if( leftswitch != rightswitch ) {
return leftswitch;
}
if( Left.track == Right.track ) {
return Left.path < Right.path;
}
if( Left.path != Right.path ) {
return Left.path < Right.path;
}
if( std::abs( Left.line - Right.line ) > 0.001 ) {
return Left.line > Right.line;
}
if( std::abs( Left.length - Right.length ) > 0.001 ) {
return Left.length > Right.length;
}
if( Left.track->name() != Right.track->name() ) {
return Left.track->name() < Right.track->name();
}
return Left.track < Right.track;
}
bool near_bounds( sampled_path const &Path, glm::dvec3 const &Point, double const Distance ) {
return Point.x > Path.low.x - Distance && Point.x < Path.high.x + Distance
&& Point.z > Path.low.y - Distance && Point.z < Path.high.y + Distance;
}
// how far the point is from the path in plan and which way the path runs there, when it's beside the path closer than the
// distance and at about the same level. a point past either end of the path isn't beside it, so a track going on from the end
// of another doesn't count as running beside it
bool beside( sampled_path const &Path, glm::dvec3 const &Point, double const Distance, double &Gap, glm::dvec2 &Direction ) {
if( Path.points.size() < 2 || false == near_bounds( Path, Point, Distance ) ) {
return false;
}
auto nearest { Distance * Distance };
auto found { false };
auto past { false }; // the nearest place of the path is its end, with the point beyond it
auto const last { Path.points.size() - 2 };
for( std::size_t index = 0; index <= last; ++index ) {
auto const &from { Path.points[ index ] };
auto const &to { Path.points[ index + 1 ] };
auto const chord { plan( to - from ) };
auto const offset { plan( Point - from ) };
auto const chordlength2 { glm::dot( chord, chord ) };
auto const along { chordlength2 > 0.0 ? glm::dot( offset, chord ) / chordlength2 : 0.0 };
auto const clamped { std::clamp( along, 0.0, 1.0 ) };
auto const gap { offset - chord * clamped };
auto const gap2 { glm::dot( gap, gap ) };
if( gap2 >= nearest ) {
continue;
}
auto const level { from.y + ( to.y - from.y ) * clamped };
if( std::abs( level - Point.y ) > level_tolerance ) {
continue;
}
nearest = gap2;
found = true;
past = ( ( index == 0 && along < 1e-6 ) || ( index == last && along > 1.0 - 1e-6 ) );
if( chordlength2 > 0.0 ) {
Direction = chord / std::sqrt( chordlength2 );
}
}
Gap = std::sqrt( nearest );
return found && false == past;
}
// the place of the path nearest to the point in plan: its distance along the path, where it is and which way the path runs
double nearest_on( sampled_path const &Path, glm::dvec2 const &Point, glm::dvec2 &Position, glm::dvec2 &Direction ) {
auto nearest { std::numeric_limits<double>::max() };
auto result { 0.0 };
for( std::size_t index = 1; index < Path.points.size(); ++index ) {
auto const from { plan( Path.points[ index - 1 ] ) };
auto const chord { plan( Path.points[ index ] ) - from };
auto const chordlength2 { glm::dot( chord, chord ) };
if( chordlength2 <= 0.0 ) {
continue;
}
auto const along { std::clamp( glm::dot( Point - from, chord ) / chordlength2, 0.0, 1.0 ) };
auto const position { from + chord * along };
auto const gap2 { glm::dot( Point - position, Point - position ) };
if( gap2 < nearest ) {
nearest = gap2;
result = glm::mix( Path.distances[ index - 1 ], Path.distances[ index ], along );
Position = position;
Direction = chord / std::sqrt( chordlength2 );
}
}
return result;
}
// where the feet of the rails of the path are at the distance along it: the middle between them, the unit vector across to the
// rail on the right, tilted with the cant, and the unit vector along the path, with its grade
void rail_frame( sampled_path const &Path, double const Distance, glm::dvec3 &Middle, glm::dvec3 &Across, glm::dvec3 &Along ) {
glm::vec3 angles;
Path.segment->RaPositionGet( Distance, Middle, angles );
glm::dvec3 behind, ahead;
glm::vec3 unused;
Path.segment->RaPositionGet( std::max( 0.0, Distance - 0.1 ), behind, unused );
Path.segment->RaPositionGet( std::min( Path.length, Distance + 0.1 ), ahead, unused );
Along = ahead - behind;
if( glm::length( Along ) < 1e-6 ) {
Along = { 0.0, 0.0, 1.0 };
}
Along = glm::normalize( Along );
auto flat { glm::dvec3 { -Along.z, 0.0, Along.x } };
flat = ( glm::length( flat ) > 1e-6 ? glm::normalize( flat ) : glm::dvec3 { 1.0, 0.0, 0.0 } );
auto const roll { static_cast<double>( angles.x ) };
Across = flat * std::cos( roll ) - glm::dvec3 { 0.0, 1.0, 0.0 } * std::sin( roll );
Middle.y -= Path.track->rail_height();
}
// how far the feet of the rails of the path at the point stand off the plane of the feet of the rails of the other path where
// it's nearest the point: a timber under both lies flat under all of them only if they're in one plane
double off_plane( sampled_path const &Path, glm::dvec3 const &Point, sampled_path const &Other ) {
if( Path.level && Other.level ) {
return std::abs( Path.feet - Other.feet );
}
glm::dvec2 position, direction;
auto const at { nearest_on( Path, plan( Point ), position, direction ) };
auto const otherat { nearest_on( Other, plan( Point ), position, direction ) };
glm::dvec3 middle, across, along, othermiddle, otheracross, otheralong;
rail_frame( Path, at, middle, across, along );
rail_frame( Other, otherat, othermiddle, otheracross, otheralong );
auto const normal { glm::normalize( glm::cross( otheracross, otheralong ) ) };
auto result { 0.0 };
for( auto const side : { -0.5, 0.5 } ) {
result = std::max( result, std::abs( glm::dot( middle + across * ( side * Path.gauge ) - othermiddle, normal ) ) );
}
return result;
}
// how far apart the middles of two paths running at the angle are where their plain sleepers would just touch: the one square to
// the other reaches out by half its length, the other by its half length foreshortened and half its width, with the gap
// between their ends. closer, the paths share the timbers
double touch_spacing( sampled_path const &Path, sampled_path const &Other, double const Angle ) {
return Other.half + Path.half * std::cos( Angle ) + half_width * std::sin( Angle ) + end_gap;
}
// how close two paths run where their sleepers can't be laid apart, so they share the timbers whether they're in one plane or not
double shared_spacing( sampled_path const &Path, sampled_path const &Other ) {
return Path.rails + Other.rails + 2.0 * overhang_min + end_gap;
}
// whether the path at the point, running the given way, is carried by the beams of a path before it, which it runs beside so
// close that their plain sleepers would touch. where the paths cross so wide that one timber can't carry both, the sleepers
// of the path are only left out where they'd meet the timbers of the other. the paths share timbers only while their rails lie
// in one plane, or run so close they can't be parted: where one rises, falls or cants apart, each lays its own
bool shares_with( sampled_path const &Path, glm::dvec3 const &Point, glm::dvec2 const &Direction, sampled_path const &Other ) {
double gap;
glm::dvec2 direction { Direction };
if( false == beside( Other, Point, Path.half + Other.half + half_width + end_gap, gap, direction ) ) {
return false;
}
auto const angle { angle_between( Direction, direction ) };
if( angle >= grid_angle ) {
return gap < Other.half + half_width + end_gap;
}
if( gap >= touch_spacing( Path, Other, angle ) ) {
return false;
}
return gap < shared_spacing( Path, Other ) || off_plane( Path, Point, Other ) <= coplanar_tolerance;
}
// the paths of the track, and of the tracks with beams around it; or of all those with the ballast the simulator makes, whether
// they get beams or not
std::vector<sampled_path> paths_around( TTrack &Track, bool const Bedded ) {
std::vector<sampled_path> result;
for( int path = 0; path < path_count( Track ); ++path ) {
result.emplace_back( sample( Track, path ) );
}
std::vector<TTrack *> others;
tracks_near( Track, [&]( TTrack *Other ) {
if( Other == &Track || Other->m_editorremoved ) {
return;
}
if( std::find( others.begin(), others.end(), Other ) != others.end() || false == ( Bedded ? bedded( *Other ) : lays( *Other ) ) ) {
return;
}
others.push_back( Other ); } );
for( auto *other : others ) {
for( int path = 0; path < path_count( *other ); ++path ) {
result.emplace_back( sample( *other, path ) );
}
}
result.erase(
std::remove_if( result.begin(), result.end(), []( sampled_path const &Path ) { return Path.points.size() < 2; } ),
result.end() );
return result;
}
void tracks_near( TTrack &Track, std::function<void( TTrack * )> const &Visit ) {
auto const sections { simulation::Region->sections( Track.location(), static_cast<float>( Track.radius() + 50.0 ) ) };
for( auto *section : sections ) {
for( auto const &cell : section->m_cells ) {
if( glm::length( cell.area().center - Track.location() ) > cell.area().radius + Track.radius() + around_margin ) {
continue;
}
for( auto *other : cell.m_directories.paths ) {
if( other == nullptr || glm::length( other->location() - Track.location() ) > other->radius() + Track.radius() + around_margin ) {
continue;
}
Visit( other );
}
}
}
}
std::vector<glm::dvec2> stretches_where( sampled_path const &Path, std::function<bool( glm::dvec3 const &, glm::dvec2 const & )> const &Test ) {
std::vector<glm::dvec2> result;
auto const holds = [&]( double const Distance ) {
auto const distance { std::clamp( Distance, std::min( 0.01, 0.5 * Path.length ), std::max( Path.length - 0.01, 0.5 * Path.length ) ) };
glm::dvec3 point;
glm::vec3 angles;
Path.segment->RaPositionGet( distance, point, angles );
return Test( point, heading( Path, distance ) ); };
auto const count { std::max( 1, static_cast<int>( std::ceil( Path.length / ( 0.5 * sample_step ) ) ) ) };
auto was { holds( 0.0 ) };
auto start { 0.0 };
auto previous { 0.0 };
for( int step = 1; step <= count; ++step ) {
auto const distance { Path.length * step / count };
auto const is { holds( distance ) };
if( is != was ) {
auto low { previous };
auto high { distance };
for( int narrow = 0; narrow < 8; ++narrow ) {
auto const middle { 0.5 * ( low + high ) };
( holds( middle ) == was ? low : high ) = middle;
}
auto const boundary { 0.5 * ( low + high ) };
if( was ) {
result.emplace_back( start, boundary );
}
start = boundary;
was = is;
}
previous = distance;
}
if( was ) {
result.emplace_back( start, Path.length );
}
return result;
}
} // namespace track_sleepers

85
world/SleeperPaths.h Normal file
View File

@@ -0,0 +1,85 @@
/*
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 <functional>
#include <vector>
#include <glm/glm.hpp>
class TTrack;
class TSegment;
// the paths of the tracks as the sleepers are laid out along them: sampled in plan, with how close they run to each other and
// whether the beams of one carry the rails of another
namespace track_sleepers {
// a path of a track by samples taken along it
struct sampled_path {
TTrack *track { nullptr };
int path { 0 };
TSegment const *segment { nullptr };
double length { 0.0 };
double gauge { 1.435 };
double rails { 0.829 }; // how far out from the middle of the path its rails reach
double reach { 1.3 }; // half the length of the sleepers of the path alone
double half { 1.3 }; // half the length of the plain sleepers of the track, of the model it lays them of
double line { 0.0 }; // the length of the line of plain tracks joined end to end the path is part of
bool level { true }; // the path runs level and without cant, its rails all at the one height
double feet { 0.0 }; // the height of the feet of its rails where it's level
std::vector<glm::dvec3> points;
std::vector<double> distances;
glm::dvec2 low { 0.0 }; // the bounds of the samples in plan
glm::dvec2 high { 0.0 };
};
int path_count( TTrack const &Track );
TSegment const *path_segment( TTrack const &Track, int const Path );
sampled_path sample( TTrack &Track, int const Path );
// the direction of the path in plan by its samples about the distance
glm::dvec2 heading( sampled_path const &Path, double const Distance );
// which of two paths lays the beams where they're merged: a switch before a plain track, the main path of a switch
// before the diverging one, a plain track of a longer line before one of a shorter, the longer before the shorter, and
// then by the name
bool before( sampled_path const &Left, sampled_path const &Right );
bool near_bounds( sampled_path const &Path, glm::dvec3 const &Point, double const Distance );
// how far the point is from the path in plan and which way the path runs there, when it's beside the path closer than the
// distance and at about the same level. a point past either end of the path isn't beside it, so a track going on from the end
// of another doesn't count as running beside it
bool beside( sampled_path const &Path, glm::dvec3 const &Point, double const Distance, double &Gap, glm::dvec2 &Direction );
// the place of the path nearest to the point in plan: its distance along the path, where it is and which way the path runs
double nearest_on( sampled_path const &Path, glm::dvec2 const &Point, glm::dvec2 &Position, glm::dvec2 &Direction );
// where the feet of the rails of the path are at the distance along it: the middle between them, the unit vector across to the
// rail on the right, tilted with the cant, and the unit vector along the path, with its grade
void rail_frame( sampled_path const &Path, double const Distance, glm::dvec3 &Middle, glm::dvec3 &Across, glm::dvec3 &Along );
// how far the feet of the rails of the path at the point stand off the plane of the feet of the rails of the other path where
// it's nearest the point: a timber under both lies flat under all of them only if they're in one plane
double off_plane( sampled_path const &Path, glm::dvec3 const &Point, sampled_path const &Other );
// how far apart the middles of two paths running at the angle are where their plain sleepers would just touch: the one square to
// the other reaches out by half its length, the other by its half length foreshortened and half its width, with the gap
// between their ends. closer, the paths share the timbers
double touch_spacing( sampled_path const &Path, sampled_path const &Other, double const Angle );
// how close two paths run where their sleepers can't be laid apart, so they share the timbers whether they're in one plane or not
double shared_spacing( sampled_path const &Path, sampled_path const &Other );
// whether the path at the point, running the given way, is carried by the beams of a path before it, which it runs beside so
// close that their plain sleepers would touch. where the paths cross so wide that one timber can't carry both, the sleepers
// of the path are only left out where they'd meet the timbers of the other. the paths share timbers only while their rails lie
// in one plane, or run so close they can't be parted: where one rises, falls or cants apart, each lays its own
bool shares_with( sampled_path const &Path, glm::dvec3 const &Point, glm::dvec2 const &Direction, sampled_path const &Other );
// the paths of the track, and of the tracks with beams around it; or of all those with the ballast the simulator makes, whether
// they get beams or not
std::vector<sampled_path> paths_around( TTrack &Track, bool const Bedded = false );
// the tracks of the region near enough the track to take part in laying its beams, itself among them, some more than once
void tracks_near( TTrack &Track, std::function<void( TTrack * )> const &Visit );
// the stretches of the path where the test holds of the place at a distance along it and the way the path runs there, tested at
// half the step of the samples, with the ends narrowed down between
std::vector<glm::dvec2> stretches_where( sampled_path const &Path, std::function<bool( glm::dvec3 const &, glm::dvec2 const & )> const &Test );
} // namespace track_sleepers

75
world/SleeperRules.h Normal file
View File

@@ -0,0 +1,75 @@
/*
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 <cstddef>
// the rules the sleepers and timbers are laid by, and the record of where the layout had to break them
namespace track_sleepers {
// how far apart the beams are along the track, and how far closer or further they may go to put one where a pin is
double constexpr pitch { 0.6 };
double constexpr pitch_min { 0.83 * pitch };
double constexpr pitch_max { 1.08 * pitch };
// how far out from the middle of a track its rail reaches past half the gauge: the foot of the rail outside the gauge line
double constexpr rail_reach { 0.1115 };
// how far a beam reaches out past the foot of the rail, 2.6 m sleepers under the standard gauge, and how far at least
double constexpr overhang { 0.471 };
double constexpr overhang_min { 0.25 };
// how far the feet of the rails a timber carries may stand off one plane
double constexpr coplanar_tolerance { 0.01 };
// the gap left between the ends of two beams on one line
double constexpr end_gap { 0.05 };
// half the width of a beam, to keep the sleepers of a track crossing another at a wide angle clear of the timbers of the other
double constexpr half_width { 0.13 };
// a timber longer than this is a fault of the layout
double constexpr length_cap { 8.5 }; // the longest prestressed concrete bearer of EN 13230-4, under both tracks of a scissors crossing about 5 m apart
// the rails of a path crossing the line of a beam at a lower angle than this aren't laid on it
double constexpr crossing_sine_min { 0.2588 }; // 15 degrees
// two paths crossing at this angle or wider keep the beams square to each, laid as a grid
double constexpr grid_angle { 1.2915 }; // 74 degrees
// the timbers turn toward the bisector of a frog over as many beams before it
double constexpr frog_ramp { 6.0 * pitch };
// past where the tracks go apart the sleepers lie square to them at once, unless the last timber turns so far from them that
// the ends of the next sleepers would meet it: then they turn back by at most so much a beam
double constexpr turn_max { 0.2415 }; // 13.8 degrees: a sleeper 1.3 m out from the track keeps clear of one turned so far a pitch away
// how far apart the samples of a path are, to tell how far the paths around run from it
double constexpr sample_step { 0.5 };
// two paths over each other, on a bridge, aren't merged
double constexpr level_tolerance { 1.0 };
// how far around a track the tracks are taken in to lay its beams: those whose beams it carries, or which carry its own, and
// those which set where the beams of these go
double constexpr around_margin { 20.0 };
// how many beams make a run drawn or left out together
std::size_t constexpr chunk_beams { 16 };
// how many tracks a cluster takes at most
std::size_t constexpr cluster_cap { 256 };
// how far the lines of the timbers turn from one to the next at most, so the far ends of the long ones keep their spacing, and how
// far the plain sleepers turn back square to the track from one to the next
double constexpr timber_turn { 0.0175 }; // a degree
double constexpr sleeper_turn { 0.06 }; // three and a half degrees
// what was wrong with the beams laid since the counts were taken last
struct fault_counts {
std::size_t offplane { 0 }; // timbers shared by rails out of one plane, where the tracks run too close to part them
std::size_t clipped { 0 }; // beam ends cut shorter than the least overhang, to keep clear of the beams of other tracks
std::size_t dropped { 0 }; // beams left out where they'd lie over one laid before
double slowest { 0.0 }; // the longest a cluster took to lay, in ms
std::size_t biggest { 0 }; // the most tracks in a cluster
};
inline fault_counts &faults() {
static fault_counts counts;
return counts;
}
} // namespace track_sleepers

230
world/SleeperWorker.cpp Normal file
View File

@@ -0,0 +1,230 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/SleeperWorker.h"
#include "world/SleeperLayout.h"
#include "world/SleeperRules.h"
#include "world/Track.h"
#include "simulation/simulation.h"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <thread>
namespace track_sleepers {
namespace {
// the version of the layout of the beams, moved on whenever a track changes, so the beams worked out for a cluster hold till then
std::atomic<std::uint64_t> &layout_epoch() {
static std::atomic<std::uint64_t> epoch { 1 };
return epoch;
}
std::map<TTrack const *, std::shared_ptr<cluster_beams>> &known_clusters() {
static std::map<TTrack const *, std::shared_ptr<cluster_beams>> clusters;
return clusters;
}
std::mutex &clusters_mutex() {
static std::mutex mutex;
return mutex;
}
// the beams of the cluster of the track worked out already for the current layout, null where they aren't
std::shared_ptr<cluster_beams> cached_cluster( TTrack const &Track ) {
std::lock_guard<std::mutex> lock { clusters_mutex() };
auto const found { known_clusters().find( &Track ) };
return ( found != known_clusters().end() && found->second->epoch == layout_epoch() ? found->second : nullptr );
}
// the beams of the cluster of the track worked out, and kept for each track of it unless the tracks changed meanwhile
std::shared_ptr<cluster_beams> work_out( TTrack &Track ) {
auto const start { std::chrono::steady_clock::now() };
auto const epoch { layout_epoch().load() };
auto const tracks { cluster_of( Track ) };
auto const laid { lay_out( tracks, epoch ) };
faults().slowest = std::max( faults().slowest, std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - start ).count() );
faults().biggest = std::max( faults().biggest, tracks.size() );
std::lock_guard<std::mutex> lock { clusters_mutex() };
if( laid->epoch == layout_epoch() ) {
for( auto *track : tracks ) {
known_clusters()[ track ] = laid;
}
}
return laid;
}
std::vector<beam> beams_of( cluster_beams const &Cluster, TTrack const &Track ) {
auto const beams { Cluster.beams.find( &Track ) };
return ( beams != Cluster.beams.end() ? beams->second : std::vector<beam> {} );
}
// the clusters of the tracks coming near worked out in a thread of their own, one after another
class background_worker {
public:
~background_worker() {
{
std::lock_guard<std::mutex> lock { mutex };
stop = true;
queue.clear();
}
wake.notify_all();
if( thread.joinable() ) {
thread.join();
}
}
bool enabled { true };
void request( TTrack &Track ) {
if( false == resolved ) {
// the models the sleepers are made of loaded here, the thread can't load them
resolved = true;
for( auto *track : simulation::Paths.sequence() ) {
if( track != nullptr && bedded( *track ) ) {
plain_model( *track );
timber_model( *track );
}
}
}
{
std::lock_guard<std::mutex> lock { mutex };
if( queued.count( &Track ) ) {
return;
}
queued.insert( &Track );
queue.push_back( &Track );
if( false == thread.joinable() ) {
thread = std::thread( [ this ]() { run(); } );
}
}
wake.notify_one();
}
// nothing left to work out nor being worked out, the waiting ones dropped
void settle() {
std::unique_lock<std::mutex> lock { mutex };
queue.clear();
queued.clear();
idle.wait( lock, [ this ]() { return false == busy; } );
}
private:
void run() {
while( true ) {
TTrack *track { nullptr };
{
std::unique_lock<std::mutex> lock { mutex };
wake.wait( lock, [ this ]() { return stop || false == queue.empty(); } );
if( stop ) {
return;
}
track = queue.front();
queue.pop_front();
busy = true;
}
if( cached_cluster( *track ) == nullptr ) {
work_out( *track );
}
{
std::lock_guard<std::mutex> lock { mutex };
queued.erase( track );
busy = false;
}
idle.notify_all();
}
}
std::mutex mutex;
std::condition_variable wake;
std::condition_variable idle;
std::deque<TTrack *> queue;
std::set<TTrack const *> queued;
std::thread thread;
bool stop { false };
bool busy { false };
bool resolved { false };
};
background_worker &worker() {
static background_worker worker;
return worker;
}
} // namespace
std::vector<beam> place( TTrack &Track ) {
auto cluster { cached_cluster( Track ) };
if( cluster == nullptr ) {
cluster = work_out( Track );
}
return beams_of( *cluster, Track );
}
void lay_in_background( bool const Background ) {
if( false == Background ) {
worker().settle();
}
worker().enabled = Background;
}
void prefetch( TTrack &Track ) {
if( worker().enabled && cached_cluster( Track ) == nullptr ) {
worker().request( Track );
}
}
void outdate_layouts() {
++layout_epoch();
}
std::optional<std::vector<beam>> laid_out( TTrack const &Track ) {
if( auto const cluster { cached_cluster( Track ) }; cluster != nullptr ) {
return beams_of( *cluster, Track );
}
return std::nullopt;
}
bool in_background() {
return worker().enabled;
}
void ask_layout( TTrack &Track ) {
worker().request( Track );
}
void settle_background() {
worker().settle();
}
} // namespace track_sleepers

31
world/SleeperWorker.h Normal file
View File

@@ -0,0 +1,31 @@
/*
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 <optional>
#include <vector>
#include "world/TrackSleepers.h"
// the layouts of the clusters of tracks kept till a track changes, and worked out in a thread of their own while the simulation runs
namespace track_sleepers {
// the layouts worked out so far hold no more, as a track changed
void outdate_layouts();
// the beams of the track from the layout of its cluster worked out already for the tracks as they are, none where there's none yet
std::optional<std::vector<beam>> laid_out( TTrack const &Track );
// whether the layouts are worked out in the background
bool in_background();
// the layout of the cluster of the track asked of the thread working them out
void ask_layout( TTrack &Track );
// nothing left to work out in the background nor being worked out, the waiting ones dropped
void settle_background();
} // namespace track_sleepers

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"
@@ -45,6 +47,40 @@ const int iProsto3[4] = {1, -1, 2, 1}; // segmenty do jazdy prosto
const int iEnds3[13] = {3, 0, 2, 1, 2, 0, -1, 1, 0, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1"
TIsolated *TIsolated::pRoot = nullptr;
namespace {
// a part of a switch path, shaped like the path itself: heights on the rail heads, straights made of their own points
void init_switch_piece( TSegment &Segment, segment_data const &Piece, bool const Rail ) {
auto p1 { Piece.points[ segment_data::point::start ] };
auto cp1 { Piece.points[ segment_data::point::control1 ] };
auto cp2 { Piece.points[ segment_data::point::control2 ] };
auto p2 { Piece.points[ segment_data::point::end ] };
if( Rail ) {
p1.y += 0.18;
p2.y += 0.18;
}
if( glm::length2( ( p1 + p1 + p2 ) / 3.0 - p1 - cp1 ) < sq( 0.02 )
|| glm::length2( ( p1 + p2 + p2 ) / 3.0 - p2 - cp2 ) < sq( 0.02 ) ) {
cp1 = cp2 = glm::dvec3( 0, 0, 0 );
}
auto const straight { cp1 == glm::dvec3() && cp2 == glm::dvec3() };
auto const segsize { (
Piece.radius != 0.f ?
std::clamp( std::abs( Piece.radius ) * ( 0.02 / Global.SplineFidelity ), 2.0 / Global.SplineFidelity, 10.0 / Global.SplineFidelity ) :
straight ?
10.0 :
std::clamp( glm::length( p1 - p2 ) * 0.1, 2.0 / Global.SplineFidelity, 10.0 / Global.SplineFidelity ) ) };
if( straight ) {
Segment.Init( p1, p2, segsize, Piece.rolls[ 0 ], Piece.rolls[ 1 ] );
}
else {
Segment.Init( p1, p1 + cp1, p2 + cp2, p2, segsize, Piece.rolls[ 0 ], Piece.rolls[ 1 ] );
}
}
} // namespace
TTrack::profiles_array TTrack::m_profiles;
TTrack::profiles_map TTrack::m_profilesmap;
@@ -169,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
}
@@ -518,8 +558,6 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
}
}
init_segments( true );
// optional attributes
parser->getTokens();
*parser >> token;
@@ -633,6 +671,36 @@ 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 == "movablenose" ) {
// the frog of the switch with a movable nose
if( eType == tt_Switch ) {
SwitchExtension->m_movablenose = true;
}
}
else if( str == "blades" ) {
// blades <heel> <throw>: where the moving blades of the switch end and how far their tips move over, 0: from the shape of the switch
auto const heel { parser->getToken<double>() };
auto const bladethrow { parser->getToken<double>() };
if( eType == tt_Switch ) {
SwitchExtension->m_bladeheel = std::max( 0.0, heel );
SwitchExtension->m_bladethrow = std::max( 0.0, bladethrow );
}
}
else if( str == "timbermodel" ) {
// timbermodel <model> <skin>: the model of the timbers under the switch or the crossing the track is part of, put together
// from its submodels begin, main and end, or drawn whole; skin none: the textures of the model
auto modelpath { parser->getToken<std::string>( false ) };
auto skinpath { parser->getToken<std::string>( false ) };
replace_slashes( modelpath );
m_timber_model_name = modelpath;
m_timber_skin_name = ( skinpath == "none" ? std::string{} : skinpath );
}
else if( str == "friction" ) {
// memory cell holding friction value modifiers
m_friction.first = parser->getToken<std::string>();
@@ -668,12 +736,34 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
// called after segment initialisation so the path geometry is final.
}
}
else if( str == "pieces1" || str == "pieces2" ) {
// consecutive parts of a path, kept after the path itself for versions which know only the path
auto const index { static_cast<std::size_t>( str.back() - '1' ) };
auto const count { parser->getToken<int>() };
std::vector<segment_data> pieces;
for( int piece = 0; piece < count; ++piece ) {
pieces.emplace_back();
pieces.back().deserialize( *parser, pOrigin );
}
if( index < m_paths.size() ) {
m_paths[ index ].pieces = std::move( pieces );
}
else {
ErrorLog( "Bad track: \"" + str + "\" defined for track \"" + m_name + "\" which has no such path" );
}
}
else
ErrorLog("Bad track: unknown property: \"" + str + "\" defined for track \"" + m_name + "\"");
parser->getTokens();
*parser >> token;
str = token;
}
auto const velocity { fVelocity };
init_segments( true );
if( SwitchExtension ) {
fVelocity = velocity;
}
// alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru
if ((i = m_name.find("@")) != std::string::npos && i < m_name.length())
{
@@ -958,6 +1048,35 @@ void TTrack::init_segments( bool const Initial ) {
}
}
if( eType == tt_Switch ) {
for( std::size_t index = 0; index < 2; ++index ) {
auto const &pieces { m_paths[ index ].pieces };
if( pieces.empty() ) {
continue;
}
auto joined {
glm::distance( pieces.front().points[ segment_data::point::start ], m_paths[ index ].points[ segment_data::point::start ] ) < 0.02
&& glm::distance( pieces.back().points[ segment_data::point::end ], m_paths[ index ].points[ segment_data::point::end ] ) < 0.02 };
for( std::size_t piece = 1; piece < pieces.size(); ++piece ) {
joined = joined && glm::distance( pieces[ piece - 1 ].points[ segment_data::point::end ], pieces[ piece ].points[ segment_data::point::start ] ) < 0.02;
}
if( false == joined ) {
ErrorLog( "Bad track: pieces of path " + std::to_string( index + 1 ) + " of switch \"" + m_name + "\" don't join its ends, the path is used instead" );
continue;
}
auto const texheight { fTexHeight1 };
auto const texheightoffset { fTexHeightOffset };
std::vector<std::shared_ptr<TSegment>> chain;
for( auto const &piece : pieces ) {
chain.emplace_back( std::make_shared<TSegment>( this ) );
init_switch_piece( *chain.back(), piece, ( iCategoryFlag & 1 ) != 0 );
}
fTexHeight1 = texheight;
fTexHeightOffset = texheightoffset;
SwitchExtension->Segments[ index ]->InitChain( std::move( chain ) );
}
}
if( Initial ) {
Switch(0);
} // na stałe w położeniu 0 - nie ma początkowego stanu zwrotnicy we wpisie
@@ -1351,20 +1470,76 @@ 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{};
}
m_sleeper_local_transforms.clear();
// the sleepers of this track and of the tracks around depend on where it runs
m_sleepers3d.dirty = true;
track_sleepers::unsettle_around( *this );
if( Bank == null_handle ) { return; }
create_geometry( Bank );
}
void TTrack::share_joints() {
for( int path = 0; path < track_rails::path_count( *this ); ++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 { track_rails::path_neighbour( *this, path, end ) };
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 < track_rails::path_count( *neighbour ); ++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;
// the rails of a frog with a movable nose are laid again as it moves over, in the same chunks
auto const movable { eType == tt_Switch && SwitchExtension && SwitchExtension->m_movablenose };
track_rails::reused_chunks const reuse { movable ? &SwitchExtension->m_nosechunks : nullptr };
if( eType == tt_Switch && SwitchExtension ) {
SwitchExtension->m_nosethrow = ( Bank != null_handle ? nose_throw() : -1.0 );
}
share_joints();
m_sleepers3d.active = track_sleepers::lays( *this );
// 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,11 +1552,21 @@ 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
case tt_Normal:
if (m_material2)
if (m_material2 && ( Global.Sleepers3DBed || false == m_sleepers3d.active ) )
{ // podsypka z podkładami jest tylko dla zwykłego toru
gfx::vertex_array bpts1;
create_track_bed_profile( bpts1, trPrev, trNext );
@@ -1397,7 +1582,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
gfx::vertex_array vertices;
Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, texturelength);
if( Bank != 0 && true == Geometry2.empty() ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
Geometry2.emplace_back(track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
}
if( Bank == 0 && false == Geometry2.empty() ) {
// special variant, replace existing data for a turntable track
@@ -1409,12 +1594,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, track_rails::blade_runon( *this, side ), 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(track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
}
}
if( Bank == 0 && false == Geometry1.empty() ) {
// special variant, replace existing data for a turntable track
@@ -1433,107 +1626,75 @@ 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 dense { std::max( blade( 0 ).valid ? blade( 0 ).heel : 0.0, blade( 1 ).valid ? blade( 1 ).heel : 0.0 ) };
SwitchExtension->Segments[ 0 ]->set_dense( dense );
SwitchExtension->Segments[ 1 ]->set_dense( dense );
// 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();
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;
if( auto const &shape { blade( Path ) }; shape.valid ) {
SwitchExtension->Segments[ Path ]->RenderLoftSpan( vertices, m_origin, Profile, { .side = Side, .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = shape.heel, .to = SwitchExtension->Segments[ Path ]->GetLength() } );
}
// 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();
else {
SwitchExtension->Segments[ Path ]->RenderLoft( vertices, m_origin, Profile, false, Texturelength, 1.0, track_rails::blade_steps( *this, Path ) );
}
Geometry.emplace_back( track_rails::insert_chunk( 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( track_rails::insert_chunk( 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( track_rails::insert_chunk( 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( track_rails::insert_chunk( 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
if( true == Global.CreateSwitchTrackbeds ) {
gfx::vertex_array vertices;
create_switch_trackbed( vertices );
SwitchExtension->Geometry3 = GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP);
SwitchExtension->Geometry3 = track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP);
vertices.clear();
}
@@ -1914,6 +2075,27 @@ bool TTrack::Switch(int i, float const t, float const d)
return false;
};
int TTrack::nose_set() const {
return ( SwitchExtension->fOffset > 0.5f * fMaxOffset ? 1 : 0 );
}
double TTrack::nose_throw() const {
return ( fMaxOffset > 0.f ? std::clamp( static_cast<double>( SwitchExtension->fOffset / fMaxOffset ), 0.0, 1.0 ) : nose_set() );
}
double TTrack::trailing_point( int const Path ) const {
if( SwitchExtension->m_movablenose ) {
// the nose set the other way stands in the way of the wheels before they get to the blades
if( auto const frog { this->frog() }; frog.valid ) {
return frog.at[ Path ];
}
}
return track_rails::blade_length( *this, Path );
}
bool TTrack::SwitchForced(int i, TDynamicObject *o)
{ // rozprucie rozjazdu
if (SwitchExtension)
@@ -1933,6 +2115,10 @@ bool TTrack::SwitchForced(int i, TDynamicObject *o)
break;
}
Switch(i); // jeśli się tu nie przełączy, to każdy pojazd powtórzy event rozrprucia
if( SwitchExtension->m_movablenose && o != nullptr ) {
// the wheels can't push the nose of the frog over, the vehicle runs off the rails at it
o->MoverParameters->Derail( TRAILED_SWITCH );
}
}
}
else if (eType == tt_Cross)
@@ -2018,6 +2204,42 @@ 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 opening { Path == 0 ? SwitchExtension->fOffset2 : fMaxOffset - SwitchExtension->fOffset1 };
auto const offset { blade_side( Path ) * opening };
if( auto const &shape { blade( Path ) }; shape.valid ) {
auto const thrown { blades_throw() * std::clamp( static_cast<double>( opening ) / ( fMaxOffset + SwitchExtension->fOffsetDelay ), 0.0, 1.0 ) };
std::vector<glm::dvec2> offsets;
for( auto const &bend : shape.bends ) {
offsets.emplace_back( bend.x, -thrown * bend.y );
}
auto const size { rail_dimensions() };
auto const length { SwitchExtension->Segments[ Path ]->GetLength() };
SwitchExtension->Segments[ Path ]->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = shape.tip, .to = std::min( shape.heel, length ), .offsets = offsets, .extents = shape.extents, .walls = true, .drops = shape.drops, .head = { size.neck, size.crest } } );
// the blade running on past the end of the path goes on along the plain track joined there, whose rail starts behind the heel
if( auto const *next { track_rails::runon_track( *this, Path ) }; next != nullptr && shape.heel > length + 1e-3 ) {
auto const along = [&]( std::vector<glm::dvec2> const &Knots ) {
std::vector<glm::dvec2> result;
for( auto const &knot : Knots ) {
result.emplace_back( knot.x - length, knot.y );
}
return result; };
next->Segment->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = 0.0, .to = shape.heel - length, .offsets = along( offsets ), .extents = along( shape.extents ), .walls = true, .drops = along( shape.drops ), .head = { size.neck, size.crest } } );
}
return;
}
auto const bladelength { track_rails::blade_steps( *this, Path ) };
// 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
@@ -2052,6 +2274,22 @@ TTrack * TTrack::RaAnimate()
m = false; // koniec animacji
}
}
auto const nosethrow { SwitchExtension->m_movablenose ? nose_throw() : 0.0 };
if( SwitchExtension->m_movablenose
&& SwitchExtension->m_nosethrow >= 0.0
&& ( std::abs( nosethrow - SwitchExtension->m_nosethrow ) >= 0.05
|| ( false == m && nosethrow != SwitchExtension->m_nosethrow ) )
&& false == Geometry1.empty() ) {
// the nose of the frog goes over to the wing rail of the other track along with the blades, laid again in steps
gfx::geometrybank_handle const bank { Geometry1.front().bank, 0 };
Geometry1.clear();
Geometry2.clear();
GeometryCut1.clear();
GeometryCut2.clear();
GeometryTreads.clear();
SwitchExtension->Geometry3 = gfx::geometry_handle{};
create_geometry( bank );
}
// skip the geometry update if no geometry for this track was generated yet
if( ( m_material1 != 0
|| m_material2 != 0 )
@@ -2063,48 +2301,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 );
}
}
}
@@ -2314,9 +2528,9 @@ TTrack::export_as_text_( std::ostream &Output ) const {
// type
Output << (
eType == tt_Normal ? (
iCategoryFlag == 1 ? "normal" :
iCategoryFlag == 2 ? "road" :
iCategoryFlag == 4 ? "river" :
( iCategoryFlag & 15 ) == 1 ? "normal" :
( iCategoryFlag & 15 ) == 2 ? "road" :
( iCategoryFlag & 15 ) == 4 ? "river" :
"none" ) :
eType == tt_Switch ? "switch" :
eType == tt_Cross ? "cross" :
@@ -2432,6 +2646,29 @@ TTrack::export_as_text_( std::ostream &Output ) const {
if( fVerticalRadius != 0.f ) {
Output << "vradius " << fVerticalRadius << ' ';
}
for( std::size_t index = 0; index < m_paths.size(); ++index ) {
auto const &pieces { m_paths[ index ].pieces };
if( pieces.empty() ) {
continue;
}
auto const piecesprecision { Output.precision( std::numeric_limits<double>::digits10 ) };
Output << "pieces" << index + 1 << ' ' << pieces.size() << ' ';
for( auto const &piece : pieces ) {
for( auto const point : { segment_data::point::start, segment_data::point::control1, segment_data::point::control2, segment_data::point::end } ) {
Output
<< piece.points[ point ].x << ' '
<< piece.points[ point ].y << ' '
<< piece.points[ point ].z << ' ';
if( point == segment_data::point::start ) {
Output << piece.rolls[ 0 ] << ' ';
}
}
Output
<< piece.rolls[ 1 ] << ' '
<< piece.radius << ' ';
}
Output.precision( piecesprecision );
}
if( eType == tt_Switch
&& SwitchExtension->m_material3 != null_handle) {
auto texturefile { GfxRenderer->Material( SwitchExtension->m_material3 )->GetName() };
@@ -2441,6 +2678,18 @@ TTrack::export_as_text_( std::ostream &Output ) const {
}
Output << "trackbed " << texturefile << ' ';
}
if( eType == tt_Switch
&& SwitchExtension->m_frogs ) {
Output << "frogs ";
}
if( eType == tt_Switch
&& SwitchExtension->m_movablenose ) {
Output << "movablenose ";
}
if( eType == tt_Switch
&& ( SwitchExtension->m_bladeheel > 0.0 || SwitchExtension->m_bladethrow > 0.0 ) ) {
Output << "blades " << SwitchExtension->m_bladeheel << ' ' << SwitchExtension->m_bladethrow << ' ';
}
if( iCategoryFlag == 1
&& false == m_profile1.first.empty() ) {
Output << "railprofile " << m_profile1.first << ' ';
@@ -2459,6 +2708,12 @@ TTrack::export_as_text_( std::ostream &Output ) const {
<< m_sleeper_offset.z << ' '
<< m_sleeper_ballast_z << ' ';
}
if( false == m_timber_model_name.empty() ) {
Output
<< "timbermodel "
<< m_timber_model_name << ' '
<< ( m_timber_skin_name.empty() ? std::string{ "none" } : m_timber_skin_name ) << ' ';
}
// footer
Output
<< "endtrack"
@@ -2480,6 +2735,12 @@ std::string TTrack::tooltip() const
// locates specified profile in the profile database, potentially loading it from a file
// returns: pair <profile name, profile handle>
void
TTrack::rail_profile( std::string const &Name ) {
m_profile1 = ( Name.empty() ? std::pair<std::string, int>{} : fetch_track_rail_profile( Name ) );
}
std::pair<std::string, int>
TTrack::fetch_track_rail_profile( std::string const &Profile ) {
@@ -2579,6 +2840,15 @@ TTrack::track_rail_profile( int const Profile ) {
return m_profiles[ Profile ];
}
track_rails::rail_size
TTrack::rail_dimensions() const {
if( m_profiles.empty() ) {
return {};
}
return track_rails::measure_rail( track_rail_profile( m_profile1.second ) );
}
float
TTrack::texture_length( material_handle const Material ) {
@@ -2589,6 +2859,13 @@ TTrack::texture_length( material_handle const Material ) {
return texturelength < 0.f ? fTexLength : texturelength;
}
float
TTrack::rail_height() const {
// the same height the ballast is laid at, under the rail profile
return std::abs( track_rail_profile( m_profile1.second ).front().position.y );
}
void TTrack::MovedUp1(float const dh)
{ // poprawienie przechyłki wymaga wydłużenia podsypki
fTexHeight1 += dh;
@@ -2658,10 +2935,18 @@ TTrack::DoubleSlip() const {
}
TTrack * TTrack::Connected(int s, double &d) const
TTrack * TTrack::Connected(int s, double &d, int const Path) const
{ // zwraca wskaźnik na sąsiedni tor, w kierunku określonym znakiem (s), odwraca (d) w razie
// niezgodności kierunku torów
TTrack *t; // nie zmieniamy kierunku (d), jeśli nie ma toru dalej
if( eType == tt_Switch && Path >= 0 && Path < 2 ) {
// the neighbour of the path given, not of the one the switch is set to
t = ( s > 0 ? SwitchExtension->pNexts[ Path ] : SwitchExtension->pPrevs[ Path ] );
if( t != nullptr && t->eType != tt_Cross && ( s > 0 ? SwitchExtension->iNextDirection[ Path ] : !SwitchExtension->iPrevDirection[ Path ] ) ) {
d = -d;
}
return t;
}
if (eType != tt_Cross)
{ // jeszcze trzeba sprawdzić zgodność
t = s > 0 ? trNext : trPrev;
@@ -2710,7 +2995,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 +3005,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 +3013,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 +3080,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 +3113,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 +3221,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 ) },
@@ -3089,6 +3387,12 @@ TTrack::create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previ
{0.5f + map12, 0.f} }; // prawy skos
}
}
if( m_sleepers3d.active ) {
// the ballast lowered under the sleepers laid on it, so they stick out of it
for( auto &point : Output ) {
point.position.y -= static_cast<float>( track_sleepers::bed_drop );
}
}
}
// creates road profile data for current path
@@ -3325,8 +3629,8 @@ TTrack::create_switch_trackbed( gfx::vertex_array &Output ) {
&& trNext->eType == tt_Normal ) {
SwitchExtension->m_material3 = trNext->m_material2;
}
// without material don't bother
if( SwitchExtension->m_material3 == null_handle ) { return; }
// without material don't bother, nor when the ballast isn't laid under the 3D sleepers
if( SwitchExtension->m_material3 == null_handle || ( m_sleepers3d.active && false == Global.Sleepers3DBed ) ) { return; }
// generate trackbed for each path of the switch...
auto const texturelength { texture_length( SwitchExtension->m_material3 ) };
gfx::vertex_array trackbedprofile;
@@ -3337,9 +3641,20 @@ TTrack::create_switch_trackbed( gfx::vertex_array &Output ) {
// side trackbed
create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 1 ], SwitchExtension->pNexts[ 1 ] );
SwitchExtension->Segments[ 1 ]->RenderLoft( trackbedvertices2, m_origin, trackbedprofile, true, texturelength );
if( trackbedvertices1.size() != trackbedvertices2.size() ) {
// a path made of several curves has more pieces than the other one, and the two are combined piece by piece,
// so both are lofted again in the same number of equal pieces, else the trackbed would end where the shorter count does
auto const steps { std::max( SwitchExtension->Segments[ 0 ]->RaSegCount(), SwitchExtension->Segments[ 1 ]->RaSegCount() ) };
trackbedvertices1.clear();
trackbedvertices2.clear();
create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 0 ], SwitchExtension->pNexts[ 0 ] );
SwitchExtension->Segments[ 0 ]->RenderLoftSteps( trackbedvertices1, m_origin, trackbedprofile, texturelength, steps );
create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 1 ], SwitchExtension->pNexts[ 1 ] );
SwitchExtension->Segments[ 1 ]->RenderLoftSteps( trackbedvertices2, m_origin, trackbedprofile, texturelength, steps );
}
// ...then combine them into a single geometry sequence
auto const segmentsize { 10 };
auto const segmentcount { trackbedvertices1.size() / segmentsize };
auto const segmentcount { std::min( trackbedvertices1.size(), trackbedvertices2.size() ) / segmentsize };
auto *sampler1 { trackbedvertices1.data() };
auto *sampler2 { trackbedvertices2.data() };
auto const isright { SwitchExtension->RightSwitch };

View File

@@ -19,6 +19,8 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Classes.h"
#include "world/Segment.h"
#include "world/RailLayout.h"
#include "world/TrackSleepers.h"
#include "model/material.h"
#include "scene/scenenode.h"
#include "utilities/Names.h"
@@ -54,6 +56,14 @@ class TGroundNode;
class TSubRect;
class TTraction;
// a chunk of the geometry of a track kept to be laid again in, for the rails changing with the state of a switch
struct pooled_chunk {
int type { 0 };
gfx::geometry_handle handle;
bool used { false };
};
using chunk_pool = std::vector<pooled_chunk>;
class TSwitchExtension
{ // dodatkowe dane do toru, który jest zwrotnicą
public:
@@ -99,6 +109,12 @@ 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
bool m_movablenose { false }; // the nose of the frog moves over to the rail of the track set, closing its flangeway; no check rails
double m_bladeheel { 0.0 }; // where the moving blades end along the paths, as the template of the switch gives it; 0: as long as the flangeway needs
double m_bladethrow { 0.0 }; // how far the tips of the blades move over; 0: the usual throw
double m_nosethrow { -1.0 }; // how far over to the second path the movable nose was laid, -1: no geometry yet
chunk_pool m_nosechunks; // the geometry of the switch, laid again in the same chunks as the nose moves over
gfx::geometrybank_handle map_geometry[6]; // geometry for map visualisation
};
@@ -180,12 +196,26 @@ public:
material_handle m_material1 = 0; // tekstura szyn albo nawierzchni
material_handle m_material2 = 0; // tekstura automatycznej podsypki albo pobocza
std::pair<std::string, int> m_profile1 {}; // profile of geometry chunks textured with texture 1
// sets the rail profile by its name, as railprofile does; empty: none, the default shape
void rail_profile( std::string const &Name );
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;
@@ -202,12 +232,19 @@ public:
std::string m_sleeper_skin_name; // replacable skin path, or "none" for default
glm::vec3 m_sleeper_offset { 0.f, 0.f, 0.f }; // local offset from track centerline (x: left/right, y: forward/back, z: up/down)
float m_sleeper_ballast_z { 0.f }; // vertical offset applied to the trackbed (ballast) profile
// timbermodel optional parameter: the model of the timbers laid under the track where it's part of a switch or a crossing,
// in place of the one set with sleepers3d in eu07.ini. Defined in scenery file as:
// timbermodel <model> <skin>
std::string m_timber_model_name;
std::string m_timber_skin_name; // replacable skin, empty for the textures of the model
TModel3d *m_sleeper_model { nullptr }; // resolved on init; nullptr means no model / failed to load
material_handle m_sleeper_skin { 0 }; // resolved replacable skin handle, 0 = use model defaults
// precomputed local-space transforms (relative to m_origin) for every sleeper instance along the path.
// Each matrix is translate(world_pos - m_origin) * rotate(direction, roll) * translate(local_offset).
// The renderer composes this with (view * translate(m_origin - camera_pos)) per draw.
std::vector<glm::mat4> m_sleeper_local_transforms;
// sleepers and switch timbers made of the model set with sleepers3d in eu07.ini, laid where the simulator makes the ballast
track_sleepers::laid m_sleepers3d;
public:
using dynamics_sequence = std::deque<TDynamicObject *>;
@@ -262,13 +299,20 @@ public:
material_handle copy_adjacent_trackbed_material( TTrack const *Exclude = nullptr );
inline double Length() const {
return Segment->GetLength(); };
// how far below the top of the rails their feet are, by the rail profile of the track
float rail_height() const;
inline std::shared_ptr<TSegment> CurrentSegment() const {
return Segment; };
inline TTrack *CurrentNext() const {
return trNext; };
inline TTrack *CurrentPrev() const {
return trPrev; };
TTrack *Connected(int s, double &d) const;
TTrack *Connected(int s, double &d, int const Path = -1) const; // Path: of a switch, the one to leave by whichever way it is set
double trailing_point( int const Path ) const; // where along the path the wheels coming from the frog end push the blades over
int nose_set() const; // the path the movable nose of the frog lies for, following the blades
double nose_throw() const; // how far the movable nose of the frog went over to the second path, following the blades
material_handle rail_material( int const Path ) const;
glm::vec2 tread_band() const;
bool SetConnections(int i);
bool Switch(int i, float const t = -1.f, float const d = -1.f);
bool SwitchForced(int i, TDynamicObject *o);
@@ -285,6 +329,15 @@ public:
int RouteDraw() const;
void Load(cParser *parser, glm::dvec3 const &pOrigin);
void init_segments( bool const Initial );
track_rails::switch_frog frog() const;
track_rails::switch_blade const &blade( int const Path ) const;
double blades_throw() const; // how far the tips of the blades move over between the two paths
std::vector<track_rails::rail_crossing> rail_crossings( bool const Joined = true );
track_rails::rail_size rail_dimensions() const;
// the check rails beside the rails of each path, kept clear of the blades and the rails of other paths running inside them
track_rails::check_rail_set check_rails( std::vector<track_rails::rail_crossing> const &Crossings );
// Carve: the parts of the rails at crossings carved out of what lies around them, false: only their design
track_rails::rail_layout plan_rails( bool const Crossings, bool const Carve = true );
void update_location();
bool AssignEvents();
bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus);
@@ -366,8 +419,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 );
@@ -377,6 +432,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;
track_rails::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( track_rails::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 );
class rail_layer;
class rail_planner;
int blade_side( int const Path ) 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;

21
world/TrackFeatures.h Normal file
View File

@@ -0,0 +1,21 @@
/*
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
// features of the tracks built into the simulator but switched off in it as a whole, whatever the scenery or eu07.ini sets
namespace track_features {
// the rails cut where they cross other rails into frogs, wing rails and check rails; off, the rails of plain tracks and of
// switches with the key frogs are laid whole, as before
bool constexpr crossings { false };
// the 3D sleepers and switch timbers of sleepers3d; off, the tracks keep their ballast whatever eu07.ini sets
bool constexpr sleepers { false };
} // namespace track_features

73
world/TrackRails.h Normal file
View File

@@ -0,0 +1,73 @@
/*
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"
#include "world/GaugeLine.h"
namespace track_rails {
void forget();
// changes with every forget(): what's kept of the rails around holds as long as it doesn't
unsigned int memo_stamp();
rail_size measure_rail( gfx::vertex_array const &Profile );
// the noses, wing rails and point rails at crossings at small angles are carved out of what lies around them, all by the same rules: no rail
// reaches into the flangeway of another one where its wheels run, and two heads lying over each other share the room between them by what
// the wheels of each roll on
void carve_plans( TTrack &Track, rail_layout &Layout );
// the layout of the track designed already to carve the tracks around against, before carving
rail_layout const *designed_layout( TTrack &Track );
// how many parts carving left with nothing of the head somewhere along them, since the start
int carve_faults();
// 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 );
// how far from the tip the nose of a movable frog bends over to the wing rail
double movable_nose_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, int const Path );
double blade_length( TTrack const &Track, int const Path );
// the plain track a path of a switch goes on into at its end, laid the same way, which the blade of the path may run on into
TTrack const *runon_track( TTrack const &Switch, int const Path );
// how far the blade of the switch the track goes on from runs on into its rail of Side
double blade_runon( TTrack const &Track, int const Side );
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 );
// the geometry of a track laid while one is alive goes into the chunks of the pool given, of the same type, before new ones
class reused_chunks {
public:
reused_chunks( chunk_pool *Pool );
~reused_chunks();
private:
chunk_pool *m_previous { nullptr };
};
gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type );
TSegment const &path_segment( TTrack const &Track, int const Path );
int path_count( TTrack const &Track );
TTrack *path_neighbour( TTrack const &Track, int const Path, int const End );
double knuckle_bend( double const Into );
bool replaced_check( std::vector<check_rail> const &Checks, double const Wingend, double const Direction, double &Far );
bool replaced_by_wing( check_rail const &Check, knots::line const &Wingends );
gauge_line const &rail_line( TTrack const &Track, int const Path, int const Side, bool const Blades );
check_rail_set const &rail_checks( TTrack &Track );
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

371
world/TrackSleepers.cpp Normal file
View File

@@ -0,0 +1,371 @@
/*
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/.
*/
#include "stdafx.h"
#include "world/TrackSleepers.h"
#include "world/SleeperGeometry.h"
#include "world/SleeperLayout.h"
#include "world/SleeperPaths.h"
#include "world/SleeperRules.h"
#include "world/SleeperWorker.h"
#include "world/Track.h"
#include "simulation/simulation.h"
#include "scene/scene.h"
#include "utilities/utilities.h"
#include <glm/gtc/matrix_transform.hpp>
#include <map>
#include <set>
namespace track_sleepers {
bool bedded( TTrack const &Track ) {
if( ( Track.iCategoryFlag & 15 ) != 1 || Track.m_editorremoved ) {
return false;
}
switch( Track.eType ) {
case tt_Normal: {
if( Track.m_material2 == null_handle || Track.Segment == nullptr ) {
return false;
}
break;
}
case tt_Switch: {
// the switch trackbed takes the ballast of a plain track joined to it
auto const ballast = [&]( TTrack const *Other ) {
return Other != nullptr && Other->eType == tt_Normal && Other->m_material2 != null_handle; };
if( false == Global.CreateSwitchTrackbeds || Track.SwitchExtension == nullptr
|| ( Track.SwitchExtension->m_material3 == null_handle && false == ballast( Track.trPrev ) && false == ballast( Track.trNext ) ) ) {
return false;
}
break;
}
default: {
return false;
}
}
return true;
}
bool lays( TTrack const &Track ) {
return timber_model() != nullptr && bedded( Track );
}
std::vector<std::pair<double, double>> shared_zones( TTrack const &Track, int const Path ) {
std::vector<std::pair<double, double>> result;
if( false == bedded( Track ) || Path < 0 || Path >= path_count( Track ) ) {
return result;
}
auto const around { paths_around( const_cast<TTrack &>( Track ), true ) };
auto const found { std::find_if( around.begin(), around.end(), [&]( sampled_path const &Sampled ) { return Sampled.track == &Track && Sampled.path == Path; } ) };
if( found == around.end() ) {
return result;
}
auto const &path { *found };
// where another path runs so near that the timbers carry both
for( auto const &stretch : stretches_where( path, [&]( glm::dvec3 const &Point, glm::dvec2 const &Direction ) {
return std::any_of( around.begin(), around.end(), [&]( sampled_path const &Other ) {
return &Other != &path && shares_with( path, Point, Direction, Other ); } ); } ) ) {
result.emplace_back( stretch.x, stretch.y );
}
return result;
}
std::pair<double, double> shared_zone( TTrack const &Track, int const Path ) {
auto const zones { shared_zones( Track, Path ) };
if( zones.empty() ) {
return { 0.0, 0.0 };
}
return { zones.front().first, zones.back().second };
}
void lay( TTrack &Track ) {
auto &laid { Track.m_sleepers3d };
std::vector<beam> placed;
if( laid.active && in_background() && timber_model( Track ) != nullptr ) {
// worked out in the background; till then the track keeps what it has, and tries again
auto beams { laid_out( Track ) };
if( false == beams.has_value() ) {
ask_layout( Track );
return;
}
placed = std::move( *beams );
}
laid.dirty = false;
for( auto &instances : laid.instances ) {
instances.clear();
}
laid.whole.clear();
laid.plain.clear();
laid.beams.clear();
laid.chunks.clear();
laid.timbers = timber_model( Track );
laid.plains = plain_model( Track );
if( false == laid.active || laid.timbers == nullptr ) {
return;
}
auto const &parts { *laid.timbers };
if( auto const panel { panel_off_plane( Track ) }; panel.first > coplanar_tolerance ) {
WriteLog(
"sleepers3d: switch \"" + Track.name() + "\" has its paths " + to_string( panel.first * 1000.0, 1 ) + " mm off one plane "
+ to_string( panel.second, 2 ) + " m along, where they share the timbers of its panel" );
}
laid.laying = true;
laid.beams = ( in_background() ? std::move( placed ) : place( Track ) );
laid.laying = false;
auto const &beams { laid.beams };
auto const partlength = [&]( int const Part ) {
return static_cast<double>( parts.spans[ Part ].y - parts.spans[ Part ].x ); };
auto const beginlength { partlength( part_begin ) };
auto const mainlength { std::max( 0.01, partlength( part_main ) ) };
auto const endlength { partlength( part_end ) };
chunk run;
std::vector<std::pair<glm::dvec3, double>> runbeams;
auto const close_run = [&]() {
if( runbeams.empty() ) {
return;
}
glm::dvec3 centre { 0.0 };
for( auto const &runbeam : runbeams ) {
centre += runbeam.first;
}
centre /= static_cast<double>( runbeams.size() );
auto radius { 0.0 };
for( auto const &runbeam : runbeams ) {
radius = std::max( radius, glm::length( runbeam.first - centre ) + runbeam.second );
}
run.centre = glm::vec3 { centre };
run.radius = static_cast<float>( radius + 0.3 );
for( int part = 0; part < part_count; ++part ) {
run.last[ part ] = static_cast<std::uint32_t>( laid.instances[ part ].size() );
}
run.wholelast = static_cast<std::uint32_t>( laid.whole.size() );
run.plainlast = static_cast<std::uint32_t>( laid.plain.size() );
laid.chunks.push_back( run );
runbeams.clear();
};
for( auto const &beam : beams ) {
if( runbeams.empty() ) {
for( int part = 0; part < part_count; ++part ) {
run.first[ part ] = static_cast<std::uint32_t>( laid.instances[ part ].size() );
}
run.wholefirst = static_cast<std::uint32_t>( laid.whole.size() );
run.plainfirst = static_cast<std::uint32_t>( laid.plain.size() );
}
auto const length { glm::length( beam.to - beam.from ) };
if( length < 0.1 ) {
continue;
}
// the frame of the beam: x along it, y up, z along the tracks
glm::dvec3 const x { ( beam.to - beam.from ) / length };
auto z { glm::normalize( beam.along - x * glm::dot( beam.along, x ) ) };
auto y { glm::cross( z, x ) };
if( y.y < 0.0 ) {
z = -z;
y = -y;
}
auto const middle { 0.5 * ( beam.from + beam.to ) - Track.m_origin };
glm::mat4 frame { 1.f };
frame[ 0 ] = glm::vec4 { glm::vec3 { x }, 0.f };
frame[ 1 ] = glm::vec4 { glm::vec3 { y }, 0.f };
frame[ 2 ] = glm::vec4 { glm::vec3 { z }, 0.f };
frame[ 3 ] = glm::vec4 { glm::vec3 { middle }, 1.f };
// a part stretched along x by the scale, and moved so its x lands at the given place along the beam
auto const put = [&]( int const Part, double const Scale, double const From, double const Place ) {
auto const shift { Place - Scale * From };
laid.instances[ Part ].emplace_back(
glm::scale(
glm::translate( frame, glm::vec3 { static_cast<float>( shift ), -parts.seat, 0.f } ),
glm::vec3 { static_cast<float>( Scale ), 1.f, 1.f } ) );
};
auto const half { 0.5 * length };
auto const between { length - beginlength - endlength };
if( beam.plain && laid.plains != nullptr ) {
// the whole sleeper at its own length, its middle under the middle of the track
auto const &plain { *laid.plains };
laid.plain.emplace_back(
glm::translate( frame, glm::vec3 { -0.5f * ( plain.span.x + plain.span.y ), -plain.seat, 0.f } ) );
}
else if( false == parts.stretchable ) {
// a timber of a model which can't be put together, the whole of it stretched to the length
auto const scale { length / static_cast<double>( parts.length() ) };
laid.whole.emplace_back(
glm::scale(
glm::translate( frame, glm::vec3 { static_cast<float>( -scale * 0.5 * ( parts.span.x + parts.span.y ) ), -parts.seat, 0.f } ),
glm::vec3 { static_cast<float>( scale ), 1.f, 1.f } ) );
}
else if( between > 0.01 ) {
// the ends at their own size, and as many pieces between them as fit closest to their own size
put( part_end, 1.0, parts.spans[ part_end ].x, -half );
put( part_begin, 1.0, parts.spans[ part_begin ].y, half );
auto const pieces { std::max( 1, static_cast<int>( std::round( between / mainlength ) ) ) };
auto const piece { between / pieces };
for( int index = 0; index < pieces; ++index ) {
put( part_main, piece / mainlength, parts.spans[ part_main ].x, -half + endlength + index * piece );
}
}
else {
// a beam shorter than its ends: just the ends, squeezed
auto const scale { length / std::max( 0.01, beginlength + endlength ) };
put( part_end, scale, parts.spans[ part_end ].x, -half );
put( part_begin, scale, parts.spans[ part_begin ].y, half );
}
runbeams.emplace_back( middle, half );
if( runbeams.size() >= chunk_beams ) {
close_run();
}
}
close_run();
}
layout_check check() {
layout_check result;
result.digest = 1469598103934665603ull;
settle_background();
faults() = {};
// laid anew, for the counts of what's wrong with them
outdate_layouts();
// the beams of all the tracks in plan, as boxes, with the grid of where they are
std::vector<beam_box> boxes;
std::vector<TTrack const *> owners;
std::vector<TTrack *> tracks;
for( auto *track : simulation::Paths.sequence() ) {
if( track == nullptr || track->m_editorremoved || false == track->m_sleepers3d.active ) {
continue;
}
tracks.push_back( track );
if( auto const panel { panel_off_plane( *track ) }; panel.first > coplanar_tolerance ) {
++result.panels;
if( result.panels <= 4 ) result.examples.push_back(
"switch " + track->name() + " " + to_string( panel.first * 1000.0, 1 ) + " mm off one plane at " + to_string( panel.second, 2 ) + " m" );
}
for( auto const &beam : place( *track ) ) {
for( auto const &vector : { beam.from, beam.to, beam.along } ) {
for( int axis = 0; axis < 3; ++axis ) {
result.digest = ( result.digest ^ static_cast<std::uint64_t>( std::llround( vector[ axis ] * 1e4 ) ) ) * 1099511628211ull;
}
}
result.digest = ( result.digest ^ ( beam.plain ? 1u : 2u ) ) * 1099511628211ull;
auto const span { plan( beam.to - beam.from ) };
auto const length { glm::length( span ) };
if( length < 0.1 ) {
continue;
}
boxes.push_back( box_of( beam ) );
owners.push_back( track );
result.longest = std::max( result.longest, length );
if( length > length_cap ) {
++result.toolong;
}
}
}
result.beams = boxes.size();
result.offplane = faults().offplane;
result.clipped = faults().clipped;
result.dropped = faults().dropped;
result.slowest = faults().slowest;
result.biggest = faults().biggest;
// each box in every cell its span touches
plan_grid<std::size_t> grid { 3.0 };
for( std::size_t index = 0; index < boxes.size(); ++index ) {
grid.insert( boxes[ index ].centre, boxes[ index ].half + half_width, index );
}
std::set<std::pair<std::size_t, std::size_t>> overlaps;
for( auto const &cell : grid.cells() ) {
auto const &indices { cell.second };
for( std::size_t left = 0; left < indices.size(); ++left ) {
for( std::size_t right = left + 1; right < indices.size(); ++right ) {
auto const pair { std::minmax( indices[ left ], indices[ right ] ) };
if( overlaps.count( pair ) || apart( boxes[ pair.first ], boxes[ pair.second ] ) ) {
continue;
}
overlaps.insert( pair );
if( overlaps.size() <= 16 ) {
auto const &box { boxes[ pair.first ] };
result.examples.push_back(
"overlap " + owners[ pair.first ]->name() + "/" + owners[ pair.second ]->name() + " at " + to_string( box.centre.x, 2 ) + " " + to_string( box.centre.y, 2 ) );
}
}
}
}
result.overlaps = overlaps.size();
// the rails of the tracks, followed for how far apart the beams under them are
for( auto *track : tracks ) {
for( int path = 0; path < path_count( *track ); ++path ) {
auto const *segment { path_segment( *track, path ) };
if( segment == nullptr ) {
continue;
}
auto const length { segment->GetLength() };
auto const steps { static_cast<int>( std::ceil( length / 0.05 ) ) };
for( auto const side : { -1.0, 1.0 } ) {
// from the first beam under the rail to the last, how long it runs with none under it
auto last { -1.0 };
auto first { -1.0 };
for( int step = 0; step <= steps; ++step ) {
auto const distance { length * step / std::max( 1, steps ) };
glm::dvec3 point, across;
segment->plan_frame( distance, point, across );
auto const rail { plan( point + across * ( side * 0.5 * std::abs( track->fTrackWidth ) ) ) };
auto const *cell { grid.at( rail ) };
if( cell == nullptr || std::none_of( cell->begin(), cell->end(), [&]( std::size_t const Index ) { return holds( boxes[ Index ], rail ); } ) ) {
continue;
}
if( last >= 0.0 && distance - last > 0.06 ) {
// the spacing of the beams, middle to middle, by the stretch without one
auto const spacing { distance - last + 2.0 * half_width };
if( spacing > result.widest ) {
result.widest = spacing;
result.widestat = track->name() + " " + to_string( distance, 2 );
}
if( spacing > 1.25 * 1.08 * pitch ) {
++result.gaps;
if( result.examples.size() < 24 ) {
result.examples.push_back( "gap " + to_string( spacing, 2 ) + " m under " + track->name() + " path " + std::to_string( path ) + " at " + to_string( distance, 2 ) );
}
}
}
if( first < 0.0 ) {
first = distance;
}
last = distance;
}
if( first < 0.0 ) {
++result.bare;
if( result.examples.size() < 80 ) {
result.examples.push_back( "no beams under " + track->name() + " path " + std::to_string( path ) );
}
}
}
}
}
return result;
}
void unsettle_around( TTrack &Track ) {
outdate_layouts();
tracks_near( Track, []( TTrack *Other ) {
if( Other->m_sleepers3d.active ) {
Other->m_sleepers3d.dirty = true;
} } );
}
} // namespace track_sleepers

161
world/TrackSleepers.h Normal file
View File

@@ -0,0 +1,161 @@
/*
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 <array>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include <glm/glm.hpp>
class TTrack;
class TModel3d;
class TSubModel;
// sleepers and switch timbers laid under the rails of the tracks whose ballast the simulator makes, when a model of them
// is set with sleepers3d in eu07.ini. A timber is put together from the parts of the model: its two ends and the piece between
// them, repeated and stretched to the length of the beam, so one model makes the timbers of any length; a model without such
// parts is drawn whole, stretched. The sleepers of plain tracks are a whole model of their own, set with sleepers3dplain, drawn
// at their own length. A track can have models of its own: of its sleepers with sleepermodel, of its timbers with timbermodel.
namespace track_sleepers {
// the parts of the model a beam is put together from, by the names of the submodels
enum part : int {
part_begin, // the end of the beam on its +x side
part_main, // the piece between the ends
part_end, // the end on the -x side
part_count
};
// a sleeper or a timber, by the middle of its top face at both ends
struct beam {
glm::dvec3 from;
glm::dvec3 to;
glm::dvec3 along; // unit vector across the beam, along the tracks it carries
bool plain { false }; // a sleeper of a track running alone, square to it, not a timber of a switch or a crossing
double corefrom { 0.0 }; // the stretch the feet of the rails it carries take, along it from its from end
double coreto { 0.0 };
};
// a model sleepers or timbers are made of, as found in it
struct beam_model {
std::string name;
std::string skinname;
TModel3d *model { nullptr };
int skin { 0 }; // the replacable skin, 0 for the textures of the model
// put together from its submodels begin, main and end, the one between stretched and repeated to the length of a timber; a
// model without these is drawn whole, a plain sleeper at its own length and a timber stretched to its length
bool stretchable { false };
std::array<TSubModel *, part_count> parts {};
std::array<glm::vec2, part_count> spans {}; // from and to along x, of the parts
glm::vec2 span { -1.3f, 1.3f }; // from and to along x, of the most detailed submodels of the whole model
float seat { 0.f }; // the height in the model the feet of the rails go to
std::vector<float> bands; // the squared distances the levels of detail of the model are seen from, the nearest first
float length() const { return span.y - span.x; }
};
// a run of beams of a track drawn or left out together, by the sphere around them
struct chunk {
glm::vec3 centre; // relative to the origin of the track
float radius { 0.f };
std::array<std::uint32_t, part_count> first {};
std::array<std::uint32_t, part_count> last {};
std::uint32_t wholefirst { 0 }; // the timbers of the run made of a whole model
std::uint32_t wholelast { 0 };
std::uint32_t plainfirst { 0 }; // the sleepers of the run made of the model of the plain sleepers
std::uint32_t plainlast { 0 };
};
// the beams laid under a track, as the instances of the parts of the model
struct laid {
bool active { false }; // the track gets beams and its ballast is lowered under them; set as its geometry is made
bool dirty { true }; // the beams are to be laid again before they're drawn
bool laying { false }; // the beams are being placed, so the tracks about it work out its beams themselves meanwhile
beam_model const *timbers { nullptr }; // the model the timbers are made of
beam_model const *plains { nullptr }; // the model of the plain sleepers, null where they're made of that of the timbers
std::array<std::vector<glm::mat4>, part_count> instances; // transforms of the parts relative to the origin of the track
std::vector<glm::mat4> whole; // transforms of the timbers of a model without parts, drawn whole
std::vector<glm::mat4> plain; // transforms of the plain sleepers, whole models, relative to the origin of the track
std::vector<chunk> chunks;
std::vector<beam> beams; // as they're laid
};
// how far the ballast of a track with beams is lowered, so they stick out of it
double constexpr bed_drop { 0.06 };
// the model the timbers are made of where a track has none of its own, set with sleepers3d in eu07.ini and loaded on the first
// use; null when there's none, and no beams are laid
beam_model const *timber_model();
// the model of the sleepers of the tracks running alone, set with sleepers3dplain; null when there's none
beam_model const *plain_model();
// the models the track lays its beams of: those set for it with timbermodel and sleepermodel, else the ones of eu07.ini
beam_model const *timber_model( TTrack const &Track );
beam_model const *plain_model( TTrack const &Track );
// how long the plain sleepers of the track are, of the model it lays them of; 2.6 m at the standard gauge where there's none
double plain_length( TTrack const &Track );
// whether the track has the ballast the simulator makes, which the beams are laid on when there's a model of them
bool bedded( TTrack const &Track );
// whether the track gets beams: a rail track with the ballast made by the simulator, and the model is there
bool lays( TTrack const &Track );
// where along the path of the track its rails lie on timbers shared with other tracks: where another track runs so near that
// the plain sleepers of the two would touch, of the lengths of the models they'd be made of. this goes by the shape of the
// tracks alone, so it holds whether the beams are drawn or not. the stretches in the order along the path, none where there
// are no shared timbers
std::vector<std::pair<double, double>> shared_zones( TTrack const &Track, int const Path );
// from the start of the first of these stretches to the end of the last, both zero where there's none
std::pair<double, double> shared_zone( TTrack const &Track, int const Path );
// a model of sleepers or timbers in the scenery data, by the name it's loaded by
struct pack_model {
std::string name;
bool timber { false }; // has the submodels begin, main and end a timber is put together from
};
// the models of sleepers and timbers in the scenery data, found once in models/ by the names of their files telling sleepers
// or timbers: podklad, podrozjazd, sleeper, timber. the timber ones first, then by the name
std::vector<pack_model> const &pack_models();
// the beams of the track placed anew, as instances of the parts of the model
void lay( TTrack &Track );
// the beams of the tracks around the track to be placed again, as they depend on where it runs
void unsettle_around( TTrack &Track );
// the beams worked out in a thread of their own as the tracks come near, while the simulation runs; off in the editor, where the
// tracks change and the beams are worked out as they're needed. turning it off waits for the beams being worked out
void lay_in_background( bool const Background );
// the beams of the track worked out ahead, before it comes in range
void prefetch( TTrack &Track );
// what's wrong with the layout of the beams of all the tracks: beams over each other, rails with too few beams under them,
// timbers shared by rails out of one plane
struct layout_check {
std::size_t beams { 0 };
std::size_t overlaps { 0 };
std::size_t gaps { 0 }; // stretches of rail with the beams under it further apart than the pitch allows
std::size_t bare { 0 }; // rails with no beams under them at all
std::size_t toolong { 0 };
std::size_t offplane { 0 }; // timbers shared by rails out of one plane, where the tracks run too close to part them
std::size_t clipped { 0 }; // beam ends cut shorter than the least overhang to keep clear of the beams of other tracks
std::size_t dropped { 0 }; // beams left out where they'd lie over one laid before
double slowest { 0.0 }; // the longest a cluster of tracks took to lay, in ms
std::size_t biggest { 0 }; // the most tracks in a cluster
std::size_t panels { 0 }; // switches with their paths out of one plane where they share the timbers of the panel
std::uint64_t digest { 0 }; // of where all the beams lie, to tell whether a change of the code moved any
double longest { 0.0 };
double widest { 0.0 }; // the widest spacing of the beams under a rail
std::string widestat;
std::vector<std::string> examples;
};
layout_check check();
// the beams laid with the track: the tracks whose plain sleepers would meet make a cluster laid together, lines of timbers
// across all of their rails turned to the bisectors of frogs and crossings, and the plain sleepers square to each track where no
// timber carries it, in step with the timbers. every beam is laid with one track of the cluster
std::vector<beam> place( TTrack &Track );
} // namespace track_sleepers

View File

@@ -44,8 +44,19 @@ void TTrackFollower::Reset()
fDirection = 1.0;
}
// the end of the next track joined to the end of the current one, of the path of a switch the axle runs on
int TTrackFollower::JoinedEnd(int const End) const
{
if( iPath >= 0 && pCurrentTrack->eType == tt_Switch ) {
return End ? pCurrentTrack->SwitchExtension->iNextDirection[ iPath ] : pCurrentTrack->SwitchExtension->iPrevDirection[ iPath ];
}
return End ? pCurrentTrack->iNextDirection : pCurrentTrack->iPrevDirection;
}
TTrack * TTrackFollower::SetCurrentTrack(TTrack *pTrack, int end)
{ // przejechanie na inny odcinkek toru, z ewentualnym rozpruciem
auto path { -1 };
auto trailat { -1.0 };
if (pTrack)
switch (pTrack->eType)
{
@@ -54,8 +65,14 @@ TTrack * TTrackFollower::SetCurrentTrack(TTrack *pTrack, int end)
if( pCurrentTrack == nullptr ) {
break;
}
int i = end ? pCurrentTrack->iNextDirection : pCurrentTrack->iPrevDirection;
if (i > 0) // jeżeli wjazd z ostrza
int i = JoinedEnd( end );
if( i & 1 ) {
// coming from the frog end the axle runs on its path whichever way the switch is set; it gets trailed through
// only where the wheels get to the blades, or to the nose of a movable frog
path = i >> 1;
trailat = pTrack->trailing_point( path );
}
else if (i > 0) // jeżeli wjazd z ostrza
pTrack->SwitchForced(i >> 1, Owner); // to przełożenie zwrotnicy - rozprucie!
}
break;
@@ -94,7 +111,13 @@ TTrack * TTrackFollower::SetCurrentTrack(TTrack *pTrack, int end)
pCurrentTrack->AxleCounter(-1, Owner); // opuszczenie tamtego toru
}
pCurrentTrack = pTrack;
pCurrentSegment = pCurrentTrack != nullptr ? pCurrentTrack->CurrentSegment() : nullptr;
// on a switch the axle keeps to the path it came on, the switch thrown under it doesn't move it over to the other one
iPath = ( pCurrentTrack != nullptr && pCurrentTrack->eType == tt_Switch && pCurrentTrack->SwitchExtension ) ? ( path >= 0 ? path : pCurrentTrack->SwitchExtension->CurrentIndex ) : -1;
fTrailAt = ( iPath >= 0 ? trailat : -1.0 );
pCurrentSegment =
iPath >= 0 ? pCurrentTrack->SwitchExtension->Segments[ iPath ] :
pCurrentTrack != nullptr ? pCurrentTrack->CurrentSegment() :
nullptr;
if (!pCurrentTrack)
Error(Owner->MoverParameters->Name + " at NULL track");
return pCurrentTrack;
@@ -167,6 +190,11 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary)
return false;
// if (fDistance==0.0) return true; //Ra: jak stoi, to chyba dalej nie ma co kombinować?
s = fCurrentDistance + fDistance; // doliczenie przesunięcia
if( fTrailAt >= 0.0 && s <= fTrailAt ) {
// the wheels got to the blades of the switch coming from its frog end: pushed over if set the other way - rozprucie
fTrailAt = -1.0;
pCurrentTrack->SwitchForced( iPath, Owner );
}
// Ra: W Point2 toru może znajdować się "dziura", która zamieni energię kinetyczną
// ruchu wzdłużnego na energię potencjalną, zamieniającą się potem na energię
// sprężystości na amortyzatorach. Należałoby we wpisie toru umieścić współczynnik
@@ -196,7 +224,7 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary)
if (!SetCurrentTrack(pCurrentTrack->Connected(iSegment, fDirection), 0))
return false; // wyjście z błędem
}
else if (!SetCurrentTrack(pCurrentTrack->Connected(-1, fDirection), 0)) // ustawia fDirection
else if (!SetCurrentTrack(pCurrentTrack->Connected(-1, fDirection, iPath), 0)) // ustawia fDirection
return false; // wyjście z błędem
if (dir == fDirection) //(pCurrentTrack->iPrevDirection)
{ // gdy kierunek bez zmiany (Point1->Point2)
@@ -230,7 +258,7 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary)
if (!SetCurrentTrack(pCurrentTrack->Connected(iSegment, fDirection), 1))
return false; // wyjście z błędem
}
else if (!SetCurrentTrack(pCurrentTrack->Connected(1, fDirection), 1)) // ustawia fDirection
else if (!SetCurrentTrack(pCurrentTrack->Connected(1, fDirection, iPath), 1)) // ustawia fDirection
return false; // wyjście z błędem
if (dir != fDirection) //(pCurrentTrack->iNextDirection)
{ // gdy zmiana kierunku toru (Point2->Point2)

View File

@@ -49,6 +49,7 @@ public:
private:
// methods
bool ComputatePosition(); // przeliczenie pozycji na torze
int JoinedEnd( int const End ) const;
// members
TTrack *pCurrentTrack = nullptr; // na którym torze siê znajduje
std::shared_ptr<TSegment> pCurrentSegment; // zwrotnice mog¹ mieæ dwa segmenty
@@ -58,6 +59,9 @@ public:
int iEventFlag = 0; // McZapkie-020602: informacja o tym czy wyzwalac zdarzenie: 0,1,2,3
int iEventallFlag = 0;
int iSegment = 0; // który segment toru jest używany (żeby nie przeskakiwało po przestawieniu zwrotnicy pod taborem)
int iPath = -1; // the path of the switch the axle runs on
double fTrailAt = -1.0; // coming from the frog end of a switch: where along the path the wheels push the blades over
};
//---------------------------------------------------------------------------
#endif