mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 12:44:52 +02:00
feat(track): the blades of a switch whose frog is cut from the rails are made from its geometry: the tip is where the gauge lines of both paths are 3 mm apart, the head reaches its full width where they are a head plus 0.5 mm apart, and the heel is the shortest blade whose open flangeway stays at least 58 mm with a throw of 160 mm bent like a cantilever from the heel, or straight when that does not fit as in slips; the blade is the whole rail profile lofted from the tip to the heel, cut along the head of the stock rail, with the head lowered by the drop of the blade half of the profile file towards the tip and back to the top at a 50 mm head, and thrown with knots every 0.5 m so the vertex count does not change between the positions; the stock rails get stations every 0.5 m along the blades so their curve follows the blade, the fixed rail behind the heel starts at the heel, the old blades take their segment count per path, and the self-test command blade logs the tip, heel, drop, flangeway and the gauge lines against x^2/2R and faults on a short flangeway, a heel past the throat or a change of the vertex count
This commit is contained in:
@@ -2486,6 +2486,73 @@ editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &co
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_rails(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "blade")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->SwitchExtension == nullptr)
|
||||
continue;
|
||||
if (false == name.empty() && track->name() != name)
|
||||
continue;
|
||||
auto const vertices = [&]() {
|
||||
return std::make_pair(track->Geometry1.empty() ? std::size_t{0} : GfxRenderer->Vertices(track->Geometry1[0]).size(), track->Geometry2.empty() ? std::size_t{0} : GfxRenderer->Vertices(track->Geometry2[0]).size());
|
||||
};
|
||||
auto const set = [&](int const Path) {
|
||||
track->Switch(Path);
|
||||
track->SwitchExtension->fOffset = track->SwitchExtension->fDesiredOffset;
|
||||
track->RaAnimate();
|
||||
};
|
||||
set(0);
|
||||
auto const straight{vertices()};
|
||||
set(1);
|
||||
auto const diverging{vertices()};
|
||||
set(0);
|
||||
auto const frog{track->frog()};
|
||||
auto const radius{std::abs(track->m_paths.size() > 1 ? track->m_paths[1].radius : 0.f)};
|
||||
for (int path = 0; path < 2; ++path)
|
||||
{
|
||||
auto const &shape{track->blade(path)};
|
||||
auto const &segment{*track->SwitchExtension->Segments[path]};
|
||||
auto const old{segment.GetLength() * track_rails::blade_steps(*track, path) / segment.RaSegCount()};
|
||||
auto const throat{frog.valid ? frog.at[path] - track_rails::flangeway / std::tan(frog.angle) : 0.0};
|
||||
auto const at = [&](double const Width) {
|
||||
for (std::size_t i = 1; i < shape.gauge.size(); ++i)
|
||||
if (shape.gauge[i].y >= Width)
|
||||
return glm::mix(shape.gauge[i - 1].x, shape.gauge[i].x, (Width - shape.gauge[i - 1].y) / std::max(shape.gauge[i].y - shape.gauge[i - 1].y, 1e-9));
|
||||
return -1.0;
|
||||
};
|
||||
auto const gauge = [&](double const Distance) {
|
||||
for (std::size_t i = 1; i < shape.gauge.size(); ++i)
|
||||
if (shape.gauge[i].x >= Distance)
|
||||
return glm::mix(shape.gauge[i - 1].y, shape.gauge[i].y, (Distance - shape.gauge[i - 1].x) / std::max(shape.gauge[i].x - shape.gauge[i - 1].x, 1e-9));
|
||||
return shape.gauge.empty() ? 0.0 : shape.gauge.back().y;
|
||||
};
|
||||
std::string samples;
|
||||
auto worst{0.0};
|
||||
for (auto const distance : {1.0, 2.0, 4.0, 6.0, 8.0, 10.0})
|
||||
{
|
||||
auto const analytic{radius > 0.f ? distance * distance / (2.0 * radius) : 0.0};
|
||||
samples += format(" %.0f:%.4f/%.4f", distance, gauge(distance), analytic);
|
||||
if (shape.valid && distance <= shape.heel)
|
||||
worst = std::max(worst, std::abs(gauge(distance) - analytic));
|
||||
}
|
||||
std::string faults;
|
||||
if (track_rails::cut_rails(*track) && false == shape.valid)
|
||||
faults += " FAULT blade not cut from the rails";
|
||||
if (shape.valid && shape.opening < track_rails::blade_clearance - 1e-4)
|
||||
faults += " FAULT flangeway";
|
||||
if (shape.valid && frog.valid && shape.heel >= throat)
|
||||
faults += " FAULT heel past the throat";
|
||||
if (straight != diverging)
|
||||
faults += " FAULT vertex count changes";
|
||||
WriteLog(format("SELFTEST blade %s path %d: %s, R %.0f, tip %.3f, 50mm %.3f, full head %.3f, heel %.3f (old %.3f), drop %.4f, opening %.4f at %.3f, throat %.3f, d(x)/x^2/2R%s, worst %.4f, vertices %zu/%zu %zu/%zu%s", track->name().c_str(), path, shape.valid ? (shape.rigid ? "geometric rigid" : "geometric") : "old", radius, shape.tip, at(track_rails::blade_lowered + track_rails::blade_gap), shape.full, shape.heel, old, shape.drop, shape.opening, shape.openingat,
|
||||
throat, samples.c_str(), worst, straight.first, diverging.first, straight.second, diverging.second, faults.c_str()));
|
||||
}
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "frog")
|
||||
{
|
||||
std::string name;
|
||||
@@ -2801,6 +2868,7 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm
|
||||
// wait <frames>
|
||||
// camera <x> <y> <z> <x> <y> <z> from the first point, looking at the second
|
||||
// select <track name>
|
||||
// blade [name] logs the blades of each switch cut from the rails: tip, 50 mm head, full head, heel, drop of the head, open flangeway, the gauge lines apart against x^2/2R, vertices in both positions
|
||||
// frog [name] logs where the inner rails of each switch cross: distance on both paths, 1:n, throat, nose, check rails
|
||||
// guards [name] logs each guard rail and what each of its ends and breaks is cut along; a fault when a cut is not along a rail or a glued end not on the guard rail of the other track
|
||||
// railtime lays out the rails of every track, as when its geometry is made, and logs how long it took
|
||||
|
||||
@@ -645,7 +645,7 @@ void TSegment::plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3
|
||||
Across = glm::normalize( Across );
|
||||
}
|
||||
|
||||
bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, float const Lift, std::vector<glm::dvec2> const &Inner, gfx::userdata_array *Userdata, std::vector<glm::dvec2> const &Ramp, double const Rampwidth, bool const Walls, float const Wallu, glm::dvec2 const &Bends ) const {
|
||||
bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, float const Lift, std::vector<glm::dvec2> const &Inner, gfx::userdata_array *Userdata, std::vector<glm::dvec2> const &Ramp, double const Rampwidth, bool const Walls, float const Wallu, glm::dvec2 const &Bends, std::vector<glm::dvec2> const &Drops, glm::dvec2 const &Head ) const {
|
||||
|
||||
if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() )
|
||||
return false;
|
||||
@@ -667,7 +667,10 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
for( auto distance { std::ceil( From / step ) * step }; distance < To; distance += step ) {
|
||||
stations.push_back( distance );
|
||||
}
|
||||
for( auto const *knots : { &Offsets, &Extents, &Inner } ) {
|
||||
for( auto distance { std::ceil( From / 0.5 ) * 0.5 }; distance < std::min( To, m_dense ); distance += 0.5 ) {
|
||||
stations.push_back( distance );
|
||||
}
|
||||
for( auto const *knots : { &Offsets, &Extents, &Inner, &Drops } ) {
|
||||
for( auto const &knot : *knots ) {
|
||||
if( knot.x > From && knot.x < To ) {
|
||||
stations.push_back( knot.x );
|
||||
@@ -733,6 +736,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
innerwall = glm::vec3{ glm::normalize( tangent * slope( Inner, -1e6 ) - outward ) };
|
||||
}
|
||||
auto const worn { sample( Ramp, Distance, -1e6 ) };
|
||||
auto const drop { sample( Drops, Distance, 0.0 ) };
|
||||
auto const bend { Distance <= From ? Bends.x : Distance >= To ? Bends.y : 0.0 };
|
||||
auto along { glm::vec3 { 0.f } };
|
||||
if( bend != 0.0 ) {
|
||||
@@ -823,6 +827,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
||||
wall = ( side > 0 ? outerwall : innerwall );
|
||||
}
|
||||
}
|
||||
if( drop > 0.0 && shape.position.y > Head.x && Head.y > Head.x ) {
|
||||
shape.position.y -= static_cast<float>( drop * ( shape.position.y - Head.x ) / ( Head.y - Head.x ) );
|
||||
}
|
||||
auto pt { parallel * static_cast<float>( Side * ( Halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) };
|
||||
pt.y += shape.position.y + Lift;
|
||||
auto norm { shape.normal.x * parallel };
|
||||
|
||||
@@ -75,6 +75,7 @@ class TSegment
|
||||
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;
|
||||
@@ -156,13 +157,16 @@ public:
|
||||
RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, bool const Transition, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, std::pair<float, float> fOffsetX = {0.f, 0.f}, glm::vec3 **p = nullptr, bool bRender = true );
|
||||
// the profile lofted from distance From to To, moved outwards by Offsets and cut on its outer side to Extents beyond the gauge line, both as (distance, value) knots
|
||||
bool
|
||||
RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents = {}, float const Lift = 0.f, std::vector<glm::dvec2> const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector<glm::dvec2> const &Ramp = {}, double const Rampwidth = 0.0, bool const Walls = false, float const Wallu = -1.f, glm::dvec2 const &Bends = glm::dvec2 { 0.0 } ) const;
|
||||
RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents = {}, float const Lift = 0.f, std::vector<glm::dvec2> const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector<glm::dvec2> const &Ramp = {}, double const Rampwidth = 0.0, bool const Walls = false, float const Wallu = -1.f, glm::dvec2 const &Bends = glm::dvec2 { 0.0 }, std::vector<glm::dvec2> const &Drops = {}, glm::dvec2 const &Head = glm::dvec2 { 0.0 } ) const;
|
||||
// point of the path and the unit vector across it towards the right rail profile, at a distance from Point1
|
||||
void
|
||||
plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const;
|
||||
void
|
||||
set_joint( int const End, std::optional<std::pair<glm::dvec3, glm::dvec3>> const &Joint ) {
|
||||
m_joints[ End ] = Joint; }
|
||||
void
|
||||
set_dense( double const Distance ) {
|
||||
m_dense = Distance; }
|
||||
/*
|
||||
void
|
||||
Render();
|
||||
|
||||
@@ -1588,7 +1588,9 @@ 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 { track_rails::blade_steps( *this ) };
|
||||
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 ) ) };
|
||||
@@ -1598,7 +1600,12 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
auto const fixed_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
|
||||
if( false == lay_rail( Path, Profile, Side, Texturelength, track_rails::blade_length( *this, Path ), false, false, Geometry, Cuts ) ) {
|
||||
gfx::vertex_array vertices;
|
||||
SwitchExtension->Segments[ Path ]->RenderLoft( vertices, m_origin, Profile, false, Texturelength, 1.0, bladelength );
|
||||
if( auto const &shape { blade( Path ) }; shape.valid ) {
|
||||
SwitchExtension->Segments[ Path ]->RenderLoftSpan( vertices, m_origin, Profile, Side, 0.5f * std::abs( fTrackWidth ), Texturelength, shape.heel, SwitchExtension->Segments[ Path ]->GetLength(), {} );
|
||||
}
|
||||
else {
|
||||
SwitchExtension->Segments[ Path ]->RenderLoft( vertices, m_origin, Profile, false, Texturelength, 1.0, track_rails::blade_steps( *this, Path ) );
|
||||
}
|
||||
Geometry.emplace_back( GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
|
||||
}
|
||||
};
|
||||
@@ -2141,8 +2148,19 @@ int TTrack::blade_side( int const Path ) const {
|
||||
|
||||
void TTrack::loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::vertex_array const &Blade, float const Texturelength, gfx::vertex_array &Vertices ) const {
|
||||
|
||||
auto const offset { blade_side( Path ) * ( Path == 0 ? SwitchExtension->fOffset2 : fMaxOffset - SwitchExtension->fOffset1 ) };
|
||||
auto const bladelength { track_rails::blade_steps( *this ) };
|
||||
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 { track_rails::blade_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() };
|
||||
SwitchExtension->Segments[ Path ]->RenderLoftSpan( Vertices, m_origin, Rail, blade_side( Path ), 0.5f * std::abs( fTrackWidth ), Texturelength, shape.tip, shape.heel, offsets, shape.extents, 0.f, {}, nullptr, {}, 0.0, true, -1.f, glm::dvec2 { 0.0 }, shape.drops, { 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 } );
|
||||
|
||||
@@ -308,6 +308,21 @@ public:
|
||||
double angle { 0.0 };
|
||||
};
|
||||
switch_frog frog() const;
|
||||
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;
|
||||
};
|
||||
switch_blade const &blade( int const Path ) const;
|
||||
// where a rail of this track crosses a rail of another one, found from their gauge lines
|
||||
struct rail_crossing {
|
||||
TTrack *track { nullptr };
|
||||
|
||||
@@ -147,6 +147,7 @@ struct rails_memo {
|
||||
std::optional<std::vector<TTrack::rail_crossing>> own;
|
||||
std::optional<std::vector<TTrack::rail_crossing>> joined;
|
||||
std::optional<TTrack::check_rail_set> checks;
|
||||
std::optional<TTrack::switch_blade> blades[ 2 ];
|
||||
};
|
||||
std::unordered_map<TTrack const *, rails_memo> memos;
|
||||
unsigned int memostamp { 0 };
|
||||
@@ -192,15 +193,18 @@ bool cut_rails( TTrack const &Track ) {
|
||||
Track.eType == tt_Switch && Track.SwitchExtension && Track.SwitchExtension->m_frogs && Track.SwitchExtension->Segments[ 0 ] && Track.SwitchExtension->Segments[ 1 ] );
|
||||
}
|
||||
|
||||
int blade_steps( TTrack const &Track ) {
|
||||
int blade_steps( TTrack const &Track, int const Path ) {
|
||||
|
||||
return static_cast<int>( std::ceil( Track.SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) );
|
||||
return static_cast<int>( std::ceil( Track.SwitchExtension->Segments[ Path ]->RaSegCount() * 0.65 ) );
|
||||
}
|
||||
|
||||
double blade_length( TTrack const &Track, int const Path ) {
|
||||
|
||||
if( auto const &shape { Track.blade( Path ) }; shape.valid ) {
|
||||
return shape.heel;
|
||||
}
|
||||
auto const &segment { *Track.SwitchExtension->Segments[ Path ] };
|
||||
return segment.GetLength() * blade_steps( Track ) / segment.RaSegCount();
|
||||
return segment.GetLength() * blade_steps( Track, Path ) / segment.RaSegCount();
|
||||
}
|
||||
|
||||
material_handle cut_material( material_handle const Material ) {
|
||||
@@ -1212,6 +1216,129 @@ std::vector<glm::dvec2> nose_extents( TTrack const &Track, int const Path, int c
|
||||
|
||||
} // namespace
|
||||
|
||||
TTrack::switch_blade const &TTrack::blade( int const Path ) const {
|
||||
|
||||
auto &cached { memo_of( *this ).blades[ Path ] };
|
||||
if( cached.has_value() ) {
|
||||
return *cached;
|
||||
}
|
||||
cached = switch_blade {};
|
||||
auto &result { *cached };
|
||||
if( eType != tt_Switch || false == cut_rails( *this ) || m_profiles.empty() ) {
|
||||
return result;
|
||||
}
|
||||
auto const side { blade_side( Path ) };
|
||||
auto const &ours { rail_line( *this, Path, side > 0, false ) };
|
||||
auto const &theirs { rail_line( *this, 1 - Path, side > 0, false ) };
|
||||
auto const size { rail_dimensions() };
|
||||
auto const length { std::min( SwitchExtension->Segments[ Path ]->GetLength(), SwitchExtension->Segments[ 1 - Path ]->GetLength() ) };
|
||||
auto const count { std::max( 2, static_cast<int>( std::ceil( length / 0.1 ) ) ) };
|
||||
auto &gauge { result.gauge };
|
||||
glm::dvec3 point, tangent;
|
||||
ours.at( 0.0, point, tangent );
|
||||
auto otherat { nearest_distance( theirs, point ) };
|
||||
for( int i = 0; i <= count; ++i ) {
|
||||
auto const distance { length * i / count };
|
||||
ours.at( distance, point, tangent );
|
||||
lateral( theirs, side, point, otherat );
|
||||
glm::dvec3 line;
|
||||
theirs.at( otherat, line, tangent );
|
||||
auto at { distance };
|
||||
auto const gap { lateral( ours, side, line, at ) };
|
||||
gauge.emplace_back( at, gap );
|
||||
}
|
||||
std::sort( gauge.begin(), gauge.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } );
|
||||
if( std::abs( gauge.front().y ) > 0.002 ) {
|
||||
return result;
|
||||
}
|
||||
auto const reach = [&]( double const Width ) {
|
||||
for( std::size_t i = 1; i < gauge.size(); ++i ) {
|
||||
if( gauge[ i ].y >= Width ) {
|
||||
auto const &a { gauge[ i - 1 ] };
|
||||
auto const &b { gauge[ i ] };
|
||||
return glm::mix( a.x, b.x, std::clamp( ( Width - a.y ) / std::max( b.y - a.y, 1e-9 ), 0.0, 1.0 ) );
|
||||
}
|
||||
}
|
||||
return -1.0; };
|
||||
result.tip = reach( blade_point );
|
||||
result.full = reach( size.head + blade_gap );
|
||||
auto const clear { reach( size.head + blade_clearance ) };
|
||||
auto const foot { reach( size.high + blade_gap ) };
|
||||
auto const limit { 0.9 * length };
|
||||
if( result.tip < 0.0 || result.full < 0.0 || clear < 0.0 || clear > limit ) {
|
||||
return result;
|
||||
}
|
||||
auto const bend = [&]( double const Distance, double const Heel ) {
|
||||
auto const u { std::clamp( ( Heel - Distance ) / std::max( Heel - result.tip, 1e-6 ), 0.0, 1.0 ) };
|
||||
return ( result.rigid ? u : u * u * ( 3.0 - u ) * 0.5 ); };
|
||||
auto const opening = [&]( double const Heel, double &At ) {
|
||||
auto least { 1e9 };
|
||||
for( auto const &sample : gauge ) {
|
||||
if( sample.x < result.tip || sample.x > Heel ) {
|
||||
continue;
|
||||
}
|
||||
auto const gap { std::max( 0.0, sample.y - size.head ) + blade_throw * bend( sample.x, Heel ) };
|
||||
if( gap < least ) {
|
||||
least = gap;
|
||||
At = sample.x;
|
||||
}
|
||||
}
|
||||
return least; };
|
||||
auto at { 0.0 };
|
||||
if( opening( limit, at ) < blade_clearance ) {
|
||||
result.rigid = true;
|
||||
if( opening( limit, at ) < blade_clearance ) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
auto low { clear };
|
||||
auto high { limit };
|
||||
for( int step = 0; step < 40; ++step ) {
|
||||
auto const middle { 0.5 * ( low + high ) };
|
||||
( opening( middle, at ) >= blade_clearance ? high : low ) = middle;
|
||||
}
|
||||
result.heel = high;
|
||||
result.opening = opening( result.heel, result.openingat );
|
||||
for( std::size_t i = 1; i < gauge.size() && gauge[ i ].x <= result.heel; ++i ) {
|
||||
if( gauge[ i ].y < gauge[ i - 1 ].y - 1e-4 ) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
auto const &profile { track_rail_profile( m_profile1.second ) };
|
||||
auto const half { profile.size() / 2 };
|
||||
auto bladetop { -1e9 };
|
||||
for( auto i { half }; i < profile.size(); ++i ) {
|
||||
bladetop = std::max( bladetop, static_cast<double>( profile[ i ].position.y ) );
|
||||
}
|
||||
result.drop = ( half > 0 ? std::max( 0.0, size.crest - bladetop ) : 0.0 );
|
||||
auto const level = [&]( double const Distance ) {
|
||||
for( std::size_t i = 1; i < gauge.size(); ++i ) {
|
||||
if( gauge[ i ].x >= Distance ) {
|
||||
auto const &a { gauge[ i - 1 ] };
|
||||
auto const &b { gauge[ i ] };
|
||||
return glm::mix( a.y, b.y, std::clamp( ( Distance - a.x ) / std::max( b.x - a.x, 1e-9 ), 0.0, 1.0 ) );
|
||||
}
|
||||
}
|
||||
return gauge.back().y; };
|
||||
auto const lowered { reach( blade_lowered + blade_gap ) };
|
||||
auto const cutto { std::min( result.heel, foot >= 0.0 ? foot + blade_spacing : result.heel ) };
|
||||
for( auto distance { result.tip }; distance < cutto + 0.5 * blade_spacing; distance += blade_spacing ) {
|
||||
auto const at { std::min( distance, cutto ) };
|
||||
auto const width { level( at ) - blade_gap };
|
||||
result.extents.emplace_back( at, width );
|
||||
if( result.drop > 0.0 && ( lowered < 0.0 || at <= lowered + blade_spacing ) ) {
|
||||
result.drops.emplace_back( at, result.drop * std::clamp( 1.0 - width / blade_lowered, 0.0, 1.0 ) );
|
||||
}
|
||||
}
|
||||
auto const steps { std::max( 1, static_cast<int>( std::ceil( ( result.heel - result.tip ) / blade_spacing ) ) ) };
|
||||
for( int i = 0; i <= steps; ++i ) {
|
||||
auto const distance { glm::mix( result.tip, result.heel, static_cast<double>( i ) / steps ) };
|
||||
result.bends.emplace_back( distance, bend( distance, result.heel ) );
|
||||
}
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
material_handle TTrack::rail_material( int const Path ) const {
|
||||
|
||||
return ( Path == 1 && eType == tt_Switch ? m_material2 : m_material1 );
|
||||
|
||||
@@ -22,6 +22,12 @@ double constexpr rolled_band { wheel_width - ( 1.435 - back_to_back ) / 2.0 };
|
||||
double constexpr nose_tip { 0.012 };
|
||||
double constexpr middle_angle { 0.35 };
|
||||
double constexpr wide_angle { 0.785 };
|
||||
double constexpr blade_throw { 0.160 };
|
||||
double constexpr blade_clearance { 0.058 };
|
||||
double constexpr blade_point { 0.003 };
|
||||
double constexpr blade_gap { 0.0005 };
|
||||
double constexpr blade_lowered { 0.050 };
|
||||
double constexpr blade_spacing { 0.5 };
|
||||
|
||||
void forget();
|
||||
// how far a wing rail reaches past the crossing point of the rails
|
||||
@@ -29,7 +35,7 @@ double wing_reach( double const Slope );
|
||||
double check_length( double const Slope );
|
||||
// the rails of the track are cut where they cross other rails: plain tracks, and switches whose frog is made from the rails
|
||||
bool cut_rails( TTrack const &Track );
|
||||
int blade_steps( TTrack const &Track );
|
||||
int blade_steps( TTrack const &Track, int const Path );
|
||||
double blade_length( TTrack const &Track, int const Path );
|
||||
material_handle cut_material( material_handle const Material );
|
||||
float tread_brightness( material_handle const Material, glm::vec2 const &Band );
|
||||
|
||||
Reference in New Issue
Block a user