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:
65
world/GaugeLine.cpp
Normal file
65
world/GaugeLine.cpp
Normal 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
72
world/GaugeLine.h
Normal 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
151
world/Knots.h
Normal 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
1167
world/RailCarve.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1032
world/RailCrossings.cpp
Normal file
1032
world/RailCrossings.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1324
world/RailLayer.cpp
Normal file
1324
world/RailLayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
219
world/RailLayout.h
Normal file
219
world/RailLayout.h
Normal 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
207
world/RailMaterials.cpp
Normal 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
824
world/RailPlan.cpp
Normal 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
136
world/RailProfile.cpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
255
world/SleeperFeatures.cpp
Normal 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
47
world/SleeperFeatures.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <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
148
world/SleeperGeometry.h
Normal 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
709
world/SleeperLayout.cpp
Normal 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
38
world/SleeperLayout.h
Normal 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
276
world/SleeperLines.cpp
Normal 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
82
world/SleeperLines.h
Normal 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
229
world/SleeperModels.cpp
Normal 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
384
world/SleeperPaths.cpp
Normal 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
85
world/SleeperPaths.h
Normal 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
75
world/SleeperRules.h
Normal 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
230
world/SleeperWorker.cpp
Normal 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
31
world/SleeperWorker.h
Normal 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
|
||||
621
world/Track.cpp
621
world/Track.cpp
@@ -15,7 +15,9 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "scene/scene.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "world/Event.h"
|
||||
#include "world/MemCell.h"
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
21
world/TrackFeatures.h
Normal 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
73
world/TrackRails.h
Normal 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
371
world/TrackSleepers.cpp
Normal 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
161
world/TrackSleepers.h
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user