diff --git a/CMakeLists.txt b/CMakeLists.txt index 57531796..97c50b4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,12 @@ set(SOURCES "model/Texture.cpp" "utilities/Timer.cpp" "world/Track.cpp" -"world/TrackRails.cpp" +"world/GaugeLine.cpp" +"world/RailCrossings.cpp" +"world/RailMaterials.cpp" +"world/RailProfile.cpp" +"world/RailPlan.cpp" +"world/RailLayer.cpp" "world/Road.cpp" "world/Sweep.cpp" "world/RoadPoint.cpp" diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index 9215828a..7ae14bb0 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -2881,7 +2881,17 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm auto const mix = [](std::uint64_t &Hash, float const Value) { Hash = (Hash ^ static_cast(static_cast(std::llround(Value * 1e4)))) * 1099511628211ull; }; + auto const exact = [](std::uint64_t &Hash, float const Value) { + std::uint32_t bits; + std::memcpy(&bits, &Value, sizeof(bits)); + Hash = (Hash ^ bits) * 1099511628211ull; + }; + auto const named = [](std::uint64_t &Hash, material_handle const Material) { + for (auto const letter : (Material != null_handle ? GfxRenderer->Material(Material)->GetName() : std::string{}) + '|') + Hash = (Hash ^ static_cast(letter)) * 1099511628211ull; + }; std::uint64_t total{1469598103934665603ull}; + std::uint64_t exacttotal{1469598103934665603ull}; std::size_t vertices{0}; int tracks{0}; for (auto *track : simulation::Paths.sequence()) @@ -2891,6 +2901,7 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm if (false == name.empty() && track->name() != name) continue; std::uint64_t hash{1469598103934665603ull}; + std::uint64_t exacthash{1469598103934665603ull}; std::size_t count{0}; int chunks{0}; std::vector sequences{&track->Geometry1, &track->Geometry2, &track->GeometryCut1, &track->GeometryCut2}; @@ -2898,6 +2909,8 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm { sequences.push_back(&tread.geometry); sequences.push_back(&tread.cuts); + named(exacthash, tread.material); + named(exacthash, tread.matte); } for (auto const *sequence : sequences) { @@ -2910,20 +2923,32 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm { mix(hash, vertex.position[i]); mix(hash, vertex.normal[i]); + exact(exacthash, vertex.position[i]); + exact(exacthash, vertex.normal[i]); } mix(hash, vertex.texture.x); mix(hash, vertex.texture.y); + exact(exacthash, vertex.texture.x); + exact(exacthash, vertex.texture.y); ++count; } + for (auto const &userdata : GfxRenderer->UserData(geometry)) + { + for (int i = 0; i < 4; ++i) + exact(exacthash, userdata.data[i]); + } + exact(exacthash, -2.f); } mix(hash, -1.f); + exact(exacthash, -1.f); } - WriteLog(format("SELFTEST geom %s: chunks %d, vertices %zu, hash %016llx", track->name().c_str(), chunks, count, static_cast(hash))); + WriteLog(format("SELFTEST geom %s: chunks %d, vertices %zu, hash %016llx, exact %016llx", track->name().c_str(), chunks, count, static_cast(hash), static_cast(exacthash))); total = (total ^ hash) * 1099511628211ull; + exacttotal = (exacttotal ^ exacthash) * 1099511628211ull; vertices += count; ++tracks; } - WriteLog(format("SELFTEST geom total: tracks %d, vertices %zu, hash %016llx", tracks, vertices, static_cast(total))); + WriteLog(format("SELFTEST geom total: tracks %d, vertices %zu, hash %016llx, exact %016llx", tracks, vertices, static_cast(total), static_cast(exacttotal))); return selftest_result::handled; } if (command == "frogkey") @@ -3121,7 +3146,7 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm WriteLog(format("SELFTEST crossing %s path %d side %d at %.3f m: %s with %s, 1:%.2f, into %.3f, facing %.3f, nose %d", track->name().c_str(), crossing.path, crossing.side, crossing.at, kind, crossing.track->name().c_str(), 1.0 / std::tan(crossing.angle), crossing.into, crossing.facing, crossing.nose ? 1 : 0)); } - auto const gauge = [](TTrack *Track, TTrack::rail_crossing const &Crossing) { + auto const gauge = [](TTrack *Track, track_rails::rail_crossing const &Crossing) { auto const &segment{Track->eType == tt_Switch ? *Track->SwitchExtension->Segments[Crossing.path] : *Track->Segment}; glm::dvec3 point, across; segment.plan_frame(Crossing.at, point, across); @@ -3169,7 +3194,7 @@ editor_mode::selftest_result editor_mode::selftest_rails(std::string const &comm // throw <0|1> sets every switch to the path and moves its blades there at once // crossings [name] logs where rails of tracks cross rails of other tracks and of what kind (acute, obtuse, middle acute or obtuse, wide), checks that each acute crossing has one partner and one nose // frogkey name [on|off] sets or clears the frogs key of a switch (one undo step); logs the key, whether it is written, the frog and the crossings -// geomhash [name] logs the number of vertices and a hash of the drawn geometry of each rail track (rails, trackbed, cut faces), to compare builds +// geomhash [name] logs the number of vertices and a hash of the drawn geometry of each rail track (rails, trackbed, cut faces), to compare builds; exact: the bits of every value, userdata and tread materials too // sweep =... model, params (separated by commas), lateral, height, side (origin, near, far, // centre), top (origin, bottom, top, edge), mode (bend, repeat), step, // length (original, all, or metres), from, to, widen (0, 1), face (0, 1), tilt (0, 1) diff --git a/world/GaugeLine.cpp b/world/GaugeLine.cpp new file mode 100644 index 00000000..27740f06 --- /dev/null +++ b/world/GaugeLine.cpp @@ -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( 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::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 diff --git a/world/GaugeLine.h b/world/GaugeLine.h new file mode 100644 index 00000000..8f6f72ee --- /dev/null +++ b/world/GaugeLine.h @@ -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 points; + std::vector 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( -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 diff --git a/world/Knots.h b/world/Knots.h new file mode 100644 index 00000000..fa78aad1 --- /dev/null +++ b/world/Knots.h @@ -0,0 +1,110 @@ +/* +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 +#include +#include + +namespace knots { + +using line = std::vector; + +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; + } + for( std::size_t i = 1; i < Line.size(); ++i ) { + if( Position <= Line[ i ].x ) { + return glm::mix( Line[ i - 1 ].y, Line[ i ].y, ( Position - Line[ i - 1 ].x ) / std::max( Line[ i ].x - Line[ i - 1 ].x, 1e-6 ) ); + } + } + return Line.back().y; +} + +inline line shifted( line Line, double const Value ) { + + for( auto &knot : Line ) { + knot.y += Value; + } + return Line; +} + +inline std::vector stations( line const &A, line const &B ) { + + std::vector 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 meetings( line const &A, line const &B ) { + + std::vector 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; +} + +} // namespace knots diff --git a/world/RailCrossings.cpp b/world/RailCrossings.cpp new file mode 100644 index 00000000..001a5b60 --- /dev/null +++ b/world/RailCrossings.cpp @@ -0,0 +1,813 @@ +/* +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 "simulation/simulation.h" +#include "scene/scene.h" + +using namespace track_rails; + +namespace { + +bool joined( TTrack const &Track, TTrack const &Other ) { + + auto const next = []( TTrack const &From, TTrack const &To ) { + if( From.trPrev == &To || From.trNext == &To ) { + return true; + } + if( From.eType == tt_Switch && From.SwitchExtension ) { + for( int i = 0; i < 2; ++i ) { + if( From.SwitchExtension->pPrevs[ i ] == &To || From.SwitchExtension->pNexts[ i ] == &To ) { + return true; + } + } + } + return false; }; + return next( Track, Other ) || next( Other, Track ); +} + +struct rails_memo { + std::optional lines[ 2 ][ 2 ][ 2 ]; // path, right side, from the blades + std::optional> own; + std::optional> joined; + std::optional checks; + std::optional blades[ 2 ]; +}; +std::unordered_map memos; +unsigned int memostamp { 0 }; +unsigned int stamp { 1 }; + +rails_memo &memo_of( TTrack const &Track ) { + + if( memostamp != stamp ) { + memos.clear(); + memostamp = stamp; + } + return memos[ &Track ]; +} + +} // namespace + +namespace track_rails { + +void forget() { + + ++stamp; +} + + +double wing_reach( double const Slope ) { + + return 0.15 / Slope + 0.3; +} + +double check_length( double const Slope ) { + + return std::clamp( 0.5 / Slope, 3.6, 9.0 ); +} + +// 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 ) { + + return ( Track.iCategoryFlag & 15 ) == 1 + && Track.m_material1 != null_handle + && false == Track.m_editorremoved + && ( Track.eType == tt_Normal ? Track.Segment != nullptr : + 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 const Path ) { + + return static_cast( 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, Path ) / segment.RaSegCount(); +} + +TSegment const &path_segment( TTrack const &Track, int const Path ) { + + return ( Track.eType == tt_Switch ? *Track.SwitchExtension->Segments[ Path ] : *Track.Segment ); +} + +double knuckle_bend( double const Into ) { + + return std::abs( Into ) / ( 1.0 + std::sqrt( std::max( 0.0, 1.0 - Into * Into ) ) ); +} + +// a check rail starting right beside the end of a wing rail running along the same rail, which the wing rail goes on in place of; the far end of it +bool replaced_check( std::vector const &Checks, double const Wingend, double const Direction, double &Far ) { + + for( auto const &check : Checks ) { + auto const nearend { Direction > 0.0 ? check.from : check.to }; + auto const farend { Direction > 0.0 ? check.to : check.from }; + auto const gap { ( nearend - Wingend ) * Direction }; + if( gap > -0.5 && gap < 1.0 && ( farend - Wingend ) * Direction > 0.5 ) { + Far = farend; + return true; + } + } + return false; +} + +bool replaced_by_wing( check_rail const &Check, knots::line const &Wingends ) { + + auto farend { 0.0 }; + return std::any_of( Wingends.begin(), Wingends.end(), [&]( glm::dvec2 const &Wing ) { return replaced_check( { Check }, Wing.x, Wing.y, farend ); } ); +} + +// gauge line of a rail of a track, along a path of a switch from past its blades when Blades +gauge_line const &rail_line( TTrack const &Track, int const Path, int const Side, bool const Blades ) { + + auto const blades { Blades && Track.eType == tt_Switch }; + auto &line { memo_of( Track ).lines[ Path ][ Side ][ blades ] }; + if( false == line.has_value() ) { + line = make_gauge_line( path_segment( Track, Path ), ( Side ? 1.0 : -1.0 ) * ( 0.5 * std::abs( Track.fTrackWidth ) ), blades ? blade_length( Track, Path ) : 0.0 ); + } + return *line; +} + +track_rails::check_rail_set const &rail_checks( TTrack &Track ) { + + auto &checks { memo_of( Track ).checks }; + if( false == checks.has_value() ) { + checks = Track.check_rails( Track.rail_crossings() ); + } + return *checks; +} + +glm::dvec3 rail_point( TTrack const &Track, int const Path, int const Side, double const Distance, double const Lateral ) { + + auto const line { make_gauge_line( path_segment( Track, Path ), Side * 0.5 * std::abs( Track.fTrackWidth ), 0.0 ) }; + glm::dvec3 point, tangent; + line.at( Distance, point, tangent ); + return point - line.normal( Distance, Side ) * Lateral; +} + +void rail_offset( TTrack const &Track, int const Path, int const Side, glm::dvec3 const &Point, double &Distance, double &Lateral ) { + + auto const line { make_gauge_line( path_segment( Track, Path ), Side * 0.5 * std::abs( Track.fTrackWidth ), 0.0 ) }; + Distance = nearest_distance( line, Point ); + Lateral = lateral( line, Side, Point, Distance ); +} + +int path_count( TTrack const &Track ) { + + return ( Track.eType == tt_Switch ? 2 : 1 ); +} + +TTrack *path_neighbour( TTrack const &Track, int const Path, int const End ) { + + if( Track.eType == tt_Switch ) { + return ( End ? Track.SwitchExtension->pNexts[ Path ] : Track.SwitchExtension->pPrevs[ Path ] ); + } + return ( End ? Track.trNext : Track.trPrev ); +} + +} // namespace track_rails + +namespace { + +struct rail_run { + TTrack *track { nullptr }; + int path { 0 }; + int side { 0 }; + gauge_line const *line { nullptr }; + double from { 0.0 }; + double length { 0.0 }; +}; + +std::vector nearby_rails( TTrack &Track ) { + + std::vector others; + auto const sections { simulation::Region->sections( Track.location(), static_cast( 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() + 1.0 ) { + continue; + } + for( auto *other : cell.m_directories.paths ) { + if( other == nullptr || other == &Track || false == cut_rails( *other ) ) { + continue; + } + if( glm::length( other->location() - Track.location() ) > other->radius() + Track.radius() + 1.0 ) { + continue; + } + if( std::find( others.begin(), others.end(), other ) == others.end() ) { + others.push_back( other ); + } + } + } + } + return others; +} + +double line_bend( gauge_line const &Line, double const At ) { + + glm::dvec3 point, before, after; + Line.at( At - 1.0, point, before ); + Line.at( At + 1.0, point, after ); + return std::abs( cross( plan( before ), plan( after ) ) ); +} + +double heading( glm::dvec2 const &Tangent ) { + + auto const angle { std::atan2( Tangent.y, Tangent.x ) }; + return ( angle < 0.0 ? angle + M_PI : angle ); +} + +bool overlap( gauge_line const &A, gauge_line const &B ) { + + return A.low.x <= B.high.x && B.low.x <= A.high.x && A.low.y <= B.high.y && B.low.y <= A.high.y; +} + +bool same_place( rail_crossing const &Crossing, int const Path, int const Side, double const At ) { + + return Crossing.path == Path && Crossing.side == Side && std::abs( Crossing.at - At ) < 0.05; +} + +void refine_meeting( gauge_line const &Ours, gauge_line const &Theirs, double &S, double &T ) { + + glm::dvec3 p, tp, q, tq; + for( int step = 0; step < 6; ++step ) { + Ours.at( S, p, tp ); + Theirs.at( T, q, tq ); + auto const d { cross( plan( tp ), plan( tq ) ) }; + if( std::abs( d ) < 1e-9 ) { + break; + } + auto const offset { plan( q ) - plan( p ) }; + S += cross( offset, plan( tq ) ) / d; + T += cross( offset, plan( tp ) ) / d; + } +} + +rail_crossing make_crossing( rail_run const &Ours, rail_run const &Theirs, double const S, double const T, glm::dvec2 const &Tangent, glm::dvec2 const &Othertangent, double const Angle ) { + + auto const normal { glm::normalize( plan( Ours.line->normal( S, Ours.side ) ) ) }; + auto const othernormal { glm::normalize( plan( Theirs.line->normal( T, Theirs.side ) ) ) }; + rail_crossing crossing; + crossing.track = Theirs.track; + crossing.path = Ours.path; + crossing.side = Ours.side; + crossing.at = S; + crossing.angle = Angle; + crossing.into = glm::dot( Tangent, othernormal ); + crossing.facing = glm::dot( normal, othernormal ); + auto const ourbend { line_bend( *Ours.line, S ) }; + auto const theirbend { line_bend( *Theirs.line, T ) }; + crossing.nose = ( std::abs( ourbend - theirbend ) > 1e-4 ? ourbend < theirbend : heading( Tangent ) < heading( Othertangent ) ); + crossing.otherpath = Theirs.path; + crossing.otherside = Theirs.side; + crossing.otherat = T; + crossing.aligned = glm::dot( Tangent, Othertangent ) > 0.0; + return crossing; +} + +void meet_rails( rail_run const &Ours, rail_run const &Theirs, std::vector &Result ) { + + auto const &line { *Ours.line }; + auto const &theirs { *Theirs.line }; + if( false == overlap( line, theirs ) ) { + return; + } + for( std::size_t i = 1; i < line.points.size(); ++i ) { + auto const a0 { plan( line.points[ i - 1 ] ) }; + auto const a { plan( line.points[ i ] ) - a0 }; + auto const alow { glm::min( a0, a0 + a ) - 0.02 * glm::length( a ) - 1e-6 }; + auto const ahigh { glm::max( a0, a0 + a ) + 0.02 * glm::length( a ) + 1e-6 }; + if( alow.x > theirs.high.x + 1.0 || theirs.low.x - 1.0 > ahigh.x || alow.y > theirs.high.y + 1.0 || theirs.low.y - 1.0 > ahigh.y ) { + continue; + } + for( std::size_t j = 1; j < theirs.points.size(); ++j ) { + auto const b0 { plan( theirs.points[ j - 1 ] ) }; + auto const b { plan( theirs.points[ j ] ) - b0 }; + auto const reach { 0.02 * glm::length( b ) }; + if( alow.x > std::max( b0.x, b0.x + b.x ) + reach || std::min( b0.x, b0.x + b.x ) - reach > ahigh.x + || alow.y > std::max( b0.y, b0.y + b.y ) + reach || std::min( b0.y, b0.y + b.y ) - reach > ahigh.y ) { + continue; + } + double u, v; + if( false == meet( a0, a, b0, b, 1e-12, u, v ) ) { + continue; + } + if( u < -0.01 || u > 1.01 || v < -0.01 || v > 1.01 ) { + continue; + } + auto s { glm::mix( line.distances[ i - 1 ], line.distances[ i ], u ) }; + auto t { glm::mix( theirs.distances[ j - 1 ], theirs.distances[ j ], v ) }; + refine_meeting( line, theirs, s, t ); + glm::dvec3 p, tp, q, tq; + line.at( s, p, tp ); + theirs.at( t, q, tq ); + if( glm::length( plan( p ) - plan( q ) ) > 0.01 + || s < 0.0 || s > Ours.length + || t < Theirs.from || t > Theirs.length ) { + continue; + } + if( std::abs( p.y - q.y ) > 0.5 ) { + continue; + } + if( ( s < 0.5 || s > Ours.length - 0.5 ) && ( t < 0.5 || t > Theirs.length - 0.5 ) && joined( *Ours.track, *Theirs.track ) ) { + continue; + } + auto const tangent { glm::normalize( plan( tp ) ) }; + auto const othertangent { glm::normalize( plan( tq ) ) }; + auto const angle { std::acos( std::min( 1.0, std::abs( glm::dot( tangent, othertangent ) ) ) ) }; + if( angle < 0.03 ) { + continue; + } + if( std::any_of( Result.begin(), Result.end(), [&]( rail_crossing const &Crossing ) { return same_place( Crossing, Ours.path, Ours.side, s ); } ) ) { + continue; + } + Result.push_back( make_crossing( Ours, Theirs, s, t, tangent, othertangent, angle ) ); + } + } +} + +std::vector own_crossings( TTrack &Track ) { + + std::vector result; + for( auto *other : nearby_rails( Track ) ) { + for( int otherpath = 0; otherpath < path_count( *other ); ++otherpath ) { + rail_run theirs { other, otherpath }; + theirs.from = ( other->eType == tt_Switch ? blade_length( *other, otherpath ) : 0.0 ); + theirs.length = path_segment( *other, otherpath ).GetLength(); + for( int otherside = 0; otherside < 2; ++otherside ) { + theirs.side = ( otherside ? 1 : -1 ); + theirs.line = &rail_line( *other, otherpath, otherside, true ); + for( int path = 0; path < path_count( Track ); ++path ) { + auto const length { path_segment( Track, path ).GetLength() }; + for( int side = 0; side < 2; ++side ) { + meet_rails( { &Track, path, side ? 1 : -1, &rail_line( Track, path, side, false ), 0.0, length }, theirs, result ); + } + } + } + } + } + return result; +} + +// crossings of the tracks going on from the ends, near enough for their check rails and wings to reach this one +void add_neighbour_crossings( TTrack &Track, std::vector &Result ) { + + auto const extension { 10.0 }; + auto const owncount { Result.size() }; + for( int path = 0; path < path_count( Track ); ++path ) { + auto const &segment { path_segment( Track, path ) }; + auto const length { segment.GetLength() }; + for( int end = 0; end < 2; ++end ) { + auto *neighbour { path_neighbour( Track, path, end ) }; + if( neighbour == nullptr || neighbour == &Track || false == cut_rails( *neighbour ) ) { + continue; + } + auto const joint { end ? segment.FastGetPoint_1() : segment.FastGetPoint_0() }; + auto const theirs { neighbour->rail_crossings( false ) }; + for( int neighbourpath = 0; neighbourpath < path_count( *neighbour ); ++neighbourpath ) { + auto const &neighboursegment { path_segment( *neighbour, neighbourpath ) }; + auto const neighbourlength { neighboursegment.GetLength() }; + auto const tostart { glm::length( neighboursegment.FastGetPoint_0() - joint ) }; + auto const toend { glm::length( neighboursegment.FastGetPoint_1() - joint ) }; + if( std::min( tostart, toend ) > 0.05 ) { + continue; + } + auto const atstart { tostart <= toend }; + auto const same { atstart == ( end == 1 ) }; + for( auto const &crossing : theirs ) { + if( crossing.path != neighbourpath || crossing.track == &Track ) { + continue; + } + auto const along { atstart ? crossing.at : neighbourlength - crossing.at }; + if( along > extension ) { + continue; + } + auto mapped { crossing }; + mapped.path = path; + mapped.at = ( end ? length + along : -along ); + mapped.owner = neighbour; + if( false == same ) { + mapped.side = -crossing.side; + mapped.into = -crossing.into; + mapped.aligned = !crossing.aligned; + } + if( std::none_of( Result.begin(), Result.begin() + owncount, [&]( rail_crossing const &Crossing ) { return same_place( Crossing, mapped.path, mapped.side, mapped.at ); } ) ) { + Result.push_back( mapped ); + } + } + } + } + } +} + +struct planned_check { + double from { 0.0 }; + double to { 0.0 }; + bool flarefrom { true }; + bool flareto { true }; + double shortest { 1.5 }; + double flare { 0.02 }; + double frombend { 0.0 }; + double tobend { 0.0 }; +}; +using planned_check_set = std::array, 2>, 2>; // path, right side + +void add_acute_check( std::vector &Checks, double const At, double const Toward, double const Slope, double const Flare ) { + + auto const half { 0.5 * check_length( Slope ) }; + Checks.push_back( { At + Toward * 0.25 - half, At + Toward * 0.25 + half, true, true, 1.5, Flare } ); +} + +planned_check_set planned_checks( TTrack const &Track, std::vector const &Crossings ) { + + planned_check_set plans; + for( auto const &crossing : Crossings ) { + if( crossing.angle > middle_angle || crossing.track->rail_dimensions().grooved ) { + continue; + } + if( crossing.facing < 0.0 ) { + add_acute_check( plans[ crossing.path ][ crossing.side < 0 ], crossing.at, crossing.into > 0.0 ? 1.0 : -1.0, std::tan( crossing.angle ), 0.02 ); + } + else if( crossing.into > 0.0 ) { + plans[ crossing.path ][ crossing.side > 0 ].push_back( { crossing.at, crossing.at + 1.2, false, true, 0.5, 0.02, -knuckle_bend( crossing.into ), 0.0 } ); + } + else { + plans[ crossing.path ][ crossing.side > 0 ].push_back( { crossing.at - 1.2, crossing.at, true, false, 0.5, 0.02, 0.0, knuckle_bend( crossing.into ) } ); + } + } + if( Track.eType == tt_Switch ) { + auto const frog { Track.frog() }; + if( frog.valid ) { + for( int path = 0; path < 2; ++path ) { + add_acute_check( plans[ path ][ frog.side[ path ] < 0 ], frog.at[ path ], -1.0, std::tan( frog.angle ), 0.04 ); + } + } + } + return plans; +} + +void merge_checks( std::vector &Checks ) { + + std::sort( Checks.begin(), Checks.end(), []( planned_check const &A, planned_check const &B ) { return A.from < B.from; } ); + for( std::size_t i = 1; i < Checks.size(); ++i ) { + if( Checks[ i ].from < Checks[ i - 1 ].to + 2.0 ) { + if( Checks[ i ].to > Checks[ i - 1 ].to ) { + Checks[ i - 1 ].to = Checks[ i ].to; + Checks[ i - 1 ].flareto = Checks[ i ].flareto; + Checks[ i - 1 ].tobend = Checks[ i ].tobend; + } + Checks[ i - 1 ].shortest = std::min( Checks[ i - 1 ].shortest, Checks[ i ].shortest ); + Checks[ i - 1 ].flare = std::max( Checks[ i - 1 ].flare, Checks[ i ].flare ); + Checks.erase( Checks.begin() + i-- ); + } + } +} + +bool blocked_by( std::vector const &Others, glm::dvec2 const &Gauge, glm::dvec2 const &Inward, double const Clearance ) { + + auto blocked { false }; + for( auto const &other : Others ) { + for( std::size_t i = 1; i < other.points.size() && false == blocked; ++i ) { + auto const a { plan( other.points[ i - 1 ] ) }; + auto const edge { plan( other.points[ i ] ) - a }; + double t, u; + if( false == meet( Gauge, Inward, a, edge, 1e-9, t, u ) ) { + continue; + } + blocked = ( u > -1e-6 && u < 1.0 + 1e-6 && t > -0.05 && t < Clearance ); + } + } + return blocked; +} + +// check rails keep clear of the blades and the rails of the other path of a switch running inside the rail, here and in a switch joined at an end, and of the blade tips +knots::line check_blocks( TTrack const &Track, int const Path, int const Side, std::vector const &Checks, double const Clearance ) { + + auto const halfwidth { 0.5 * std::abs( Track.fTrackWidth ) }; + auto const &segment { path_segment( Track, Path ) }; + auto const length { segment.GetLength() }; + knots::line blocks; + auto const start { plan( segment.FastGetPoint_0() ) }; + auto const end { plan( segment.FastGetPoint_1() ) }; + std::vector others; + auto const add_path = [&]( TSegment const &Other ) { + others.push_back( make_gauge_line( Other, -halfwidth, 0.0 ) ); + others.push_back( make_gauge_line( Other, halfwidth, 0.0 ) ); }; + if( Track.eType == tt_Switch ) { + blocks.emplace_back( -1e9, 0.3 ); + add_path( path_segment( Track, 1 - Path ) ); + } + for( auto const *neighbour : { Track.trPrev, Track.trNext } ) { + if( neighbour == nullptr || neighbour == &Track || neighbour->eType != tt_Switch || neighbour->SwitchExtension == nullptr ) { + continue; + } + auto const tip { plan( neighbour->SwitchExtension->Segments[ 0 ]->FastGetPoint_0() ) }; + if( glm::distance( start, tip ) < 0.1 ) { + blocks.emplace_back( -1e9, 0.3 ); + continue; + } + if( glm::distance( end, tip ) < 0.1 ) { + blocks.emplace_back( length - 0.3, 1e9 ); + continue; + } + for( int otherpath = 0; otherpath < 2; ++otherpath ) { + auto const &other { *neighbour->SwitchExtension->Segments[ otherpath ] }; + auto const continues = [&]( glm::dvec2 const &Point ) { + return glm::distance( Point, plan( other.FastGetPoint_0() ) ) < 0.1 || glm::distance( Point, plan( other.FastGetPoint_1() ) ) < 0.1; }; + if( false == continues( start ) && false == continues( end ) ) { + add_path( other ); + } + } + } + if( false == others.empty() ) { + auto const ours { make_gauge_line( segment, Side * halfwidth, 0.0 ) }; + for( auto const &check : Checks ) { + for( auto s { check.from }; s <= check.to; s += 0.1 ) { + glm::dvec3 point, tangent; + ours.at( s, point, tangent ); + if( blocked_by( others, plan( point ), plan( ours.normal( s, Side ) ), Clearance ) ) { + blocks.emplace_back( s - 0.3, s + 0.3 ); + } + } + } + } + knots::sort( blocks ); + return blocks; +} + +void cut_check( planned_check const &Check, knots::line const &Blocks, std::vector &Result ) { + + auto const add = [&]( double const From, double const To ) { + Result.push_back( { + From, To, + ( From > Check.from || Check.flarefrom ) ? Check.flare : 0.0, + ( To < Check.to || Check.flareto ) ? Check.flare : 0.0, + From > Check.from ? 0.0 : Check.frombend, + To < Check.to ? 0.0 : Check.tobend } ); }; + auto from { Check.from }; + for( auto const &block : Blocks ) { + if( block.y <= from || block.x >= Check.to ) { + continue; + } + if( block.x - from >= Check.shortest ) { + add( from, block.x ); + } + from = std::max( from, block.y ); + } + if( Check.to - from >= Check.shortest ) { + add( from, Check.to ); + } +} + +} // namespace + +std::vector TTrack::rail_crossings( bool const Joined ) { + + if( false == cut_rails( *this ) || simulation::Region == nullptr ) { + return {}; + } + auto &memo { memo_of( *this ) }; + if( Joined && memo.joined.has_value() ) { + return *memo.joined; + } + if( false == memo.own.has_value() ) { + memo.own = own_crossings( *this ); + } + auto result { *memo.own }; + if( false == Joined ) { + return result; + } + add_neighbour_crossings( *this, result ); + memo.joined = result; + return result; +} + +track_rails::check_rail_set TTrack::check_rails( std::vector const &Crossings ) { + + check_rail_set result; + auto const size { rail_dimensions() }; + if( size.grooved ) { + return result; + } + auto plans { planned_checks( *this, Crossings ) }; + auto const clearance { 0.046 + 0.04 * size.head / 0.0735 + size.head }; + for( int path = 0; path < path_count( *this ); ++path ) { + for( int right = 0; right < 2; ++right ) { + auto &checks { plans[ path ][ right ] }; + if( checks.empty() ) { + continue; + } + merge_checks( checks ); + auto const blocks { check_blocks( *this, path, right ? 1 : -1, checks, clearance ) }; + for( auto const &check : checks ) { + cut_check( check, blocks, result[ path ][ right ] ); + } + } + } + return result; +} + +track_rails::switch_frog TTrack::frog() const { + + switch_frog result; + if( eType != tt_Switch || false == cut_rails( *this ) ) { + return result; + } + result.side[ 0 ] = blade_side( 0 ); + result.side[ 1 ] = -result.side[ 0 ]; + auto const halfwidth { 0.5 * std::abs( fTrackWidth ) }; + std::vector lines[ 2 ]; + std::vector distances[ 2 ]; + for( int path = 0; path < 2; ++path ) { + auto const &segment { *SwitchExtension->Segments[ path ] }; + auto const length { segment.GetLength() }; + result.blades[ path ] = blade_length( *this, path ); + auto const count { std::max( 1, static_cast( std::ceil( length / 0.25 ) ) ) }; + for( int i = 0; i <= count; ++i ) { + auto const distance { length * i / count }; + glm::dvec3 point, across; + segment.plan_frame( distance, point, across ); + auto const gauge { point + across * ( result.side[ path ] * halfwidth ) }; + lines[ path ].emplace_back( gauge.x, gauge.z ); + distances[ path ].push_back( distance ); + } + } + for( std::size_t i = 1; i < lines[ 0 ].size() && false == result.valid; ++i ) { + if( distances[ 0 ][ i ] <= result.blades[ 0 ] ) { + continue; + } + auto const a0 { lines[ 0 ][ i - 1 ] }; + auto const a { lines[ 0 ][ i ] - a0 }; + for( std::size_t j = 1; j < lines[ 1 ].size(); ++j ) { + if( distances[ 1 ][ j ] <= result.blades[ 1 ] ) { + continue; + } + auto const b0 { lines[ 1 ][ j - 1 ] }; + auto const b { lines[ 1 ][ j ] - b0 }; + double u, v; + if( false == meet( a0, a, b0, b, 1e-12, u, v ) ) { + continue; + } + if( u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0 ) { + continue; + } + result.at[ 0 ] = glm::mix( distances[ 0 ][ i - 1 ], distances[ 0 ][ i ], u ); + result.at[ 1 ] = glm::mix( distances[ 1 ][ j - 1 ], distances[ 1 ][ j ], v ); + result.valid = true; + break; + } + } + if( false == result.valid ) { + return result; + } + glm::dvec3 point, across0, across1; + SwitchExtension->Segments[ 0 ]->plan_frame( result.at[ 0 ], point, across0 ); + SwitchExtension->Segments[ 1 ]->plan_frame( result.at[ 1 ], point, across1 ); + result.angle = std::acos( std::min( 1.0, std::abs( glm::dot( across0, across1 ) ) ) ); + if( result.angle < 0.01 || result.angle > 0.5 ) { + result.valid = false; + return result; + } + for( int path = 0; path < 2; ++path ) { + auto const throat { result.at[ path ] - flangeway / std::tan( result.angle ) }; + if( throat <= result.blades[ path ] || result.at[ path ] >= SwitchExtension->Segments[ path ]->GetLength() ) { + result.valid = false; + } + } + return result; +} + +track_rails::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( 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( 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( 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( i ) / steps ) }; + result.bends.emplace_back( distance, bend( distance, result.heel ) ); + } + result.valid = true; + return result; +} diff --git a/world/RailLayer.cpp b/world/RailLayer.cpp new file mode 100644 index 00000000..0df4538f --- /dev/null +++ b/world/RailLayer.cpp @@ -0,0 +1,1148 @@ +/* +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" + +namespace track_rails { + +namespace { + +// in the colour of the top of the rail foot +glm::vec2 cap_texture( std::vector const &Ring, std::vector const &Plan ) { + + auto bottom { 0.0 }, top { 0.0 }; + for( auto const &point : Plan ) { + bottom = std::min( bottom, point.y ); + top = std::max( top, point.y ); + } + auto web { Ring[ 0 ].texture }; + auto nearest { std::numeric_limits::max() }; + for( std::size_t j = 0; j < Ring.size() / 2; ++j ) { + auto const distance { std::abs( Plan[ j ].y - 0.5 * ( bottom + top ) ) }; + if( distance < nearest ) { + nearest = distance; + web = Ring[ j ].texture; + } + } + auto lowest { std::numeric_limits::max() }; + for( std::size_t j = 0; j < Ring.size() / 2; ++j ) { + if( Ring[ j ].normal.y > 0.5f && Plan[ j ].y < lowest ) { + lowest = Plan[ j ].y; + web = Ring[ j ].texture; + } + } + return web; +} + +std::vector> ear_clip( std::vector const &Polygon ) { + + auto const cross = []( glm::dvec2 const &A, glm::dvec2 const &B, glm::dvec2 const &C ) { + return ( B.x - A.x ) * ( C.y - A.y ) - ( B.y - A.y ) * ( C.x - A.x ); }; + double area { 0.0 }; + for( std::size_t j = 0; j < Polygon.size(); ++j ) { + auto const &a { Polygon[ j ] }; + auto const &b { Polygon[ ( j + 1 ) % Polygon.size() ] }; + area += a.x * b.y - b.x * a.y; + } + auto const orientation { area < 0.0 ? -1.0 : 1.0 }; + std::vector left( Polygon.size() ); + for( std::size_t j = 0; j < Polygon.size(); ++j ) { + left[ j ] = j; + } + std::vector> triangles; + while( left.size() > 3 ) { + auto ear { left.size() }; + for( std::size_t j = 0; j < left.size() && ear == left.size(); ++j ) { + auto const &a { Polygon[ left[ ( j + left.size() - 1 ) % left.size() ] ] }; + auto const &b { Polygon[ left[ j ] ] }; + auto const &c { Polygon[ left[ ( j + 1 ) % left.size() ] ] }; + auto const turn { cross( a, b, c ) * orientation }; + if( turn < -1e-12 ) { + continue; + } + auto inside { false }; + if( turn > 1e-12 ) { + for( auto const k : left ) { + auto const &p { Polygon[ k ] }; + if( p == a || p == b || p == c ) { + continue; + } + if( cross( a, b, p ) * orientation > 1e-12 && cross( b, c, p ) * orientation > 1e-12 && cross( c, a, p ) * orientation > 1e-12 ) { + inside = true; + break; + } + } + } + if( false == inside ) { + ear = j; + } + } + if( ear == left.size() ) { + ear = 0; + } + triangles.push_back( { left[ ( ear + left.size() - 1 ) % left.size() ], left[ ear ], left[ ( ear + 1 ) % left.size() ] } ); + left.erase( left.begin() + ear ); + } + triangles.push_back( { left[ 0 ], left[ 1 ], left[ 2 ] } ); + return triangles; +} + +} // namespace + +void cap_loft( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::vertex_array &Triangles ) { + + if( Ring < 3 || Strip.size() < 2 * Ring ) { + return; + } + std::vector, glm::vec3>> caps; + if( Start ) { + std::vector ring; + for( std::size_t j = 0; j < Ring; ++j ) { + ring.emplace_back( Strip[ 2 * j ] ); + } + caps.emplace_back( ring, Strip[ 0 ].position - Strip[ 1 ].position ); + } + if( End ) { + auto const base { Strip.size() - 2 * Ring }; + std::vector ring; + for( std::size_t j = 0; j < Ring; ++j ) { + ring.emplace_back( Strip[ base + 2 * j + 1 ] ); + } + caps.emplace_back( ring, Strip[ base + 1 ].position - Strip[ base ].position ); + } + for( auto const &cap : caps ) { + auto const &ring { cap.first }; + if( glm::length2( cap.second ) == 0.f ) { + continue; + } + auto const outward { glm::normalize( cap.second ) }; + auto across { glm::vec3{ -outward.z, 0.f, outward.x } }; + if( glm::length2( across ) == 0.f ) { + continue; + } + across = glm::normalize( across ); + std::vector plan; + for( auto const &vertex : ring ) { + auto const offset { vertex.position - ring[ 0 ].position }; + plan.emplace_back( glm::dot( offset, across ), offset.y ); + } + auto const web { cap_texture( ring, plan ) }; + auto const triangles { ear_clip( plan ) }; + for( auto const &triangle : triangles ) { + for( auto const &order : { std::array{ 0, 1, 2 }, std::array{ 0, 2, 1 } } ) { + for( auto const k : order ) { + auto const index { triangle[ k ] }; + Triangles.emplace_back( ring[ index ].position, outward, web ); + } + } + } + } +} + +gfx::userdata_array worn_userdata( std::size_t const Count ) { + + return gfx::userdata_array( Count, gfx::vertex_userdata{ glm::vec4{ 1.f, 0.f, 0.f, 0.f } } ); +} + +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 ) { + + gfx::vertex_array triangles; + cap_loft( Strip, Ring, Start, End, triangles ); + if( false == triangles.empty() ) { + auto userdata { worn_userdata( triangles.size() ) }; + Geometry.emplace_back( GfxRenderer->Insert( triangles, userdata, Bank, GL_TRIANGLES ) ); + } +} + +} // namespace track_rails + +using namespace track_rails; + +namespace { + +gfx::vertex_array box_profile( float const Bottom, float const Top, float const Texture, float const Spread = 0.f ) { + + return { + {{ 1.f, Bottom, 0.f }, { 1.f, 0.f, 0.f }, { Texture + Spread, 0.f }}, + {{ 1.f, Top, 0.f }, { 1.f, 0.f, 0.f }, { Texture + Spread, 0.f }}, + {{ 1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture + Spread, 0.f }}, + {{ -1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture - Spread, 0.f }}, + {{ -1.f, Top, 0.f }, { -1.f, 0.f, 0.f }, { Texture - Spread, 0.f }}, + {{ -1.f, Bottom, 0.f }, { -1.f, 0.f, 0.f }, { Texture - Spread, 0.f }} }; +} + +} // namespace + +TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const &Bank ) { + + auto const sides = [&]( gfx::vertex_array const &Shape, gfx::vertex_array ( &Sides )[ 2 ] ) { + create_track_rail_profile( Sides[ 0 ], Sides[ 1 ], &Shape ); + for( auto &side : Sides ) { + side.resize( Shape.size() ); + } }; + rail_drawing drawing; + drawing.bank = Bank; + drawing.size = rail_dimensions(); + drawing.halfwidth = 0.5f * std::abs( fTrackWidth ); + // check rail: a narrow head facing the running rail across the flangeway, on a thin web with a foot turned away from it, sized after the rail + gfx::vertex_array checkshape { + {{ -0.041f, -0.170f, 0.f }, { 1.000f, 0.000f, 0.f }, { 0.15f, 0.f }}, + {{ -0.041f, -0.010f, 0.f }, { 0.707f, 0.707f, 0.f }, { 0.40f, 0.f }}, + {{ -0.051f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.45f, 0.f }}, + {{ -0.071f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.55f, 0.f }}, + {{ -0.081f, -0.010f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.60f, 0.f }}, + {{ -0.081f, -0.045f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.65f, 0.f }}, + {{ -0.066f, -0.060f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.75f, 0.f }}, + {{ -0.066f, -0.150f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.85f, 0.f }}, + {{ -0.101f, -0.170f, 0.f }, { -1.000f, 0.000f, 0.f }, { 1.00f, 0.f }} }; + for( auto &point : checkshape ) { + point.position.x = -0.041f + ( point.position.x + 0.041f ) * static_cast( drawing.size.head / 0.0735 ); + point.position.y *= static_cast( drawing.size.height / 0.172 ); + } + sides( checkshape, drawing.checkshapes ); + if( false == m_profiles.empty() ) { + auto const &outline { track_rail_profile( m_profile1.second ) }; + auto texture { 0.f }; + auto highest { -1e9f }; + for( std::size_t i = 0; i < outline.size() / 2; ++i ) { + if( outline[ i ].position.y > highest ) { + highest = outline[ i ].position.y; + texture = outline[ i ].texture.s; + } + } + if( drawing.size.grooved ) { + auto sum { 0.f }; + auto points { 0 }; + for( std::size_t i = 0; i < outline.size() / 2; ++i ) { + if( outline[ i ].position.y > highest - 0.0005f && outline[ i ].position.x > -0.02f ) { + sum += outline[ i ].texture.s; + ++points; + } + } + texture = sum / std::max( points, 1 ); + } + auto const top { static_cast( ( drawing.size.grooved ? drawing.size.liptop : highest ) - 0.0005 ) }; + auto const bottom { static_cast( -drawing.size.height ) }; + drawing.platetexture = texture; + sides( box_profile( bottom, top, texture ), drawing.plates ); + auto const neck { static_cast( drawing.size.neck ) }; + auto webtexture { texture }; + for( std::size_t i = 0; i < outline.size() / 2; ++i ) { + auto const &point { outline[ i ] }; + if( point.position.y < neck && point.normal.y > 0.9f ) { + webtexture = point.texture.s; + break; + } + } + glm::vec2 across { 1e9f, -1e9f }; + glm::vec2 band { 1e9f, -1e9f }; + for( std::size_t i = 0; i < outline.size() / 2; ++i ) { + if( outline[ i ].position.y > highest - 0.0005f ) { + across = { std::min( across.x, outline[ i ].position.x ), std::max( across.y, outline[ i ].position.x ) }; + band = { std::min( band.x, outline[ i ].texture.s ), std::max( band.y, outline[ i ].texture.s ) }; + } + } + auto const spread { across.y > across.x ? ( band.y - band.x ) / ( across.y - across.x ) : 0.f }; + sides( box_profile( bottom, neck, webtexture, spread ), drawing.fillers ); + } + return drawing; +} + +namespace { + +struct piece_ends { + double from { 0.0 }; + double to { 0.0 }; + loft_end fromend; + loft_end toend; +}; + +struct rail_piece { + material_handle material { null_handle }; + double from { 0.0 }; + double to { 0.0 }; + knots::line offsets; + knots::line extents; + knots::line inner; + bool capfrom { false }; + bool capto { false }; + bool rolled { true }; + knots::line ramp; + double fade { blend }; + bool walls { false }; + float lift { 0.f }; + float wallu { -1.f }; +}; + +struct top_band { + material_handle material { null_handle }; + knots::line extents; + knots::line inner; + bool rolled { true }; + knots::line ramp; + double fade { blend }; +}; + +struct grooved_profile { + gfx::vertex_array body; + std::vector tops; + std::vector flats; + float wallu { -1.f }; +}; + +glm::vec3 const plain_mark { 0.f, 1.f, 1.f }; + +bool treads_marked() { + + static auto const marked { std::getenv( "EU07_EDITOR_TREADS" ) != nullptr }; + return marked; +} + +void sort_cuts( std::vector &Cuts ) { + + std::sort( Cuts.begin(), Cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } ); +} + +void add_meetings( knots::line const &A, knots::line const &B, double const Start, double const End, double const Margin, std::vector &Splits ) { + + for( auto const distance : knots::meetings( A, B ) ) { + if( distance > Start + Margin && distance < End - Margin ) { + Splits.push_back( distance ); + } + } +} + +std::vector cross_splits( rail_piece const &Whole, glm::dvec2 const &Reach, std::initializer_list const Edges, double const Margin ) { + + std::vector result { Whole.from, Whole.to }; + knots::line const lowline { { Whole.from, Reach.x }, { Whole.to, Reach.x } }; + knots::line const highline { { Whole.from, Reach.y }, { Whole.to, Reach.y } }; + for( auto const *edge : Edges ) { + for( auto const *limit : { &lowline, &highline, &Whole.extents, &Whole.inner } ) { + add_meetings( *edge, *limit, Whole.from, Whole.to, Margin, result ); + } + } + std::sort( result.begin(), result.end() ); + return result; +} + +knots::line flared( double const From, double const To, double const Flare, double const Base, double const Fromdepth, double const Todepth ) { + + knots::line result; + if( Fromdepth > 0.0 ) { + result.insert( result.end(), { { From, Base - Fromdepth }, { From + Flare, Base } } ); + } + else { + result.push_back( { From, Base } ); + } + if( Todepth > 0.0 ) { + result.insert( result.end(), { { To - Flare, Base }, { To, Base - Todepth } } ); + } + else { + result.push_back( { To, Base } ); + } + return result; +} + +knots::line envelope( knots::line const &Lines, bool const Upper, double const A, double const B ) { + + knots::line result; + if( Lines.empty() ) { + return result; + } + std::vector stations { A, B }; + for( std::size_t i = 0; i < Lines.size(); ++i ) { + for( std::size_t j = i + 1; j < Lines.size(); ++j ) { + if( std::abs( Lines[ i ].x - Lines[ j ].x ) > 1e-12 ) { + auto const d { ( Lines[ j ].y - Lines[ i ].y ) / ( Lines[ i ].x - Lines[ j ].x ) }; + if( d > A && d < B ) { + stations.push_back( d ); + } + } + } + } + std::sort( stations.begin(), stations.end() ); + for( auto const d : stations ) { + auto value { Upper ? 1e6 : -1e6 }; + for( auto const &line : Lines ) { + value = Upper ? std::min( value, line.x * d + line.y ) : std::max( value, line.x * d + line.y ); + } + result.emplace_back( d, value ); + } + return result; +} + +rail_cut band_cut( rail_band const &Band, glm::dvec2 const &Reach ) { + + rail_cut cut; + cut.at = Band.at; + auto const before { Band.into > 0.0 ? Band.from : Band.to }; + auto const after { Band.into > 0.0 ? Band.to : Band.from }; + auto const meets = [&]( double const Edge, double const Across ) { + return glm::dvec2{ Band.at + ( Edge + Band.facing * Across ) / Band.into, Across }; }; + if( std::abs( Band.facing ) < 1e-3 ) { + cut.end = meets( before, 0.0 ).x; + cut.start = meets( after, 0.0 ).x; + return cut; + } + knots::line endknots { meets( before, Reach.x ), meets( before, Reach.y ) }; + knots::line startknots { meets( after, Reach.x ), meets( after, Reach.y ) }; + knots::sort( endknots ); + knots::sort( startknots ); + cut.end = endknots.back().x; + cut.start = startknots.front().x; + cut.endcap = false; + cut.startcap = false; + auto const outwards { Band.facing / Band.into > 0.0 }; + ( outwards ? cut.endinner : cut.endextents ) = endknots; + ( outwards ? cut.startextents : cut.startinner ) = startknots; + return cut; +} + +rail_cut seam( double const At, double const End, double const Start ) { + + rail_cut cut; + cut.at = At; + cut.end = End; + cut.start = Start; + cut.endcap = false; + cut.startcap = false; + return cut; +} + +knots::line merged_blocks( knots::line Blocks, std::vector const &Cuts ) { + + for( auto const &cut : Cuts ) { + for( auto &block : Blocks ) { + if( cut.at > block.x && cut.at < block.y ) { + block = { std::min( block.x, cut.end ), std::max( block.y, cut.start ) }; + } + } + } + knots::sort( Blocks ); + knots::line result; + for( auto const &block : Blocks ) { + if( false == result.empty() && block.x <= result.back().y ) { + result.back().y = std::max( result.back().y, block.y ); + } + else { + result.push_back( block ); + } + } + return result; +} + +rail_tread const *tread_at( std::vector const &Treads, double const Middle ) { + + for( auto const &tread : Treads ) { + if( Middle > tread.from && Middle < tread.to ) { + return &tread; + } + } + return nullptr; +} + +material_handle tread_material( rail_tread const *Tread ) { + + return ( Tread != nullptr ? Tread->material : null_handle ); +} + +} // namespace + +class TTrack::rail_layer { + +public: +// constructors + rail_layer( TTrack &Track, 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 ); +// methods + bool lay(); + +private: +// methods + template + Type_ const &ours( Type_ const ( &Sides )[ 2 ][ 2 ] ) const { + return Sides[ m_path ][ m_side > 0 ]; } + std::vector const &treads() const { + return ours( m_layout.treads ); } + void lay_guards(); + void lay_checks(); + void lay_fills(); + void lay_treads( rail_piece const &Whole, std::vector const &Treads ); + void lay_cuts( gfx::vertex_array const &Shape, std::vector Cuts, bool const Wheels, std::vector const &Treads, float const Wallu = -1.f ); + void lay_grooved( gfx::vertex_array const &Outline ); + grooved_profile split_grooved( gfx::vertex_array const &Outline ) const; + std::vector top_cuts( gfx::vertex_array const &Top ) const; + void running( rail_piece const &Whole, knots::line const &Rolled, piece_ends const &Ends ); + void plain( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends ); + void treaded( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends ); + gfx::vertex_array lay_sides( rail_piece const &Whole, piece_ends const &Ends ); + void lay_top( gfx::vertex_array const &Top, rail_piece const &Whole, glm::dvec2 const &Reach, std::vector const &Splits, std::vector const &Bands, std::function( double )> const &Edges, piece_ends const &Ends ); + void piece( gfx::vertex_array const &Shape, rail_piece const &Piece, piece_ends const &Ends = {} ); + loft_span span( rail_piece const &Piece, piece_ends const &Ends ) const; + double texture_length( material_handle const Material ) const; + geometryhandle_sequence &sink( material_handle const Material, bool const Matte ); + material_handle paint( material_handle const Material, glm::vec3 const &Colour ); + glm::dvec2 reaches( gfx::vertex_array const &Shape ) const; + gfx::vertex_array strip( std::size_t const First, std::size_t const Last ) const; +// members + TTrack &m_track; + rail_layout const &m_layout; + rail_drawing const &m_drawing; + int const m_path; + gfx::vertex_array const &m_profile; + int const m_side; + float const m_texturelength; + double const m_from; + bool const m_openstart; + bool const m_openend; + geometryhandle_sequence &m_geometry; + geometryhandle_sequence &m_cuts; + TSegment const &m_segment; + double const m_length; + double const m_guardoffset; + std::size_t m_topfirst; + std::size_t m_toplast { 0 }; + std::vector m_tints; +}; + +TTrack::rail_layer::rail_layer( TTrack &Track, 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 ) : + m_track { Track }, + m_layout { Layout }, + m_drawing { Drawing }, + m_path { Path }, + m_profile { Profile }, + m_side { Side }, + m_texturelength { Texturelength }, + m_from { From }, + m_openstart { Openstart }, + m_openend { Openend }, + m_geometry { Geometry }, + m_cuts { Cuts }, + m_segment { path_segment( Track, Path ) }, + m_length { m_segment.GetLength() }, + m_guardoffset { -( flangeway + Drawing.size.head ) }, + m_topfirst { Profile.size() } { + // the running surface on top of the head is split along the edge of the wheel treads, the part of it out of their reach goes matte; the rest of the profile and the end faces are laid whole + for( std::size_t j = 0; j < Profile.size(); ++j ) { + if( Profile[ j ].position.y > static_cast( -0.087 * Drawing.size.height ) ) { + m_topfirst = std::min( m_topfirst, j ); + m_toplast = std::max( m_toplast, j ); + } + } +} + +bool TTrack::rail_layer::lay() { + + lay_guards(); + lay_checks(); + lay_fills(); + auto const &plan { ours( m_layout.plans ) }; + auto const &bands { ours( m_layout.bands ) }; + if( plan.cuts.empty() && bands.empty() && treads().empty() ) { + return false; + } + auto const &outline { track_rail_profile( m_track.m_profile1.second ) }; + if( false == m_drawing.size.grooved || outline.size() != 2 * m_profile.size() ) { + auto cuts { plan.cuts }; + auto const reach { reaches( m_profile ) }; + for( auto const &band : bands ) { + cuts.push_back( band_cut( band, reach ) ); + } + lay_cuts( m_profile, cuts, true, treads() ); + return true; + } + lay_grooved( outline ); + return true; +} + +void TTrack::rail_layer::lay_guards() { + + auto const &size { m_drawing.size }; + auto const top { flangeway + size.head + size.low - 0.002 }; + auto const mitered = [&]( glm::dvec2 const &Miter, double const Square, bool const Latest ) { + if( Miter.y == 0.0 ) { + return Square; + } + auto const inside { Miter.x + Miter.y * ( size.low + m_guardoffset ) }; + auto const outside { Miter.x + Miter.y * ( top + m_guardoffset ) }; + return Latest ? std::max( inside, outside ) : std::min( inside, outside ); }; + for( auto const &guard : ours( m_layout.guards ) ) { + auto const offsets { flared( guard.from, guard.to, std::min( guard_flare, 0.25 * ( guard.to - guard.from ) ), m_guardoffset, guard.flarefrom ? 0.02 : 0.0, guard.flareto ? 0.02 : 0.0 ) }; + auto cuts { guard.plan.cuts }; + sort_cuts( cuts ); + auto start { guard.from }; + auto startmiter { guard.frommiter }; + for( std::size_t i = 0; i <= cuts.size(); ++i ) { + auto const end { i < cuts.size() ? cuts[ i ].end : guard.to }; + auto const endmiter { i < cuts.size() ? cuts[ i ].endmiter : guard.tomiter }; + auto const a { std::max( { mitered( startmiter, start, false ), m_from, 0.0 } ) }; + auto const b { std::min( { mitered( endmiter, end, true ), m_length, mitered( guard.tomiter, guard.to, true ) } ) }; + if( b - a > 0.01 ) { + knots::line uppers { { 0.0, top } }; + knots::line lowers; + for( auto const &[ miter, ending ] : { std::pair{ startmiter, false }, std::pair{ endmiter, true } } ) { + if( miter.y != 0.0 ) { + ( ending == ( miter.y < 0.0 ) ? uppers : lowers ).emplace_back( 1.0 / miter.y, -miter.x / miter.y - m_guardoffset ); + } + } + lay_treads( { .from = a, .to = b, .offsets = offsets, .extents = envelope( uppers, true, a, b ), .inner = envelope( lowers, false, a, b ), .capfrom = true, .capto = true, .rolled = false }, guard.treads ); + } + if( i < cuts.size() && cuts[ i ].start > start ) { + start = cuts[ i ].start; + startmiter = cuts[ i ].startmiter; + } + } + } +} + +void TTrack::rail_layer::lay_checks() { + + auto const &shape { m_drawing.checkshapes[ m_side > 0 ? 0 : 1 ] }; + for( auto const &check : m_layout.checks[ m_path ][ m_side > 0 ] ) { + auto const a { std::max( check.from, m_from ) }; + auto const b { std::min( check.to, m_length ) }; + if( replaced_by_wing( check, ours( m_layout.wingends ) ) || b - a <= 0.01 ) { + continue; + } + piece( + shape, + { .from = a, .to = b, .offsets = flared( check.from, check.to, check_flare( check ), 0.0, check.flarefrom, check.flareto ), .capfrom = a > 0.0 || check.from >= 0.0, .capto = check.to <= m_length, .rolled = false }, + { a, b, { a == check.from ? check.frombend : 0.0 }, { b == check.to ? check.tobend : 0.0 } } ); + } +} + +void TTrack::rail_layer::lay_treads( rail_piece const &Whole, std::vector const &Treads ) { + + std::vector splits { Whole.from, Whole.to }; + for( auto const &tread : Treads ) { + for( auto const edge : { tread.from, tread.to } ) { + if( edge > Whole.from + 0.01 && edge < Whole.to - 0.01 ) { + splits.push_back( edge ); + } + } + } + std::sort( splits.begin(), splits.end() ); + piece_ends const ends { Whole.from, Whole.to }; + for( std::size_t k = 1; k < splits.size(); ++k ) { + auto part { Whole }; + part.from = splits[ k - 1 ]; + part.to = splits[ k ]; + part.capfrom = ( k == 1 && Whole.capfrom ); + part.capto = ( k + 1 == splits.size() && Whole.capto ); + auto const *tread { tread_at( Treads, 0.5 * ( part.from + part.to ) ) }; + if( tread != nullptr && m_topfirst < m_toplast && tread->edge.empty() ) { + plain( part, *tread, ends ); + } + else if( tread != nullptr && m_topfirst < m_toplast ) { + treaded( part, *tread, ends ); + } + else { + piece( m_profile, part, ends ); + } + } +} + +void TTrack::rail_layer::lay_fills() { + + for( auto const &fill : ours( m_layout.fills ) ) { + auto const a { std::max( fill.from, m_from ) }; + auto const b { std::min( fill.to, m_length ) }; + if( b - a > 0.01 ) { + auto const &shapes { fill.web ? m_drawing.fillers : m_drawing.plates }; + piece( + shapes[ m_side > 0 ? 0 : 1 ], + { .material = ( fill.web ? null_handle : tread_material( tread_at( treads(), 0.5 * ( a + b ) ) ) ), .from = a, .to = b, .extents = fill.outer, .inner = fill.inner, .capfrom = a == fill.from && fill.capfrom, .capto = b == fill.to && fill.capto, .walls = true, .lift = static_cast( fill.lift ) } ); + } + } +} + +void TTrack::rail_layer::lay_cuts( gfx::vertex_array const &Shape, std::vector Cuts, bool const Wheels, std::vector const &Treads, float const Wallu ) { + + sort_cuts( Cuts ); + auto start { -1e9 }; + auto startcap { true }; + auto startbend { 0.0 }; + knots::line rolledfrom; + knots::line offsets, extents, inner; + for( std::size_t i = 0; i <= Cuts.size(); ++i ) { + auto const end { i < Cuts.size() ? Cuts[ i ].end : 1e9 }; + auto pieceoffsets { offsets }; + auto pieceextents { extents }; + auto pieceinner { inner }; + if( i < Cuts.size() ) { + pieceoffsets.insert( pieceoffsets.end(), Cuts[ i ].endoffsets.begin(), Cuts[ i ].endoffsets.end() ); + pieceextents.insert( pieceextents.end(), Cuts[ i ].endextents.begin(), Cuts[ i ].endextents.end() ); + pieceinner.insert( pieceinner.end(), Cuts[ i ].endinner.begin(), Cuts[ i ].endinner.end() ); + } + knots::sort( pieceoffsets ); + knots::sort( pieceextents ); + knots::sort( pieceinner ); + auto const a { std::max( start, m_from ) }; + auto const b { std::min( end, m_length ) }; + auto const rolled { i < Cuts.size() ? knots::bound( rolledfrom, Cuts[ i ].endrolled, true ) : rolledfrom }; + auto const capstart { start > m_from ? startcap : m_openstart }; + auto const capend { end < m_length ? Cuts[ i ].endcap : m_openend }; + piece_ends const ends { + a, b, + { start > m_from ? startbend : 0.0 }, + { end < m_length ? Cuts[ i ].endbend : 0.0 } }; + if( b - a > 0.01 ) { + std::vector active; + for( auto const &tread : Treads ) { + if( tread.anchor == 0 + || ( tread.anchor > 0 && b > tread.from + 0.01 && b < tread.to + 0.01 ) + || ( tread.anchor < 0 && a > tread.from - 0.01 && a < tread.to - 0.01 ) ) { + active.push_back( tread ); + } + } + std::vector splits; + for( auto const &tread : active ) { + for( auto const edge : { tread.from, tread.to } ) { + if( edge > a + 0.01 && edge < b - 0.01 ) { + splits.push_back( edge ); + } + } + } + std::sort( splits.begin(), splits.end() ); + splits.erase( std::unique( splits.begin(), splits.end(), []( double const A, double const B ) { return B - A < 0.002; } ), splits.end() ); + splits.insert( splits.begin(), a ); + splits.push_back( b ); + for( std::size_t k = 1; k < splits.size(); ++k ) { + auto const *tread { tread_at( active, 0.5 * ( splits[ k - 1 ] + splits[ k ] ) ) }; + rail_piece const whole { + .material = ( tread != nullptr && treads_marked() ? paint( tread->material, plain_mark ) : tread_material( tread ) ), + .from = splits[ k - 1 ], + .to = splits[ k ], + .offsets = pieceoffsets, + .extents = pieceextents, + .inner = pieceinner, + .capfrom = k == 1 && capstart, + .capto = k + 1 == splits.size() && capend, + .walls = false == Wheels, + .wallu = Wallu }; + if( Wheels && tread != nullptr && false == tread->edge.empty() && m_topfirst < m_toplast ) { + treaded( whole, *tread, ends ); + } + else if( false == Wheels || rolled.empty() || m_topfirst >= m_toplast ) { + piece( Shape, whole, ends ); + } + else { + running( whole, rolled, ends ); + } + } + } + if( i < Cuts.size() ) { + rolledfrom = Cuts[ i ].startrolled; + start = Cuts[ i ].start; + startcap = Cuts[ i ].startcap; + startbend = Cuts[ i ].startbend; + offsets = Cuts[ i ].startoffsets; + extents = Cuts[ i ].startextents; + inner = Cuts[ i ].startinner; + } + } +} + +void TTrack::rail_layer::lay_grooved( gfx::vertex_array const &Outline ) { + + auto const grooved { split_grooved( Outline ) }; + auto const &bands { ours( m_layout.bands ) }; + auto wholecuts { ours( m_layout.plans ).cuts }; + auto const bodyreach { reaches( grooved.body ) }; + for( auto const &band : bands ) { + if( band.whole ) { + wholecuts.push_back( band_cut( band, bodyreach ) ); + } + } + lay_cuts( grooved.body, wholecuts, false, {} ); + for( std::size_t k = 0; k < grooved.tops.size(); ++k ) { + auto const cuts { top_cuts( grooved.tops[ k ] ) }; + auto const merged { merged_blocks( ours( m_layout.blocks ), cuts ) }; + if( merged.empty() ) { + lay_cuts( grooved.tops[ k ], cuts, false, treads(), grooved.wallu ); + continue; + } + std::vector blocktreads; + for( auto const &span : merged ) { + for( auto const &tread : treads() ) { + if( tread.from < span.y && tread.to > span.x ) { + blocktreads.push_back( { span.x, span.y, tread.material } ); + break; + } + } + } + std::vector outside, inside; + for( auto const &cut : cuts ) { + auto const within { std::any_of( merged.begin(), merged.end(), [&]( glm::dvec2 const &Span ) { return cut.at > Span.x && cut.at < Span.y; } ) }; + ( within ? inside : outside ).push_back( cut ); + } + inside.push_back( seam( merged.front().x - 0.001, -1e9, merged.front().x ) ); + for( std::size_t m = 0; m < merged.size(); ++m ) { + outside.push_back( seam( 0.5 * ( merged[ m ].x + merged[ m ].y ), merged[ m ].x, merged[ m ].y ) ); + inside.push_back( m + 1 < merged.size() ? + seam( 0.5 * ( merged[ m ].y + merged[ m + 1 ].x ), merged[ m ].y, merged[ m + 1 ].x ) : + seam( merged[ m ].y + 0.001, merged[ m ].y, 1e9 ) ); + } + lay_cuts( grooved.tops[ k ], outside, false, treads(), grooved.wallu ); + lay_cuts( grooved.flats[ k ], inside, false, blocktreads, grooved.wallu ); + } +} + +grooved_profile TTrack::rail_layer::split_grooved( gfx::vertex_array const &Outline ) const { + + auto const &size { m_drawing.size }; + auto const count { m_profile.size() }; + auto const height = [&]( std::size_t const Index ) { + return static_cast( Outline[ m_side > 0 ? Index : count - 1 - Index ].position.y ) - size.groove; }; + auto const above = [&]( std::size_t const Index ) { + return height( Index ) > 1e-4; }; + auto const floorpoint = [&]( std::size_t const A, std::size_t const B ) { + auto const t { static_cast( height( A ) / ( height( A ) - height( B ) ) ) }; + auto point { m_profile[ above( A ) ? A : B ] }; + point.position = glm::mix( m_profile[ A ].position, m_profile[ B ].position, t ); + point.texture = glm::mix( m_profile[ A ].texture, m_profile[ B ].texture, t ); + return point; }; + auto const flattened = [&]( std::size_t const Index ) { + auto point { m_profile[ Index ] }; + auto const &file { Outline[ m_side > 0 ? Index : count - 1 - Index ].position }; + if( file.y > size.crest - 0.0005 || ( file.x < -0.02 && file.y > size.liptop - 0.0005 ) ) { + point.normal = { 0.f, 1.f, 0.f }; + point.texture.x = m_drawing.platetexture; + } + return point; }; + grooved_profile result; + auto &body { result.body }; + std::vector floors; + auto floortexture { 0.f }; + auto floortextures { 0 }; + for( std::size_t i = 0; i < count; ++i ) { + if( false == above( i ) ) { + if( height( i ) > -1e-4 ) { + floors.push_back( body.size() ); + floortexture += m_profile[ i ].texture.x; + ++floortextures; + } + body.push_back( m_profile[ i ] ); + continue; + } + auto const first { i }; + while( i + 1 < count && above( i + 1 ) ) { + ++i; + } + gfx::vertex_array top, flat; + if( first > 0 ) { + if( height( first - 1 ) > -1e-4 ) { + top.push_back( m_profile[ first - 1 ] ); + flat.push_back( flattened( first - 1 ) ); + } + else { + top.push_back( floorpoint( first - 1, first ) ); + flat.push_back( top.back() ); + body.push_back( top.back() ); + floors.push_back( body.size() ); + body.push_back( top.back() ); + } + } + for( auto k { first }; k <= i; ++k ) { + top.push_back( m_profile[ k ] ); + flat.push_back( flattened( k ) ); + } + if( i + 1 < count ) { + if( height( i + 1 ) > -1e-4 ) { + top.push_back( m_profile[ i + 1 ] ); + flat.push_back( flattened( i + 1 ) ); + } + else { + top.push_back( floorpoint( i, i + 1 ) ); + flat.push_back( top.back() ); + floors.push_back( body.size() ); + body.push_back( top.back() ); + body.push_back( top.back() ); + } + } + for( auto *strip : { &top, &flat } ) { + gfx::vertex_array doubled { strip->front(), strip->front() }; + for( std::size_t k = 1; k + 1 < strip->size(); ++k ) { + doubled.insert( doubled.end(), 2, ( *strip )[ k ] ); + } + doubled.insert( doubled.end(), 2, strip->back() ); + *strip = doubled; + } + result.tops.push_back( top ); + result.flats.push_back( flat ); + } + if( floors.size() > 1 ) { + auto const across { body[ floors.back() ].position - body[ floors.front() ].position }; + auto up { glm::normalize( glm::vec3{ -across.y, across.x, 0.f } ) }; + if( up.y < 0.f ) { + up = -up; + } + for( auto const index : floors ) { + body[ index ].normal = up; + if( floortextures > 0 ) { + body[ index ].texture.x = floortexture / floortextures; + } + } + } + result.wallu = ( floortextures > 0 ? floortexture / floortextures : -1.f ); + return result; +} + +std::vector TTrack::rail_layer::top_cuts( gfx::vertex_array const &Top ) const { + + auto const &size { m_drawing.size }; + auto const reach { reaches( Top ) }; + auto const head { reach.x + reach.y > 0.0 }; + auto edge { head ? 1e9 : -1e9 }; + for( auto const &point : Top ) { + if( point.position.y > ( head ? size.crest : size.liptop ) - 0.0005 ) { + auto const across { static_cast( m_side * point.position.x - m_drawing.halfwidth ) }; + edge = ( head ? std::min( edge, across ) : std::max( edge, across ) ); + } + } + auto cuts { ours( m_layout.plans ).cuts }; + for( auto const &band : ours( m_layout.bands ) ) { + auto cut { band_cut( band, reach ) }; + if( false == band.whole && std::abs( band.facing ) >= 1e-3 && std::abs( edge ) < 1.0 ) { + auto const blunt { edge + ( head ? nose_tip : -nose_tip ) }; + if( head == ( band.facing / band.into > 0.0 ) ) { + cut.start = std::max( cut.start, band.at + ( ( band.into > 0.0 ? band.to : band.from ) + band.facing * blunt ) / band.into ); + cut.startcap = true; + } + else { + cut.end = std::min( cut.end, band.at + ( ( band.into > 0.0 ? band.from : band.to ) + band.facing * blunt ) / band.into ); + cut.endcap = true; + } + } + cuts.push_back( cut ); + } + return cuts; +} + +void TTrack::rail_layer::running( rail_piece const &Whole, knots::line const &Rolled, piece_ends const &Ends ) { + + auto const top { lay_sides( Whole, Ends ) }; + auto const reach { reaches( top ) }; + auto const inner { knots::shifted( Rolled, -blend ) }; + auto const splits { cross_splits( Whole, reach, { &Rolled, &inner }, 0.0 ) }; + auto const marked { treads_marked() }; + auto const own { Whole.material == null_handle ? m_track.rail_material( m_path ) : Whole.material }; + lay_top( + top, Whole, reach, splits, + { + { Whole.material, knots::bound( Whole.extents, inner, true ), Whole.inner }, + { marked ? paint( own, { 1.f, 1.f, 0.f } ) : Whole.material, knots::bound( Whole.extents, Rolled, true ), knots::bound( inner, Whole.inner, false ), false, marked ? knots::line {} : Rolled }, + { marked ? paint( own, { 1.f, 0.f, 1.f } ) : Whole.material, Whole.extents, knots::bound( Rolled, Whole.inner, false ), false } }, + [&]( double const Middle ) { + auto const rolled { knots::sample( Rolled, Middle, 0.0 ) }; + return std::vector { rolled - blend, rolled }; }, + Ends ); +} + +void TTrack::rail_layer::plain( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends ) { + + Whole.material = null_handle; + auto const top { lay_sides( Whole, Ends ) }; + Whole.material = ( treads_marked() ? paint( Tread.material, plain_mark ) : Tread.material ); + Whole.capfrom = false; + Whole.capto = false; + Whole.rolled = true; + piece( top, Whole, Ends ); +} + +void TTrack::rail_layer::treaded( rail_piece Whole, rail_tread const &Tread, piece_ends const &Ends ) { + + Whole.material = null_handle; + auto const top { lay_sides( Whole, Ends ) }; + auto const reach { reaches( top ) }; + auto const &lower { Tread.below ? Tread.gauge : Tread.edge }; + auto const &upper { Tread.below ? Tread.edge : Tread.gauge }; + auto const marked { treads_marked() }; + auto const own { m_track.rail_material( m_path ) }; + auto const colour { 0.5f * ( tread_colour( Tread.material, m_track.tread_band() ) + tread_colour( own, m_track.tread_band() ) ) }; + auto const tint = [&]( material_handle const Material, glm::vec3 const &Mark ) { + return paint( Material, marked ? Mark : colour ); }; + auto const owntint { Whole.rolled ? tint( own, { 1.f, 0.f, 0.f } ) : null_handle }; + auto const sharedtint { tint( Tread.material, { 0.f, 1.f, 0.f } ) }; + auto const shared { marked ? tint( Tread.material, { 0.f, 0.f, 1.f } ) : Tread.material }; + auto const ramp = [&]( knots::line const &Knots ) { + return marked ? knots::line {} : Knots; }; + auto const fade { Tread.fade }; + auto const centre { knots::middle( lower, upper ) }; + auto const beneath { knots::shifted( lower, -fade ) }; + auto const beyond { knots::shifted( upper, fade ) }; + auto const lowfade { knots::bound( knots::shifted( lower, fade ), centre, true ) }; + auto const highfade { knots::bound( knots::shifted( upper, -fade ), centre, false ) }; + auto splits { cross_splits( Whole, reach, { &lower, &upper, &lowfade, &highfade, &beneath, &beyond }, 0.001 ) }; + add_meetings( knots::shifted( lower, 2.0 * fade ), upper, Whole.from, Whole.to, 0.001, splits ); + std::sort( splits.begin(), splits.end() ); + lay_top( + top, Whole, reach, splits, + { + { null_handle, knots::bound( Whole.extents, beneath, true ), Whole.inner, Whole.rolled }, + { owntint, knots::bound( Whole.extents, lower, true ), knots::bound( Whole.inner, beneath, false ), false, ramp( lower ), fade }, + { sharedtint, knots::bound( Whole.extents, lowfade, true ), knots::bound( Whole.inner, lower, false ), false, ramp( lower ), -fade }, + { shared, knots::bound( Whole.extents, highfade, true ), knots::bound( Whole.inner, lowfade, false ), false == marked }, + { sharedtint, knots::bound( Whole.extents, upper, true ), knots::bound( Whole.inner, highfade, false ), false, ramp( upper ), fade }, + { owntint, knots::bound( Whole.extents, beyond, true ), knots::bound( Whole.inner, upper, false ), false, ramp( upper ), -fade }, + { null_handle, Whole.extents, knots::bound( Whole.inner, beyond, false ), Whole.rolled } }, + [&]( double const Middle ) { + auto const from { knots::sample( lower, Middle, 0.0 ) }; + auto const to { knots::sample( upper, Middle, 0.0 ) }; + return std::vector { from - fade, from, knots::sample( lowfade, Middle, 0.0 ), knots::sample( highfade, Middle, 0.0 ), to, to + fade }; }, + Ends ); +} + +gfx::vertex_array TTrack::rail_layer::lay_sides( rail_piece const &Whole, piece_ends const &Ends ) { + + gfx::vertex_array vertices; + if( m_segment.RenderLoftSpan( vertices, m_track.m_origin, m_profile, span( Whole, Ends ) ) ) { + cut_faces( vertices, m_profile.size(), Whole.capfrom, Whole.capto, m_drawing.bank, sink( Whole.material, true ) ); + } + auto side { Whole }; + side.capfrom = false; + side.capto = false; + piece( strip( 0, m_topfirst ), side, Ends ); + piece( strip( m_toplast, m_profile.size() - 1 ), side, Ends ); + return strip( m_topfirst, m_toplast ); +} + +void TTrack::rail_layer::lay_top( gfx::vertex_array const &Top, rail_piece const &Whole, glm::dvec2 const &Reach, std::vector const &Splits, std::vector const &Bands, std::function( double )> const &Edges, piece_ends const &Ends ) { + + for( std::size_t i = 1; i < Splits.size(); ++i ) { + auto const middle { 0.5 * ( Splits[ i - 1 ] + Splits[ i ] ) }; + auto const least { std::max( Reach.x, knots::sample( Whole.inner, middle, Reach.x ) ) }; + auto const most { std::min( Reach.y, knots::sample( Whole.extents, middle, Reach.y ) ) }; + auto edges { Edges( middle ) }; + edges.insert( edges.begin(), -1e9 ); + edges.push_back( 1e9 ); + for( std::size_t k = 0; k < Bands.size(); ++k ) { + auto const &band { Bands[ k ] }; + if( edges[ k + 1 ] > edges[ k ] + 1e-6 && edges[ k + 1 ] > least && edges[ k ] < most ) { + piece( + Top, + { .material = band.material, .from = Splits[ i - 1 ], .to = Splits[ i ], .offsets = Whole.offsets, .extents = band.extents, .inner = band.inner, .rolled = band.rolled, .ramp = band.ramp, .fade = band.fade }, + Ends ); + } + } + } +} + +void TTrack::rail_layer::piece( gfx::vertex_array const &Shape, rail_piece const &Piece, piece_ends const &Ends ) { + + gfx::vertex_array vertices; + gfx::userdata_array userdata; + auto loft { span( Piece, Ends ) }; + loft.userdata = ( Piece.rolled ? nullptr : &userdata ); + if( Shape.size() < 2 || false == m_segment.RenderLoftSpan( vertices, m_track.m_origin, Shape, loft ) ) { + return; + } + cut_faces( vertices, Shape.size(), Piece.capfrom, Piece.capto, m_drawing.bank, sink( Piece.material, true ) ); + gfx::vertex_array triangles; + gfx::userdata_array triangleuserdata; + auto const ring { 2 * Shape.size() }; + for( std::size_t block = 0; block + ring <= vertices.size(); block += ring ) { + for( std::size_t j = 0; j + 1 < Shape.size(); ++j ) { + for( auto const index : { 0, 1, 2, 1, 3, 2 } ) { + auto const vertex { block + 2 * j + index }; + triangles.push_back( vertices[ vertex ] ); + if( false == Piece.rolled ) { + triangleuserdata.push_back( userdata[ vertex ] ); + } + } + } + } + if( triangles.empty() ) { + return; + } + sink( Piece.material, false == Piece.rolled ).emplace_back( GfxRenderer->Insert( triangles, triangleuserdata, m_drawing.bank, GL_TRIANGLES ) ); +} + +loft_span TTrack::rail_layer::span( rail_piece const &Piece, piece_ends const &Ends ) const { + + loft_span result; + result.side = m_side; + result.halfwidth = m_drawing.halfwidth; + result.texturelength = texture_length( Piece.material ); + result.from = Piece.from; + result.to = Piece.to; + result.offsets = Piece.offsets; + result.extents = Piece.extents; + result.inner = Piece.inner; + result.lift = Piece.lift; + result.ramp = Piece.ramp; + result.rampwidth = ( Piece.ramp.empty() ? 0.0 : Piece.fade ); + result.walls = Piece.walls; + result.wallu = Piece.wallu; + if( std::abs( Piece.from - Ends.from ) < 1e-6 ) { + result.fromend = Ends.fromend; + } + if( std::abs( Piece.to - Ends.to ) < 1e-6 ) { + result.toend = Ends.toend; + } + return result; +} + +double TTrack::rail_layer::texture_length( material_handle const Material ) const { + + return ( Material == null_handle ? m_texturelength : m_track.texture_length( Material ) ); +} + +// parts no wheel rolls on go in the matte material of the cut faces +TTrack::geometryhandle_sequence &TTrack::rail_layer::sink( material_handle const Material, bool const Matte ) { + + if( Material == null_handle ) { + return Matte ? m_cuts : m_geometry; + } + if( Material == m_track.m_material1 ) { + return Matte ? m_track.GeometryCut1 : m_track.Geometry1; + } + if( m_track.eType == tt_Switch && Material == m_track.m_material2 ) { + return Matte ? m_track.GeometryCut2 : m_track.Geometry2; + } + auto const tinted { std::find( m_tints.begin(), m_tints.end(), Material ) != m_tints.end() }; + auto &treads { m_track.GeometryTreads }; + auto found { std::find_if( treads.begin(), treads.end(), [&]( tread_geometry const &Tread ) { return Tread.material == Material; } ) }; + if( found == treads.end() ) { + found = treads.insert( treads.end(), { Material, tinted ? Material : cut_material( Material ), {}, {} } ); + } + return Matte && false == tinted ? found->cuts : found->geometry; +} + +material_handle TTrack::rail_layer::paint( material_handle const Material, glm::vec3 const &Colour ) { + + auto const tinted { tint_material( Material, Colour ) }; + if( tinted != Material ) { + m_tints.push_back( tinted ); + } + return tinted; +} + +glm::dvec2 TTrack::rail_layer::reaches( gfx::vertex_array const &Shape ) const { + + glm::dvec2 result { 1e9, -1e9 }; + for( auto const &point : Shape ) { + auto const reach { static_cast( m_side * point.position.x - m_drawing.halfwidth ) }; + result = { std::min( result.x, reach ), std::max( result.y, reach ) }; + } + return result; +} + +gfx::vertex_array TTrack::rail_layer::strip( std::size_t const First, std::size_t const Last ) const { + + return gfx::vertex_array( m_profile.begin() + First, m_profile.begin() + Last + 1 ); +} + +bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) { + + return rail_layer( *this, Layout, Drawing, Path, Profile, Side, Texturelength, From, Openstart, Openend, Geometry, Cuts ).lay(); +} diff --git a/world/RailLayout.h b/world/RailLayout.h new file mode 100644 index 00000000..5597cdea --- /dev/null +++ b/world/RailLayout.h @@ -0,0 +1,185 @@ +/* +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 +#include +#include +#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 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 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 }; + +// 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 gauge; + std::vector extents; + std::vector drops; + std::vector 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 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 turn away from the running rail +struct check_rail { + double from { 0.0 }; + double to { 0.0 }; + double flarefrom { 0.0 }; + double flareto { 0.0 }; + double frombend { 0.0 }; + double tobend { 0.0 }; +}; +using check_rail_set = std::array, 2>, 2>; // path, right side +inline double check_flare( check_rail const &Check ) { + + return std::min( guard_flare, 0.4 * ( Check.to - Check.from ) ); +} +// 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 }; +}; +struct rail_plan { + std::vector 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 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 bands[ 2 ][ 2 ]; + std::vector fills[ 2 ][ 2 ]; + knots::line blocks[ 2 ][ 2 ]; + std::vector treads[ 2 ][ 2 ]; + std::vector 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 crossed; // tracks whose rails cross the rails of this one +}; + +} // namespace track_rails diff --git a/world/RailMaterials.cpp b/world/RailMaterials.cpp new file mode 100644 index 00000000..b3a94b09 --- /dev/null +++ b/world/RailMaterials.cpp @@ -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 band_columns( glm::vec2 const &Band, int const Width ) { + + auto const first { std::clamp( static_cast( std::floor( std::min( Band.x, Band.y ) * Width ) ), 0, Width - 1 ) }; + auto const last { std::clamp( static_cast( 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( Block[ 0 ] | Block[ 1 ] << 8 ) }; + auto const top { static_cast( 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( Block[ 4 ] ) | static_cast( Block[ 5 ] ) << 8 | static_cast( Block[ 6 ] ) << 16 | static_cast( 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 header {}; + if( !input.read( reinterpret_cast( 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( header[ Offset ] ) | static_cast( header[ Offset + 1 ] ) << 8 | static_cast( header[ Offset + 2 ] ) << 16 | static_cast( header[ Offset + 3 ] ) << 24; }; + auto const height { static_cast( word( 12 ) ) }; + auto const width { static_cast( 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 data( static_cast( wide ) * high * blocksize ); + if( !input.read( reinterpret_cast( 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( 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( y ) * width + x ) * 4 ] }; + Mean.add( pixel[ 0 ], pixel[ 1 ], pixel[ 2 ] ); + } + } + stbi_image_free( pixels ); +} + +} // namespace + +std::pair const &tread_sample( material_handle const Material, glm::vec2 const &Band ) { + + static std::map, std::pair> 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( mean.sum / mean.count ), glm::vec3{ mean.colour / static_cast( 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 ); +} diff --git a/world/RailPlan.cpp b/world/RailPlan.cpp new file mode 100644 index 00000000..d3869015 --- /dev/null +++ b/world/RailPlan.cpp @@ -0,0 +1,736 @@ +/* +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 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 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( 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( 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; +} + +// acute frog: the rail coming from the throat bends out as a wing rail, the rail going on behind the gap is a leg of the nose +void acute( track_rails::rail_plan &Plan, double const At, double const Toward, double const Slope, std::vector const &Extension, std::vector const &Legs ) { + + auto const throat { At + Toward * flangeway / Slope }; + auto const flare { At - Toward * wing_reach( Slope ) }; + auto const nose { Toward < 0.0 ? Legs.front().x : Legs.back().x }; + std::vector wing { { throat, 0.0 }, { flare, std::abs( flare - throat ) * Slope } }; + wing.insert( wing.end(), Extension.begin(), Extension.end() ); + auto const wingend { Extension.empty() ? flare : Extension.back().x }; + std::vector rolled; + for( auto const &knot : wing ) { + rolled.emplace_back( knot.x, rolled_band - knot.y ); + } + knots::sort( rolled ); + track_rails::rail_cut cut; + cut.at = At; + if( Toward < 0.0 ) { + cut.end = wingend; + cut.endoffsets = wing; + cut.endrolled = rolled; + cut.start = nose; + cut.startextents = Legs; + } + else { + cut.end = nose; + cut.endextents = Legs; + cut.start = wingend; + cut.startoffsets = wing; + cut.startrolled = rolled; + } + Plan.cuts.push_back( cut ); +} + +// obtuse crossing: the rail ends where it meets the other one and starts again pointed past its flangeway, a check rail beside the point guards the gap +void obtuse( track_rails::rail_plan &Plan, double const At, double const Into, track_rails::rail_size const &Size ) { + + auto const gap { flangeway / std::abs( Into ) }; + auto const full { Size.high + 0.0135 }; + auto const taper { ( flangeway + full ) / std::abs( Into ) }; + auto const bend { knuckle_bend( Into ) }; + track_rails::rail_cut cut; + cut.at = At; + if( Into > 0.0 ) { + cut.end = At; + cut.endbend = -bend; + cut.start = At + gap; + cut.startextents = { { At + gap, 0.006 }, { At + taper, full } }; + } + else { + cut.end = At - gap; + cut.endextents = { { At - taper, full }, { At - gap, 0.006 } }; + cut.start = At; + cut.startbend = bend; + } + 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 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( 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 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 guard_fills( track_rails::guard_rail const &Guard, track_rails::rail_size const &Size ) { + + auto const offset { Size.web - ( flangeway + Size.head ) }; + auto const flare { std::min( guard_flare, 0.25 * ( Guard.to - Guard.from ) ) }; + track_rails::rail_fill whole; + whole.web = true; + whole.lift = ( Guard.through ? 0.0 : -0.001 ); + whole.inner = { { Guard.from, offset - ( Guard.flarefrom ? 0.02 : 0.0 ) }, { Guard.from + flare, offset }, { Guard.to - flare, offset }, { Guard.to, offset - ( Guard.flareto ? 0.02 : 0.0 ) } }; + whole.outer = { { Guard.from, Size.web }, { Guard.to, Size.web } }; + return spacer_fills( whole, Guard.from, Guard.to, Guard.castings ); +} + +std::vector 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 &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 line { { apex, edge + sign * reach }, { end, edge + sign * ( gap + reach ) } }; + knots::sort( line ); + std::vector 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 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 &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 knots; + auto below { 0 }; + auto const count { std::max( 2, static_cast( std::ceil( ( To - From ) / 0.1 ) ) + 1 ) }; + for( int i = 0; i < count; ++i ) { + auto distance { glm::mix( From, To, static_cast( 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; +} + +std::vector nose_extents( TTrack const &Track, int const Path, int const Side, TTrack const &Other, int const Otherpath, int const Otherside, double const At, double const Toward, double const Slope, track_rails::rail_size const &Size ) { + + auto const half { Slope / ( 1.0 + std::sqrt( 1.0 + Slope * Slope ) ) }; + auto const tip { 0.5 * nose_tip }; + auto const apart { Size.high + 0.01 }; + auto const nose { At - Toward * tip / half }; + auto const blunt { At - Toward * apart / half }; + std::vector straight { { std::min( nose, blunt ), Toward < 0.0 ? tip : apart }, { std::max( nose, blunt ), Toward < 0.0 ? apart : tip } }; + 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( nose, point, tangent ); + auto otherat { nearest_distance( theirs, point ) }; + auto extent { tip }; + auto const step { 0.1 }; + auto const limit { static_cast( std::ceil( 3.0 * std::abs( blunt - nose ) / step ) ) }; + std::vector knots; + for( int i = 0; i <= limit; ++i ) { + auto const distance { nose - Toward * step * i }; + ours.at( distance, point, tangent ); + auto const normal { ours.normal( distance, Side ) }; + for( int iteration = 0; iteration < 6; ++iteration ) { + extent += 0.5 * ( lateral( theirs, Otherside, point - normal * extent, otherat ) - extent ); + } + if( extent >= apart && false == knots.empty() ) { + auto const &last { knots.back() }; + knots.emplace_back( glm::mix( last.x, distance, ( apart - last.y ) / ( extent - last.y ) ), apart ); + break; + } + knots.emplace_back( distance, std::max( extent, 0.0 ) ); + } + if( knots.size() < 2 || knots.back().y < apart ) { + return straight; + } + knots::sort( knots ); + return knots; +} + +} // namespace + +namespace { + +void merge_guard( std::vector &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 ( &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 ); +// 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 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; + rail_size const m_size; + double const m_guardoffset; + rail_layout m_layout; + std::vector m_corners; +}; + +TTrack::rail_planner::rail_planner( TTrack &Track, bool const Crossings ) : + m_track { Track }, + m_crossings { Crossings }, + m_size { Track.rail_dimensions() }, + m_guardoffset { -( flangeway + m_size.head ) } +{} + +rail_layout TTrack::rail_planner::lay_out() { + + std::vector 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() ); + } + } + } + } + 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 { + obtuse( plan_of( Crossing ), Crossing.at, Crossing.into, m_size ); + 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 ); + acute( plan, Crossing.at, Toward, slope, + 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 ), + nose_extents( m_track, Crossing.path, Crossing.side, *Crossing.track, Crossing.otherpath, Crossing.otherside, Crossing.at, Toward, slope, m_size ) ); + auto const nose { Toward < 0.0 ? plan.cuts.back().start : plan.cuts.back().end }; + 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 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; + } + acute_frog( crossing, -1.0 ); + } +} + +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 ) { + + return rail_planner( *this, Crossings ).lay_out(); +} diff --git a/world/RailProfile.cpp b/world/RailProfile.cpp new file mode 100644 index 00000000..e8d5b244 --- /dev/null +++ b/world/RailProfile.cpp @@ -0,0 +1,128 @@ +/* +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( point.y ) ); + bottom = std::min( bottom, static_cast( point.y ) ); + result.low = std::min( result.low, static_cast( point.x ) ); + result.high = std::max( result.high, static_cast( 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( 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( point.x ) ); + } + } + result.tread = result.head; + std::vector 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( 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( Profile[ i ].position.x ) ); + result.liptop = std::max( result.liptop, static_cast( 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( 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( 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 diff --git a/world/Segment.cpp b/world/Segment.cpp index 1b335ddc..16b98d5e 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -645,50 +645,38 @@ 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 const &Offsets, std::vector const &Extents, float const Lift, std::vector const &Inner, gfx::userdata_array *Userdata, std::vector const &Ramp, double const Rampwidth, bool const Walls, float const Wallu, glm::dvec2 const &Bends, std::vector const &Drops, glm::dvec2 const &Head ) const { +bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, loft_span const &Span ) const { - if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() ) + if( fTsBuffer.empty() || Span.to - Span.from < 0.001 || ShapePoints.empty() ) return false; - auto const sample = []( std::vector const &Knots, double const Distance, double const Default ) { - if( Knots.empty() ) - return Default; - if( Distance <= Knots.front().x ) - return Knots.front().y; - for( std::size_t i = 1; i < Knots.size(); ++i ) { - if( Distance <= Knots[ i ].x ) { - return glm::mix( Knots[ i - 1 ].y, Knots[ i ].y, ( Distance - Knots[ i - 1 ].x ) / std::max( Knots[ i ].x - Knots[ i - 1 ].x, 1e-6 ) ); - } - } - return Knots.back().y; }; - - std::vector stations { From, To }; + std::vector stations { Span.from, Span.to }; auto const step { std::max( fStep, 0.2 ) }; - for( auto distance { std::ceil( From / step ) * step }; distance < To; distance += step ) { + for( auto distance { std::ceil( Span.from / step ) * step }; distance < Span.to; distance += step ) { stations.push_back( distance ); } - for( auto distance { std::ceil( From / 0.5 ) * 0.5 }; distance < std::min( To, m_dense ); distance += 0.5 ) { + 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 *knots : { &Offsets, &Extents, &Inner, &Drops } ) { - for( auto const &knot : *knots ) { - if( knot.x > From && knot.x < To ) { + 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 *knots : { &Extents, &Inner } ) { - for( std::size_t i = 1; i < knots->size(); ++i ) { - auto const &a { ( *knots )[ i - 1 ] }; - auto const &b { ( *knots )[ i ] }; + 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( Side * point.position.x - Halfwidth ) }; + auto const reach { static_cast( 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 > From && distance < To ) { + if( t > 0.0 && t < 1.0 && distance > Span.from && distance < Span.to ) { stations.push_back( distance ); } } @@ -696,14 +684,16 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } std::sort( stations.begin(), stations.end() ); stations.erase( std::unique( stations.begin(), stations.end(), []( double const A, double const B ) { return B - A < 0.001; } ), stations.end() ); - if( Bends.x != 0.0 || Bends.y != 0.0 ) { + 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( Side * point.position.x - Halfwidth ) ) ); + widest = std::max( widest, std::abs( static_cast( 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 > From && Distance < From + std::abs( Bends.x ) * widest + 0.001 ) - || ( Distance < To && Distance > To - std::abs( Bends.y ) * widest - 0.001 ); } ), stations.end() ); + 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 &Ring, std::vector &Wear ) { @@ -716,28 +706,31 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } auto const position { glm::vec3{ point - Origin } }; auto const parallel { glm::vec3{ across } }; - auto const offset { sample( Offsets, Distance, 0.0 ) }; - auto const extent { sample( Extents, Distance, 1e6 ) }; - auto const innerextent { sample( Inner, Distance, -1e6 ) }; - auto const middleextent { sample( Extents, Middle, 1e6 ) }; - auto const middleinner { sample( Inner, Middle, -1e6 ) }; + 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( Walls ) { + if( Span.walls ) { auto const delta { 0.001 }; glm::dvec3 ahead, behind, unused; plan_frame( std::min( Distance + delta, static_cast( 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( Side ) }; - auto const slope = [&]( std::vector const &Knots, double const Default ) { - return ( sample( Knots, Distance + delta, Default ) - sample( Knots, Distance - delta, Default ) + sample( Offsets, Distance + delta, 0.0 ) - sample( Offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); }; - outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Extents, 1e6 ) ) }; - innerwall = glm::vec3{ glm::normalize( tangent * slope( Inner, -1e6 ) - outward ) }; + auto const outward { glm::dvec3{ across } * static_cast( 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 { 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 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; @@ -745,14 +738,14 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig plan_frame( std::max( Distance - 0.001, 0.0 ), behind, unused ); auto const tangent { glm::dvec3 { ahead.x - behind.x, 0.0, ahead.z - behind.z } }; if( glm::length2( tangent ) > 0.0 ) { - along = glm::vec3 { glm::normalize( tangent ) * bend }; + along = glm::vec3 { glm::normalize( tangent ) } * static_cast( bend ); } } Ring.clear(); Wear.clear(); auto const count { ShapePoints.size() }; auto const reach = [&]( std::size_t const Index ) { - return static_cast( Side * ShapePoints[ Index ].position.x - Halfwidth ); }; + return static_cast( 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 ) { @@ -769,7 +762,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig return Index < count && ( side > 0 ? reach( Index ) <= middleextent : reach( Index ) >= middleinner ); }; auto first { j }; auto last { j }; - if( Walls ) { + if( Span.walls ) { while( first > 0 && twin( first - 1, j ) ) { --first; } @@ -787,7 +780,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig continue; } auto const &other { ShapePoints[ neighbour ] }; - auto const t { static_cast( ( limit - reach( neighbour ) ) / ( side > 0 ? std::max( outside - reach( neighbour ), 1e-6 ) : std::min( outside - reach( neighbour ), -1e-6 ) ) ) }; + auto const t { std::clamp( static_cast( ( 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 ) }; @@ -821,34 +814,34 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig shape.position.y = std::max( shape.position.y, bottom ); } } - outside = limit; + 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 > Head.x && Head.y > Head.x ) { - shape.position.y -= static_cast( drop * ( shape.position.y - Head.x ) / ( Head.y - Head.x ) ); + if( drop > 0.0 && shape.position.y > Span.head.x && Span.head.y > Span.head.x ) { + shape.position.y -= static_cast( drop * ( shape.position.y - Span.head.x ) / ( Span.head.y - Span.head.x ) ); } - auto pt { parallel * static_cast( Side * ( Halfwidth + outside + offset ) ) + position + along * static_cast( outside + offset ) }; - pt.y += shape.position.y + Lift; + auto pt { parallel * static_cast( Span.side * ( Span.halfwidth + outside + offset ) ) + position + along * static_cast( 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( Wallu >= 0.f ) { - shape.texture.x = Wallu; + 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( Rampwidth != 0.0 ? static_cast( std::clamp( ( Rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f ); + Wear.push_back( Span.rampwidth != 0.0 ? static_cast( 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( fTextureLength ) }; + auto const texturelength { static_cast( Span.texturelength ) }; std::vector ring1, ring2; std::vector wear1, wear2; - float tv1 { clamp_circular( 1.f - static_cast( std::fmod( From, fTextureLength ) / fTextureLength ), 1.0f ) }; + float tv1 { clamp_circular( 1.f - static_cast( 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 ); @@ -858,9 +851,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig for( std::size_t j = 0; j < ring1.size(); ++j ) { Output.emplace_back( ring1[ j ].position, ring1[ j ].normal, glm::vec2{ ring1[ j ].texture.x, tv1 } ); Output.emplace_back( ring2[ j ].position, ring2[ j ].normal, glm::vec2{ ring2[ j ].texture.x, tv2 } ); - if( Userdata != nullptr ) { - Userdata->push_back( { glm::vec4{ wear1[ j ], 0.f, 0.f, 0.f } } ); - Userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } ); + 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; diff --git a/world/Segment.h b/world/Segment.h index b8a947ee..8df81d4a 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include "world/Knots.h" struct map_colored_paths { std::vector switches; @@ -23,6 +24,31 @@ struct map_colored_paths { std::vector 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 { @@ -157,7 +183,7 @@ 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 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 const &Offsets, std::vector const &Extents = {}, float const Lift = 0.f, std::vector const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector 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 const &Drops = {}, glm::dvec2 const &Head = glm::dvec2 { 0.0 } ) const; + 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; diff --git a/world/Track.cpp b/world/Track.cpp index 0fbbb693..7bec6e73 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1460,20 +1460,20 @@ void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { void TTrack::share_joints() { - for( int path = 0; path < ( eType == tt_Switch ? 2 : 1 ); ++path ) { + 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> joint; - auto *neighbour { eType == tt_Switch ? ( end ? SwitchExtension->pNexts[ path ] : SwitchExtension->pPrevs[ path ] ) : ( end ? trNext : trPrev ) }; + 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 < ( neighbour->eType == tt_Switch ? 2 : 1 ); ++theirpath ) { + 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; @@ -1601,7 +1601,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { 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, 0.5f * std::abs( fTrackWidth ), Texturelength, shape.heel, SwitchExtension->Segments[ Path ]->GetLength(), {} ); + 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() } ); } else { SwitchExtension->Segments[ Path ]->RenderLoft( vertices, m_origin, Profile, false, Texturelength, 1.0, track_rails::blade_steps( *this, Path ) ); @@ -2157,7 +2157,7 @@ void TTrack::loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::ver 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 } ); + SwitchExtension->Segments[ Path ]->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = shape.tip, .to = shape.heel, .offsets = offsets, .extents = shape.extents, .walls = true, .drops = shape.drops, .head = { size.neck, size.crest } } ); return; } auto const bladelength { track_rails::blade_steps( *this, Path ) }; @@ -2730,121 +2730,13 @@ TTrack::track_rail_profile( int const Profile ) { return m_profiles[ Profile ]; } -TTrack::rail_size +track_rails::rail_size TTrack::rail_dimensions() const { - rail_size result; if( m_profiles.empty() ) { - return result; + return {}; } - auto const &profile { track_rail_profile( m_profile1.second ) }; - auto const count { profile.size() / 2 }; - if( count < 2 ) { - return result; - } - auto top { -1e9 }; - auto bottom { 1e9 }; - result.low = 1e9; - result.high = -1e9; - for( std::size_t i = 0; i < count; ++i ) { - auto const &point { profile[ i ].position }; - top = std::max( top, static_cast( point.y ) ); - bottom = std::min( bottom, static_cast( point.y ) ); - result.low = std::min( result.low, static_cast( point.x ) ); - result.high = std::max( result.high, static_cast( 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( 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( point.x ) ); - } - } - result.tread = result.head; - std::vector 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( 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( profile[ i ].position.x ) ); - result.liptop = std::max( result.liptop, static_cast( 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( 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( 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; + return track_rails::measure_rail( track_rail_profile( m_profile1.second ) ); } float diff --git a/world/Track.h b/world/Track.h index 01e94044..e7420c9a 100644 --- a/world/Track.h +++ b/world/Track.h @@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Classes.h" #include "world/Segment.h" +#include "world/RailLayout.h" #include "model/material.h" #include "scene/scenenode.h" #include "utilities/Names.h" @@ -299,145 +300,13 @@ public: int RouteDraw() const; void Load(cParser *parser, glm::dvec3 const &pOrigin); void init_segments( bool const Initial ); - // where the inner rails of the two paths of a switch cross, found from their gauge lines behind the blades - struct switch_frog { - bool valid { false }; - int side[ 2 ] {}; // rail profile of the crossing rail of each path, 1 right, -1 left - double at[ 2 ] {}; // distance of the crossing along each path - double blades[ 2 ] {}; // distance the blades take along each path - double angle { 0.0 }; - }; - switch_frog frog() const; - 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 gauge; - std::vector extents; - std::vector drops; - std::vector 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 }; - int path { 0 }; - int side { 0 }; // rail profile of the crossing rail, 1 right, -1 left - double at { 0.0 }; // distance of the crossing along the path, past the ends where the track goes on - double angle { 0.0 }; - double into { 0.0 }; // the path direction against the gauge normal of the other rail, above zero: going into the other track - double facing { 0.0 }; // the gauge normals of both rails against each other, below zero: an acute frog - bool nose { false }; // at an acute frog the rail goes on as the nose rail, the other one as the rail beside it - int otherpath { 0 }; - int otherside { 0 }; // rail profile of the other rail, 1 right, -1 left - double otherat { 0.0 }; // distance of the crossing along the other path - bool aligned { true }; // both paths run the same way at the crossing - TTrack *owner { nullptr }; - }; - std::vector rail_crossings( bool const Joined = true ); - // the size of the rail profile, out from the gauge face: positive outwards, the side of the head away from the track - struct rail_size { - double head { 0.0735 }; // how far the head reaches out - double height { 0.172 }; - double low { -0.0385 }; // the reach of the profile inwards - double high { 0.1115 }; // the reach of the profile outwards - bool grooved { false }; - double groove { 0.0 }; - double lip { 0.0 }; - double rim { 0.0 }; - double crown { 0.0 }; - double liptop { 0.0 }; - double crest { 0.0 }; - double tread { 0.0735 }; - double ledge { 0.0 }; - double web { 0.0365 }; - double neck { -0.0467 }; - }; - rail_size rail_dimensions() const; - // a check rail beside a rail, how far its ends turn away from the running rail - struct check_rail { - double from { 0.0 }; - double to { 0.0 }; - double flarefrom { 0.0 }; - double flareto { 0.0 }; - double frombend { 0.0 }; - double tobend { 0.0 }; - }; - using check_rail_set = std::array, 2>, 2>; // path, right side + track_rails::switch_frog frog() const; + track_rails::switch_blade const &blade( int const Path ) const; + std::vector 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 - check_rail_set check_rails( std::vector const &Crossings ); - // a rail broken where another one crosses it: the part coming from below ends at end, the part going on starts at start - struct rail_cut { - double at { 0.0 }; - double end { 0.0 }; - double start { 0.0 }; - std::vector endoffsets, endextents, startoffsets, startextents; - std::vector endrolled, startrolled; // how far out from the gauge line the wheels roll on the head, of the part coming from below and of the part going on - glm::dvec2 endmiter { 0.0 }; - glm::dvec2 startmiter { 0.0 }; - double endbend { 0.0 }; - double startbend { 0.0 }; - std::vector endinner, startinner; - bool endcap { true }; - bool startcap { true }; - }; - struct rail_plan { - std::vector cuts; - }; - // guard rails of a crossing at a wide angle: the rail profile laid inside the running rail across the flangeway, broken like the rails - struct guard_rail { - double from { 0.0 }; - double to { 0.0 }; - rail_plan plan; - bool flarefrom { true }; - bool flareto { true }; - glm::dvec2 frommiter { 0.0 }; - glm::dvec2 tomiter { 0.0 }; - bool through { true }; - }; - struct rail_band { - double at { 0.0 }; - double into { 0.0 }; - double facing { 0.0 }; - double from { 0.0 }; - double to { 0.0 }; - bool whole { true }; - }; - struct rail_fill { - double from { 0.0 }; - double to { 0.0 }; - std::vector inner, outer; - bool capfrom { false }; - bool capto { false }; - double lift { 0.0 }; - bool web { false }; - }; - struct rail_tread { - double from { 0.0 }; - double to { 0.0 }; - material_handle material { 0 }; - int anchor { 0 }; - std::vector edge; - std::vector gauge; - bool below { false }; - }; - struct rail_layout { - rail_plan plans[ 2 ][ 2 ]; // path, right side - std::vector bands[ 2 ][ 2 ]; - std::vector fills[ 2 ][ 2 ]; - std::vector blocks[ 2 ][ 2 ]; - std::vector treads[ 2 ][ 2 ]; - std::vector guards[ 2 ][ 2 ]; - std::vector 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 crossed; // tracks whose rails cross the rails of this one - }; - rail_layout plan_rails( bool const Crossings ); + track_rails::check_rail_set check_rails( std::vector const &Crossings ); + track_rails::rail_layout plan_rails( bool const Crossings ); void update_location(); bool AssignEvents(); bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus); @@ -534,7 +403,7 @@ private: void build_sleeper_transforms(); struct rail_drawing { gfx::geometrybank_handle bank; - rail_size size; + track_rails::rail_size size; float halfwidth { 0.f }; gfx::vertex_array checkshapes[ 2 ]; gfx::vertex_array plates[ 2 ]; @@ -543,7 +412,9 @@ private: }; rail_drawing make_rail_drawing( gfx::geometrybank_handle const &Bank ); // a rail along a path from a distance on, in parts where other rails cross it, and the check rails beside it. false: no crossing, the rail is laid whole as before - bool lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ); + 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 rail_material( int const Path ) const; glm::vec2 tread_band() const; diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp deleted file mode 100644 index 31f7b262..00000000 --- a/world/TrackRails.cpp +++ /dev/null @@ -1,2446 +0,0 @@ -/* -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 "world/Segment.h" -#include "simulation/simulation.h" -#include "scene/scene.h" -#include "rendering/renderer.h" -#include "stb/stb_image.h" - -namespace { - -glm::dvec2 plan( glm::dvec3 const &V ) { - - return glm::dvec2{ V.x, V.z }; -} - -double cross( glm::dvec2 const &A, glm::dvec2 const &B ) { - - return A.x * B.y - A.y * B.x; -} - -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 points; - std::vector 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( -Side ); - } -}; - -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( 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; -} - -bool cut_profile( std::string Profile ) { - - std::transform( Profile.begin(), Profile.end(), Profile.begin(), []( unsigned char const Char ) { return static_cast( std::tolower( Char ) ); } ); - return Profile.empty() || Profile == "default" || Profile == "s60" || Profile == "uic60" || Profile == "s39"; -} - -bool joined( TTrack const &Track, TTrack const &Other ) { - - auto const next = []( TTrack const &From, TTrack const &To ) { - if( From.trPrev == &To || From.trNext == &To ) { - return true; - } - if( From.eType == tt_Switch && From.SwitchExtension ) { - for( int i = 0; i < 2; ++i ) { - if( From.SwitchExtension->pPrevs[ i ] == &To || From.SwitchExtension->pNexts[ i ] == &To ) { - return true; - } - } - } - return false; }; - return next( Track, Other ) || next( Other, Track ); -} - -TSegment const &path_segment( TTrack const &Track, int const Path ) { - - return ( Track.eType == tt_Switch ? *Track.SwitchExtension->Segments[ Path ] : *Track.Segment ); -} - -double knuckle_bend( double const Into ) { - - return std::abs( Into ) / ( 1.0 + std::sqrt( std::max( 0.0, 1.0 - Into * Into ) ) ); -} - -// a check rail starting right beside the end of a wing rail running along the same rail, which the wing rail goes on in place of; the far end of it -bool replaced_check( std::vector const &Checks, double const Wingend, double const Direction, double &Far ) { - - for( auto const &check : Checks ) { - auto const nearend { Direction > 0.0 ? check.from : check.to }; - auto const farend { Direction > 0.0 ? check.to : check.from }; - auto const gap { ( nearend - Wingend ) * Direction }; - if( gap > -0.5 && gap < 1.0 && ( farend - Wingend ) * Direction > 0.5 ) { - Far = farend; - return true; - } - } - return false; -} - -struct rails_memo { - std::optional lines[ 2 ][ 2 ][ 2 ]; // path, right side, from the blades - std::optional> own; - std::optional> joined; - std::optional checks; - std::optional blades[ 2 ]; -}; -std::unordered_map memos; -unsigned int memostamp { 0 }; -unsigned int stamp { 1 }; - -rails_memo &memo_of( TTrack const &Track ) { - - if( memostamp != stamp ) { - memos.clear(); - memostamp = stamp; - } - return memos[ &Track ]; -} - -} // namespace - -namespace track_rails { - -void forget() { - - ++stamp; -} - - -double wing_reach( double const Slope ) { - - return 0.15 / Slope + 0.3; -} - -double check_length( double const Slope ) { - - return std::clamp( 0.5 / Slope, 3.6, 9.0 ); -} - -// 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 ) { - - return ( Track.iCategoryFlag & 15 ) == 1 - && Track.m_material1 != null_handle - && false == Track.m_editorremoved - && cut_profile( Track.m_profile1.first ) - && ( Track.eType == tt_Normal ? Track.Segment != nullptr : - 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 const Path ) { - - return static_cast( 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, Path ) / segment.RaSegCount(); -} - -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 ); -} - -std::pair const &tread_sample( material_handle const Material, glm::vec2 const &Band ) { - - static std::map, std::pair> 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 }; - auto const columns = [&]( int const Width ) { - auto const first { std::clamp( static_cast( std::floor( std::min( Band.x, Band.y ) * Width ) ), 0, Width - 1 ) }; - auto const last { std::clamp( static_cast( std::ceil( std::max( Band.x, Band.y ) * Width ) ) - 1, first, Width - 1 ) }; - return std::pair{ first, last }; }; - auto sum { 0.0 }; - glm::dvec3 colour { 0.0 }; - std::size_t count { 0 }; - auto const 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; }; - if( type == ".dds" ) { - std::ifstream input( file, std::ios::binary ); - std::array header {}; - if( input.read( reinterpret_cast( header.data() ), header.size() ) && std::equal( header.begin(), header.begin() + 4, "DDS " ) ) { - auto const word = [&]( std::size_t const Offset ) { - return static_cast( header[ Offset ] ) | static_cast( header[ Offset + 1 ] ) << 8 | static_cast( header[ Offset + 2 ] ) << 16 | static_cast( header[ Offset + 3 ] ) << 24; }; - auto const height { static_cast( word( 12 ) ) }; - auto const width { static_cast( 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 ) { - auto const wide { ( width + 3 ) / 4 }; - auto const high { ( height + 3 ) / 4 }; - std::vector data( static_cast( wide ) * high * blocksize ); - if( input.read( reinterpret_cast( data.data() ), data.size() ) ) { - auto const [ first, last ] { columns( width ) }; - auto const unpack = []( 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 }; }; - for( int row = 0; row < high; ++row ) { - for( int column = first / 4; column <= last / 4; ++column ) { - auto const *block { &data[ ( static_cast( row ) * wide + column ) * blocksize + ( blocksize - 8 ) ] }; - auto const low { static_cast( block[ 0 ] | block[ 1 ] << 8 ) }; - auto const top { static_cast( block[ 2 ] | block[ 3 ] << 8 ) }; - auto const opaque { blocksize == 16 || low > top }; - glm::vec3 const colors[ 4 ] { - unpack( low ), - unpack( top ), - opaque ? ( 2.f * unpack( low ) + unpack( top ) ) / 3.f : 0.5f * ( unpack( low ) + unpack( top ) ), - opaque ? ( unpack( low ) + 2.f * unpack( top ) ) / 3.f : glm::vec3{ 0.f } }; - auto const bits { static_cast( block[ 4 ] ) | static_cast( block[ 5 ] ) << 8 | static_cast( block[ 6 ] ) << 16 | static_cast( 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; - } - add( colors[ index ].r, colors[ index ].g, colors[ index ].b ); - } - } - } - } - } - } - } - else { - int width, height, components; - if( auto *pixels { stbi_load( file.c_str(), &width, &height, &components, 4 ) } ) { - auto const [ first, last ] { columns( width ) }; - for( int y = 0; y < height; ++y ) { - for( int x = first; x <= last; ++x ) { - auto const *pixel { &pixels[ ( static_cast( y ) * width + x ) * 4 ] }; - add( pixel[ 0 ], pixel[ 1 ], pixel[ 2 ] ); - } - } - stbi_image_free( pixels ); - } - } - if( count > 0 ) { - result = { static_cast( sum / count ), glm::vec3{ colour / static_cast( 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; -} - -// in the colour of the top of the rail foot -void cap_loft( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::vertex_array &Triangles ) { - - if( Ring < 3 || Strip.size() < 2 * Ring ) { - return; - } - std::vector, glm::vec3>> caps; - if( Start ) { - std::vector ring; - for( std::size_t j = 0; j < Ring; ++j ) { - ring.emplace_back( Strip[ 2 * j ] ); - } - caps.emplace_back( ring, Strip[ 0 ].position - Strip[ 1 ].position ); - } - if( End ) { - auto const base { Strip.size() - 2 * Ring }; - std::vector ring; - for( std::size_t j = 0; j < Ring; ++j ) { - ring.emplace_back( Strip[ base + 2 * j + 1 ] ); - } - caps.emplace_back( ring, Strip[ base + 1 ].position - Strip[ base ].position ); - } - for( auto const &cap : caps ) { - auto const &ring { cap.first }; - if( glm::length2( cap.second ) == 0.f ) { - continue; - } - auto const outward { glm::normalize( cap.second ) }; - auto across { glm::vec3{ -outward.z, 0.f, outward.x } }; - if( glm::length2( across ) == 0.f ) { - continue; - } - across = glm::normalize( across ); - std::vector plan; - auto bottom { 0.0 }, top { 0.0 }; - for( auto const &vertex : ring ) { - auto const offset { vertex.position - ring[ 0 ].position }; - plan.emplace_back( glm::dot( offset, across ), offset.y ); - bottom = std::min( bottom, offset.y ); - top = std::max( top, offset.y ); - } - auto web { ring[ 0 ].texture }; - auto nearest { std::numeric_limits::max() }; - for( std::size_t j = 0; j < Ring / 2; ++j ) { - auto const distance { std::abs( plan[ j ].y - 0.5 * ( bottom + top ) ) }; - if( distance < nearest ) { - nearest = distance; - web = ring[ j ].texture; - } - } - auto lowest { std::numeric_limits::max() }; - for( std::size_t j = 0; j < Ring / 2; ++j ) { - if( ring[ j ].normal.y > 0.5f && plan[ j ].y < lowest ) { - lowest = plan[ j ].y; - web = ring[ j ].texture; - } - } - auto const cross = []( glm::dvec2 const &A, glm::dvec2 const &B, glm::dvec2 const &C ) { - return ( B.x - A.x ) * ( C.y - A.y ) - ( B.y - A.y ) * ( C.x - A.x ); }; - double area { 0.0 }; - for( std::size_t j = 0; j < plan.size(); ++j ) { - auto const &a { plan[ j ] }; - auto const &b { plan[ ( j + 1 ) % plan.size() ] }; - area += a.x * b.y - b.x * a.y; - } - auto const orientation { area < 0.0 ? -1.0 : 1.0 }; - std::vector left( Ring ); - for( std::size_t j = 0; j < Ring; ++j ) { - left[ j ] = j; - } - std::vector> triangles; - while( left.size() > 3 ) { - auto ear { left.size() }; - for( std::size_t j = 0; j < left.size() && ear == left.size(); ++j ) { - auto const &a { plan[ left[ ( j + left.size() - 1 ) % left.size() ] ] }; - auto const &b { plan[ left[ j ] ] }; - auto const &c { plan[ left[ ( j + 1 ) % left.size() ] ] }; - auto const turn { cross( a, b, c ) * orientation }; - if( turn < -1e-12 ) { - continue; - } - auto inside { false }; - if( turn > 1e-12 ) { - for( auto const k : left ) { - auto const &p { plan[ k ] }; - if( p == a || p == b || p == c ) { - continue; - } - if( cross( a, b, p ) * orientation > 1e-12 && cross( b, c, p ) * orientation > 1e-12 && cross( c, a, p ) * orientation > 1e-12 ) { - inside = true; - break; - } - } - } - if( false == inside ) { - ear = j; - } - } - if( ear == left.size() ) { - ear = 0; - } - triangles.push_back( { left[ ( ear + left.size() - 1 ) % left.size() ], left[ ear ], left[ ( ear + 1 ) % left.size() ] } ); - left.erase( left.begin() + ear ); - } - triangles.push_back( { left[ 0 ], left[ 1 ], left[ 2 ] } ); - for( auto const &triangle : triangles ) { - for( auto const &order : { std::array{ 0, 1, 2 }, std::array{ 0, 2, 1 } } ) { - for( auto const k : order ) { - auto const index { triangle[ k ] }; - Triangles.emplace_back( ring[ index ].position, outward, web ); - } - } - } - } -} - -gfx::userdata_array worn_userdata( std::size_t const Count ) { - - return gfx::userdata_array( Count, gfx::vertex_userdata{ glm::vec4{ 1.f, 0.f, 0.f, 0.f } } ); -} - -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 ) { - - gfx::vertex_array triangles; - cap_loft( Strip, Ring, Start, End, triangles ); - if( false == triangles.empty() ) { - auto userdata { worn_userdata( triangles.size() ) }; - Geometry.emplace_back( GfxRenderer->Insert( triangles, userdata, Bank, GL_TRIANGLES ) ); - } -} - -glm::dvec3 rail_point( TTrack const &Track, int const Path, int const Side, double const Distance, double const Lateral ) { - - auto const line { make_gauge_line( path_segment( Track, Path ), Side * 0.5 * std::abs( Track.fTrackWidth ), 0.0 ) }; - glm::dvec3 point, tangent; - line.at( Distance, point, tangent ); - return point - line.normal( Distance, Side ) * Lateral; -} - -void rail_offset( TTrack const &Track, int const Path, int const Side, glm::dvec3 const &Point, double &Distance, double &Lateral ) { - - auto const line { make_gauge_line( path_segment( Track, Path ), Side * 0.5 * std::abs( Track.fTrackWidth ), 0.0 ) }; - Distance = line.distances.front(); - auto nearest { std::numeric_limits::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; - Distance = line.distances[ i ]; - } - } - 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 ); - Lateral = -glm::dot( plan( Point - point ), plan( line.normal( Distance, Side ) ) ); -} - -} // namespace track_rails - -using namespace track_rails; - -namespace { - -// gauge line of a rail of a track, along a path of a switch from past its blades when Blades -gauge_line const &rail_line( TTrack const &Track, int const Path, int const Side, bool const Blades ) { - - auto const blades { Blades && Track.eType == tt_Switch }; - auto &line { memo_of( Track ).lines[ Path ][ Side ][ blades ] }; - if( false == line.has_value() ) { - line = make_gauge_line( path_segment( Track, Path ), ( Side ? 1.0 : -1.0 ) * ( 0.5 * std::abs( Track.fTrackWidth ) ), blades ? blade_length( Track, Path ) : 0.0 ); - } - return *line; -} - -TTrack::check_rail_set const &rail_checks( TTrack &Track ) { - - auto &checks { memo_of( Track ).checks }; - if( false == checks.has_value() ) { - checks = Track.check_rails( Track.rail_crossings() ); - } - return *checks; -} - -} // namespace - -std::vector TTrack::rail_crossings( bool const Joined ) { - - std::vector result; - if( false == cut_rails( *this ) || simulation::Region == nullptr ) { - return result; - } - auto &memo { memo_of( *this ) }; - if( Joined && memo.joined.has_value() ) { - return *memo.joined; - } - auto const extension { 10.0 }; - auto const paths { eType == tt_Switch ? 2 : 1 }; - if( memo.own.has_value() ) { - result = *memo.own; - } - else { - auto const sections { simulation::Region->sections( location(), static_cast( radius() + 50.0 ) ) }; - std::vector others; - for( auto *section : sections ) { - for( auto const &cell : section->m_cells ) { - if( glm::length( cell.area().center - location() ) > cell.area().radius + radius() + 1.0 ) { - continue; - } - for( auto *other : cell.m_directories.paths ) { - if( other == nullptr || other == this || false == cut_rails( *other ) ) { - continue; - } - if( glm::length( other->location() - location() ) > other->radius() + radius() + 1.0 ) { - continue; - } - if( std::find( others.begin(), others.end(), other ) == others.end() ) { - others.push_back( other ); - } - } - } - } - auto const bend = [&]( gauge_line const &Line, double const At ) { - glm::dvec3 point, before, after; - Line.at( At - 1.0, point, before ); - Line.at( At + 1.0, point, after ); - return std::abs( cross( plan( before ), plan( after ) ) ); }; - auto const heading = []( glm::dvec2 const &Tangent ) { - auto const angle { std::atan2( Tangent.y, Tangent.x ) }; - return ( angle < 0.0 ? angle + M_PI : angle ); }; - auto const overlap = []( gauge_line const &A, gauge_line const &B ) { - return A.low.x <= B.high.x && B.low.x <= A.high.x && A.low.y <= B.high.y && B.low.y <= A.high.y; }; - for( auto *other : others ) { - auto const otherpaths { other->eType == tt_Switch ? 2 : 1 }; - for( int otherpath = 0; otherpath < otherpaths; ++otherpath ) { - auto const &othersegment { path_segment( *other, otherpath ) }; - auto const otherfrom { other->eType == tt_Switch ? blade_length( *other, otherpath ) : 0.0 }; - auto const otherlength { othersegment.GetLength() }; - for( int otherside = 0; otherside < 2; ++otherside ) { - auto const &theirs { rail_line( *other, otherpath, otherside, true ) }; - for( int path = 0; path < paths; ++path ) { - auto const length { path_segment( *this, path ).GetLength() }; - for( int side = 0; side < 2; ++side ) { - auto const &line { rail_line( *this, path, side, false ) }; - if( false == overlap( line, theirs ) ) { - continue; - } - for( std::size_t i = 1; i < line.points.size(); ++i ) { - auto const a0 { plan( line.points[ i - 1 ] ) }; - auto const a { plan( line.points[ i ] ) - a0 }; - auto const alow { glm::min( a0, a0 + a ) - 0.02 * glm::length( a ) - 1e-6 }; - auto const ahigh { glm::max( a0, a0 + a ) + 0.02 * glm::length( a ) + 1e-6 }; - if( alow.x > theirs.high.x + 1.0 || theirs.low.x - 1.0 > ahigh.x || alow.y > theirs.high.y + 1.0 || theirs.low.y - 1.0 > ahigh.y ) { - continue; - } - for( std::size_t j = 1; j < theirs.points.size(); ++j ) { - auto const b0 { plan( theirs.points[ j - 1 ] ) }; - auto const b { plan( theirs.points[ j ] ) - b0 }; - auto const reach { 0.02 * glm::length( b ) }; - if( alow.x > std::max( b0.x, b0.x + b.x ) + reach || std::min( b0.x, b0.x + b.x ) - reach > ahigh.x - || alow.y > std::max( b0.y, b0.y + b.y ) + reach || std::min( b0.y, b0.y + b.y ) - reach > ahigh.y ) { - continue; - } - double u, v; - if( false == meet( a0, a, b0, b, 1e-12, u, v ) ) { - continue; - } - if( u < -0.01 || u > 1.01 || v < -0.01 || v > 1.01 ) { - continue; - } - auto s { glm::mix( line.distances[ i - 1 ], line.distances[ i ], u ) }; - auto t { glm::mix( theirs.distances[ j - 1 ], theirs.distances[ j ], v ) }; - glm::dvec3 p, tp, q, tq; - for( int step = 0; step < 6; ++step ) { - line.at( s, p, tp ); - theirs.at( t, q, tq ); - auto const d { cross( plan( tp ), plan( tq ) ) }; - if( std::abs( d ) < 1e-9 ) { - break; - } - auto const offset { plan( q ) - plan( p ) }; - s += cross( offset, plan( tq ) ) / d; - t += cross( offset, plan( tp ) ) / d; - } - line.at( s, p, tp ); - theirs.at( t, q, tq ); - if( glm::length( plan( p ) - plan( q ) ) > 0.01 - || s < 0.0 || s > length - || t < otherfrom || t > otherlength ) { - continue; - } - if( std::abs( p.y - q.y ) > 0.5 ) { - continue; - } - if( ( s < 0.5 || s > length - 0.5 ) && ( t < 0.5 || t > otherlength - 0.5 ) && joined( *this, *other ) ) { - continue; - } - auto const tangent { glm::normalize( plan( tp ) ) }; - auto const othertangent { glm::normalize( plan( tq ) ) }; - auto const angle { std::acos( std::min( 1.0, std::abs( glm::dot( tangent, othertangent ) ) ) ) }; - if( angle < 0.03 ) { - continue; - } - auto const sidevalue { side ? 1 : -1 }; - if( std::any_of( result.begin(), result.end(), [&]( rail_crossing const &Crossing ) { - return Crossing.path == path && Crossing.side == sidevalue && std::abs( Crossing.at - s ) < 0.05; } ) ) { - continue; - } - auto const normal { glm::normalize( plan( line.normal( s, sidevalue ) ) ) }; - auto const othernormal { glm::normalize( plan( theirs.normal( t, otherside ? 1 : -1 ) ) ) }; - rail_crossing crossing; - crossing.track = other; - crossing.path = path; - crossing.side = sidevalue; - crossing.at = s; - crossing.angle = angle; - crossing.into = glm::dot( tangent, othernormal ); - crossing.facing = glm::dot( normal, othernormal ); - auto const ourbend { bend( line, s ) }; - auto const theirbend { bend( theirs, t ) }; - crossing.nose = ( std::abs( ourbend - theirbend ) > 1e-4 ? ourbend < theirbend : heading( tangent ) < heading( othertangent ) ); - crossing.otherpath = otherpath; - crossing.otherside = ( otherside ? 1 : -1 ); - crossing.otherat = t; - crossing.aligned = glm::dot( tangent, othertangent ) > 0.0; - result.push_back( crossing ); - } - } - } - } - } - } - } - memo.own = result; - } - if( false == Joined ) { - return result; - } - // crossings of the tracks going on from the ends, near enough for their check rails and wings to reach this one - auto const owncount { result.size() }; - for( int path = 0; path < paths; ++path ) { - auto const &segment { path_segment( *this, path ) }; - auto const length { segment.GetLength() }; - for( int end = 0; end < 2; ++end ) { - auto *neighbour { eType == tt_Switch ? ( end ? SwitchExtension->pNexts[ path ] : SwitchExtension->pPrevs[ path ] ) : ( end ? trNext : trPrev ) }; - if( neighbour == nullptr || neighbour == this || false == cut_rails( *neighbour ) ) { - continue; - } - auto const joint { end ? segment.FastGetPoint_1() : segment.FastGetPoint_0() }; - auto const theirs { neighbour->rail_crossings( false ) }; - for( int neighbourpath = 0; neighbourpath < ( neighbour->eType == tt_Switch ? 2 : 1 ); ++neighbourpath ) { - auto const &neighboursegment { path_segment( *neighbour, neighbourpath ) }; - auto const neighbourlength { neighboursegment.GetLength() }; - auto const tostart { glm::length( neighboursegment.FastGetPoint_0() - joint ) }; - auto const toend { glm::length( neighboursegment.FastGetPoint_1() - joint ) }; - if( std::min( tostart, toend ) > 0.05 ) { - continue; - } - auto const atstart { tostart <= toend }; - auto const same { atstart == ( end == 1 ) }; - for( auto const &crossing : theirs ) { - if( crossing.path != neighbourpath || crossing.track == this ) { - continue; - } - auto const along { atstart ? crossing.at : neighbourlength - crossing.at }; - if( along > extension ) { - continue; - } - auto mapped { crossing }; - mapped.path = path; - mapped.at = ( end ? length + along : -along ); - mapped.owner = neighbour; - if( false == same ) { - mapped.side = -crossing.side; - mapped.into = -crossing.into; - mapped.aligned = !crossing.aligned; - } - if( std::none_of( result.begin(), result.begin() + owncount, [&]( rail_crossing const &Crossing ) { - return Crossing.path == mapped.path && Crossing.side == mapped.side && std::abs( Crossing.at - mapped.at ) < 0.05; } ) ) { - result.push_back( mapped ); - } - } - } - } - } - memo.joined = result; - return result; -} - -TTrack::check_rail_set TTrack::check_rails( std::vector const &Crossings ) { - - struct planned { - double from { 0.0 }; - double to { 0.0 }; - bool flarefrom { true }; - bool flareto { true }; - double shortest { 1.5 }; - double flare { 0.02 }; - double frombend { 0.0 }; - double tobend { 0.0 }; - }; - std::vector plans[ 2 ][ 2 ]; - check_rail_set result; - auto const size { rail_dimensions() }; - if( size.grooved ) { - return result; - } - auto const acute = [&]( std::vector &Other, double const At, double const Toward, double const Slope, double const Flare ) { - auto const half { 0.5 * check_length( Slope ) }; - Other.push_back( { At + Toward * 0.25 - half, At + Toward * 0.25 + half, true, true, 1.5, Flare } ); - }; - for( auto const &crossing : Crossings ) { - if( crossing.angle > middle_angle || crossing.track->rail_dimensions().grooved ) { - continue; - } - if( crossing.facing < 0.0 ) { - acute( plans[ crossing.path ][ crossing.side < 0 ], crossing.at, crossing.into > 0.0 ? 1.0 : -1.0, std::tan( crossing.angle ), 0.02 ); - } - else if( crossing.into > 0.0 ) { - plans[ crossing.path ][ crossing.side > 0 ].push_back( { crossing.at, crossing.at + 1.2, false, true, 0.5, 0.02, -knuckle_bend( crossing.into ), 0.0 } ); - } - else { - plans[ crossing.path ][ crossing.side > 0 ].push_back( { crossing.at - 1.2, crossing.at, true, false, 0.5, 0.02, 0.0, knuckle_bend( crossing.into ) } ); - } - } - if( eType == tt_Switch ) { - auto const frog { this->frog() }; - if( frog.valid ) { - for( int path = 0; path < 2; ++path ) { - acute( plans[ path ][ frog.side[ path ] < 0 ], frog.at[ path ], -1.0, std::tan( frog.angle ), 0.04 ); - } - } - } - auto const halfwidth { 0.5 * std::abs( fTrackWidth ) }; - auto const clearance { 0.046 + 0.04 * size.head / 0.0735 + size.head }; - for( int path = 0; path < ( eType == tt_Switch ? 2 : 1 ); ++path ) { - auto const &segment { path_segment( *this, path ) }; - auto const length { segment.GetLength() }; - for( int right = 0; right < 2; ++right ) { - auto const side { right ? 1 : -1 }; - auto &checks { plans[ path ][ right ] }; - if( checks.empty() ) { - continue; - } - std::sort( checks.begin(), checks.end(), []( planned const &A, planned const &B ) { return A.from < B.from; } ); - for( std::size_t i = 1; i < checks.size(); ++i ) { - if( checks[ i ].from < checks[ i - 1 ].to + 2.0 ) { - if( checks[ i ].to > checks[ i - 1 ].to ) { - checks[ i - 1 ].to = checks[ i ].to; - checks[ i - 1 ].flareto = checks[ i ].flareto; - checks[ i - 1 ].tobend = checks[ i ].tobend; - } - checks[ i - 1 ].shortest = std::min( checks[ i - 1 ].shortest, checks[ i ].shortest ); - checks[ i - 1 ].flare = std::max( checks[ i - 1 ].flare, checks[ i ].flare ); - checks.erase( checks.begin() + i-- ); - } - } - // check rails keep clear of the blades and the rails of the other path of a switch running inside the rail, here and in a switch joined at an end, and of the blade tips - std::vector blocks; - auto const start { plan( segment.FastGetPoint_0() ) }; - auto const end { plan( segment.FastGetPoint_1() ) }; - std::vector others; - auto const add_path = [&]( TSegment const &Other ) { - others.push_back( make_gauge_line( Other, -halfwidth, 0.0 ) ); - others.push_back( make_gauge_line( Other, halfwidth, 0.0 ) ); }; - if( eType == tt_Switch ) { - blocks.emplace_back( -1e9, 0.3 ); - add_path( path_segment( *this, 1 - path ) ); - } - for( auto const *neighbour : { trPrev, trNext } ) { - if( neighbour == nullptr || neighbour == this || neighbour->eType != tt_Switch || neighbour->SwitchExtension == nullptr ) { - continue; - } - auto const tip { plan( neighbour->SwitchExtension->Segments[ 0 ]->FastGetPoint_0() ) }; - if( glm::distance( start, tip ) < 0.1 ) { - blocks.emplace_back( -1e9, 0.3 ); - continue; - } - if( glm::distance( end, tip ) < 0.1 ) { - blocks.emplace_back( length - 0.3, 1e9 ); - continue; - } - for( int otherpath = 0; otherpath < 2; ++otherpath ) { - auto const &other { *neighbour->SwitchExtension->Segments[ otherpath ] }; - auto const continues = [&]( glm::dvec2 const &Point ) { - return glm::distance( Point, plan( other.FastGetPoint_0() ) ) < 0.1 || glm::distance( Point, plan( other.FastGetPoint_1() ) ) < 0.1; }; - if( false == continues( start ) && false == continues( end ) ) { - add_path( other ); - } - } - } - if( false == others.empty() ) { - auto const ours { make_gauge_line( segment, side * halfwidth, 0.0 ) }; - for( auto const &check : checks ) { - for( auto s { check.from }; s <= check.to; s += 0.1 ) { - glm::dvec3 point, tangent; - ours.at( s, point, tangent ); - auto const gauge { plan( point ) }; - auto const inward { plan( ours.normal( s, side ) ) }; - auto blocked { false }; - for( auto const &other : others ) { - for( std::size_t i = 1; i < other.points.size() && false == blocked; ++i ) { - auto const a { plan( other.points[ i - 1 ] ) }; - auto const edge { plan( other.points[ i ] ) - a }; - double t, u; - if( false == meet( gauge, inward, a, edge, 1e-9, t, u ) ) { - continue; - } - blocked = ( u > -1e-6 && u < 1.0 + 1e-6 && t > -0.05 && t < clearance ); - } - } - if( blocked ) { - blocks.emplace_back( s - 0.3, s + 0.3 ); - } - } - } - } - std::sort( blocks.begin(), blocks.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); - for( auto const &check : checks ) { - auto const add = [&]( double const From, double const To ) { - result[ path ][ right ].push_back( { - From, To, - ( From > check.from || check.flarefrom ) ? check.flare : 0.0, - ( To < check.to || check.flareto ) ? check.flare : 0.0, - From > check.from ? 0.0 : check.frombend, - To < check.to ? 0.0 : check.tobend } ); }; - auto from { check.from }; - for( auto const &block : blocks ) { - if( block.y <= from || block.x >= check.to ) { - continue; - } - if( block.x - from >= check.shortest ) { - add( from, block.x ); - } - from = std::max( from, block.y ); - } - if( check.to - from >= check.shortest ) { - add( from, check.to ); - } - } - } - } - return result; -} - -TTrack::switch_frog TTrack::frog() const { - - switch_frog result; - if( eType != tt_Switch || false == cut_rails( *this ) ) { - return result; - } - result.side[ 0 ] = blade_side( 0 ); - result.side[ 1 ] = -result.side[ 0 ]; - auto const halfwidth { 0.5 * std::abs( fTrackWidth ) }; - std::vector lines[ 2 ]; - std::vector distances[ 2 ]; - for( int path = 0; path < 2; ++path ) { - auto const &segment { *SwitchExtension->Segments[ path ] }; - auto const length { segment.GetLength() }; - result.blades[ path ] = blade_length( *this, path ); - auto const count { std::max( 1, static_cast( std::ceil( length / 0.25 ) ) ) }; - for( int i = 0; i <= count; ++i ) { - auto const distance { length * i / count }; - glm::dvec3 point, across; - segment.plan_frame( distance, point, across ); - auto const gauge { point + across * ( result.side[ path ] * halfwidth ) }; - lines[ path ].emplace_back( gauge.x, gauge.z ); - distances[ path ].push_back( distance ); - } - } - for( std::size_t i = 1; i < lines[ 0 ].size() && false == result.valid; ++i ) { - if( distances[ 0 ][ i ] <= result.blades[ 0 ] ) { - continue; - } - auto const a0 { lines[ 0 ][ i - 1 ] }; - auto const a { lines[ 0 ][ i ] - a0 }; - for( std::size_t j = 1; j < lines[ 1 ].size(); ++j ) { - if( distances[ 1 ][ j ] <= result.blades[ 1 ] ) { - continue; - } - auto const b0 { lines[ 1 ][ j - 1 ] }; - auto const b { lines[ 1 ][ j ] - b0 }; - double u, v; - if( false == meet( a0, a, b0, b, 1e-12, u, v ) ) { - continue; - } - if( u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0 ) { - continue; - } - result.at[ 0 ] = glm::mix( distances[ 0 ][ i - 1 ], distances[ 0 ][ i ], u ); - result.at[ 1 ] = glm::mix( distances[ 1 ][ j - 1 ], distances[ 1 ][ j ], v ); - result.valid = true; - break; - } - } - if( false == result.valid ) { - return result; - } - glm::dvec3 point, across0, across1; - SwitchExtension->Segments[ 0 ]->plan_frame( result.at[ 0 ], point, across0 ); - SwitchExtension->Segments[ 1 ]->plan_frame( result.at[ 1 ], point, across1 ); - result.angle = std::acos( std::min( 1.0, std::abs( glm::dot( across0, across1 ) ) ) ); - if( result.angle < 0.01 || result.angle > 0.5 ) { - result.valid = false; - return result; - } - for( int path = 0; path < 2; ++path ) { - auto const throat { result.at[ path ] - flangeway / std::tan( result.angle ) }; - if( throat <= result.blades[ path ] || result.at[ path ] >= SwitchExtension->Segments[ path ]->GetLength() ) { - result.valid = false; - } - } - return result; -} - -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 wing_extension( TTrack &Track, TTrack::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 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( 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( 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; -} - -// acute frog: the rail coming from the throat bends out as a wing rail, the rail going on behind the gap is a leg of the nose -void acute( TTrack::rail_plan &Plan, double const At, double const Toward, double const Slope, std::vector const &Extension, std::vector const &Legs ) { - - auto const throat { At + Toward * flangeway / Slope }; - auto const flare { At - Toward * wing_reach( Slope ) }; - auto const nose { Toward < 0.0 ? Legs.front().x : Legs.back().x }; - std::vector wing { { throat, 0.0 }, { flare, std::abs( flare - throat ) * Slope } }; - wing.insert( wing.end(), Extension.begin(), Extension.end() ); - auto const wingend { Extension.empty() ? flare : Extension.back().x }; - std::vector rolled; - for( auto const &knot : wing ) { - rolled.emplace_back( knot.x, rolled_band - knot.y ); - } - std::sort( rolled.begin(), rolled.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); - TTrack::rail_cut cut; - cut.at = At; - if( Toward < 0.0 ) { - cut.end = wingend; - cut.endoffsets = wing; - cut.endrolled = rolled; - cut.start = nose; - cut.startextents = Legs; - } - else { - cut.end = nose; - cut.endextents = Legs; - cut.start = wingend; - cut.startoffsets = wing; - cut.startrolled = rolled; - } - Plan.cuts.push_back( cut ); -} - -// obtuse crossing: the rail ends where it meets the other one and starts again pointed past its flangeway, a check rail beside the point guards the gap -void obtuse( TTrack::rail_plan &Plan, double const At, double const Into, TTrack::rail_size const &Size ) { - - auto const gap { flangeway / std::abs( Into ) }; - auto const full { Size.high + 0.0135 }; - auto const taper { ( flangeway + full ) / std::abs( Into ) }; - auto const bend { knuckle_bend( Into ) }; - TTrack::rail_cut cut; - cut.at = At; - if( Into > 0.0 ) { - cut.end = At; - cut.endbend = -bend; - cut.start = At + gap; - cut.startextents = { { At + gap, 0.006 }, { At + taper, full } }; - } - else { - cut.end = At - gap; - cut.endextents = { { At - taper, full }, { At - gap, 0.006 } }; - cut.start = At; - cut.startbend = bend; - } - Plan.cuts.push_back( cut ); -} - -// crossing at a wide angle: running rails are broken for the flanges of the other track, the ones of the track not going through also where the head of the other rail lies; guard rails give way to the running rails and flangeways of the other track, the ones of the track not going through also to its guard rails -void wide( TTrack::rail_plan &Plan, double const At, double const Into, double const Facing, double const From, double const To, double const Inner, double const Outer ) { - - auto const low { ( From + std::min( Inner * Facing, Outer * Facing ) - 0.005 ) / Into }; - auto const high { ( To + std::max( Inner * Facing, Outer * Facing ) + 0.005 ) / Into }; - TTrack::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 ? From - 0.005 : To + 0.005 ) / Into, Facing / Into }; - cut.startmiter = { At + ( Into > 0.0 ? To + 0.005 : From - 0.005 ) / Into, Facing / Into }; - Plan.cuts.push_back( cut ); -} - -TTrack::rail_fill guard_fill( TTrack::guard_rail const &Guard, TTrack::rail_size const &Size ) { - - auto const offset { Size.web - ( flangeway + Size.head ) }; - auto const flare { std::min( 0.3, 0.25 * ( Guard.to - Guard.from ) ) }; - TTrack::rail_fill fill; - fill.web = true; - fill.from = Guard.from; - fill.to = Guard.to; - fill.capfrom = true; - fill.capto = true; - fill.lift = ( Guard.through ? 0.0 : -0.001 ); - fill.inner = { { Guard.from, offset - ( Guard.flarefrom ? 0.02 : 0.0 ) }, { Guard.from + flare, offset }, { Guard.to - flare, offset }, { Guard.to, offset - ( Guard.flareto ? 0.02 : 0.0 ) } }; - fill.outer = { { Guard.from, Size.web }, { Guard.to, Size.web } }; - return fill; -} - -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 ); -} - -TTrack::rail_band groove_band( double const At, double const Into, double const Facing, bool const Through, TTrack::rail_size const &Other ) { - - auto const seam { 0.001 }; - TTrack::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 &Fills, double const At, double const Into, double const Facing, TTrack::rail_size const &Own, TTrack::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 }; - TTrack::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 line { { apex, edge + sign * reach }, { end, edge + sign * ( gap + reach ) } }; - std::sort( line.begin(), line.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); - std::vector 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 ); -} - -// 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 -struct frame_corner { - TTrack *track { nullptr }; - int path { 0 }; - int side { 0 }; - int otherpath { 0 }; - double at { 0.0 }; - double into { 0.0 }; - double facing { 0.0 }; -}; - -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::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; -} - -std::vector 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 &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 knots; - auto below { 0 }; - auto const count { std::max( 2, static_cast( std::ceil( ( To - From ) / 0.1 ) ) + 1 ) }; - for( int i = 0; i < count; ++i ) { - auto distance { glm::mix( From, To, static_cast( 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 ); - } - auto const byposition = []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; }; - std::sort( knots.begin(), knots.end(), byposition ); - std::sort( Gauge.begin(), Gauge.end(), byposition ); - Below = ( below > 0 ); - return knots; -} - -std::vector nose_extents( TTrack const &Track, int const Path, int const Side, TTrack const &Other, int const Otherpath, int const Otherside, double const At, double const Toward, double const Slope, TTrack::rail_size const &Size ) { - - auto const half { Slope / ( 1.0 + std::sqrt( 1.0 + Slope * Slope ) ) }; - auto const tip { 0.5 * nose_tip }; - auto const apart { Size.high + 0.01 }; - auto const nose { At - Toward * tip / half }; - auto const blunt { At - Toward * apart / half }; - std::vector straight { { std::min( nose, blunt ), Toward < 0.0 ? tip : apart }, { std::max( nose, blunt ), Toward < 0.0 ? apart : tip } }; - 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( nose, point, tangent ); - auto otherat { nearest_distance( theirs, point ) }; - auto extent { tip }; - auto const step { 0.1 }; - auto const limit { static_cast( std::ceil( 3.0 * std::abs( blunt - nose ) / step ) ) }; - std::vector knots; - for( int i = 0; i <= limit; ++i ) { - auto const distance { nose - Toward * step * i }; - ours.at( distance, point, tangent ); - auto const normal { ours.normal( distance, Side ) }; - for( int iteration = 0; iteration < 6; ++iteration ) { - extent += 0.5 * ( lateral( theirs, Otherside, point - normal * extent, otherat ) - extent ); - } - if( extent >= apart && false == knots.empty() ) { - auto const &last { knots.back() }; - knots.emplace_back( glm::mix( last.x, distance, ( apart - last.y ) / ( extent - last.y ) ), apart ); - break; - } - knots.emplace_back( distance, std::max( extent, 0.0 ) ); - } - if( knots.size() < 2 || knots.back().y < apart ) { - return straight; - } - std::sort( knots.begin(), knots.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); - return knots; -} - -} // 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( 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( 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( 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( 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 ); -} - -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 ); -} - -TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { - - rail_layout layout; - auto const size { rail_dimensions() }; - auto const railhead { size.head }; - std::vector crossings; - if( Crossings ) { - crossings = rail_crossings(); - } - layout.checks = ( Crossings ? rail_checks( *this ) : check_rails( crossings ) ); - auto const &checks { layout.checks }; - auto &plans { layout.plans }; - auto &guards { layout.guards }; - auto &wingends { layout.wingends }; - auto const guardoffset { -( flangeway + railhead ) }; - auto const 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 ) { - auto const material { shared_tread( Path, Other, Otherpath ) }; - if( material == rail_material( Path ) || Span.y <= Span.x ) { - return; - } - if( Otherside == 0 ) { - layout.treads[ Path ][ Side > 0 ].push_back( { Span.x, Span.y, material, Anchor } ); - return; - } - rail_tread part { Span.x, Span.y, material, Anchor }; - part.edge = tread_edge( *this, 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 < 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 ) { - layout.treads[ Path ][ Side > 0 ].push_back( part ); - } - }; - auto const across = [&]( rail_crossing const &Crossing, rail_size const &Other ) { - 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 : { size.grooved ? size.ledge : 0.0, size.grooved ? size.tread : 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; }; - std::vector corners; - if( Crossings ) { - for( auto const &crossing : crossings ) { - auto &plan { plans[ crossing.path ][ crossing.side > 0 ] }; - if( std::find( layout.crossed.begin(), layout.crossed.end(), crossing.track ) == layout.crossed.end() ) { - layout.crossed.push_back( crossing.track ); - } - auto const theirs { crossing.track->rail_dimensions() }; - if( size.grooved || theirs.grooved ) { - auto const through { size.grooved != theirs.grooved ? size.grooved : goes_through( crossing.owner ? *crossing.owner : *this, *crossing.track ) }; - layout.bands[ crossing.path ][ crossing.side > 0 ].push_back( groove_band( crossing.at, crossing.into, crossing.facing, through, theirs ) ); - if( size.grooved && theirs.grooved && crossing.angle <= middle_angle ) { - std::vector vee; - vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, theirs, true ); - vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, theirs, false ); - glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) }; - layout.blocks[ crossing.path ][ crossing.side > 0 ].push_back( block ); - tread( crossing.path, crossing.side, block, *crossing.track, crossing.otherpath ); - if( through ) { - auto &fills { layout.fills[ crossing.path ][ crossing.side > 0 ] }; - fills.insert( fills.end(), vee.begin(), vee.end() ); - } - } - else if( through ) { - tread( crossing.path, crossing.side, across( crossing, theirs ), *crossing.track, crossing.otherpath ); - } - continue; - } - if( crossing.angle > middle_angle && crossing.angle <= wide_angle ) { - corners.push_back( { crossing.track, crossing.path, crossing.side, crossing.otherpath, crossing.at, crossing.into, crossing.facing } ); - } - if( crossing.angle > wide_angle ) { - auto const other { crossing.track->rail_dimensions() }; - auto const through { goes_through( *this, *crossing.track ) }; - wide( plan, crossing.at, crossing.into, crossing.facing, through ? 0.0 : -other.head, flangeway, 0.0, railhead ); - if( through ) { - tread( crossing.path, crossing.side, across( crossing, other ), *crossing.track, crossing.otherpath ); - } - auto const reach { ( flangeway + railhead + 0.3 ) / std::abs( crossing.into ) }; - auto &spans { guards[ crossing.path ][ crossing.side > 0 ] }; - TTrack::guard_rail guard { crossing.at - reach, crossing.at + reach }; - guard.through = through; - wide( guard.plan, crossing.at, crossing.into, crossing.facing, -other.head, through ? flangeway : flangeway + other.head, guardoffset, guardoffset + railhead ); - auto merged { false }; - for( auto &span : spans ) { - 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 ); - merged = true; - break; - } - } - if( false == merged ) { - spans.push_back( guard ); - } - } - else if( crossing.facing < 0.0 ) { - auto const toward { crossing.into > 0.0 ? 1.0 : -1.0 }; - auto const slope { std::tan( crossing.angle ) }; - wingends[ crossing.path ][ crossing.side > 0 ].emplace_back( crossing.at - toward * wing_reach( slope ), -toward ); - acute( plan, crossing.at, toward, slope, - wing_extension( *this, checks, crossing.path, crossing.side, crossing.at, toward, slope, *crossing.track, crossing.otherpath, crossing.otherside, crossing.otherat, crossing.aligned ? -toward : toward ), - nose_extents( *this, crossing.path, crossing.side, *crossing.track, crossing.otherpath, crossing.otherside, crossing.at, toward, slope, size ) ); - auto const nose { toward < 0.0 ? plan.cuts.back().start : plan.cuts.back().end }; - auto const shared { crossing.at - toward * ( rolled_band + size.head + tread_fade ) / slope }; - tread( crossing.path, crossing.side, { std::min( nose, shared ), std::max( nose, shared ) }, *crossing.track, crossing.otherpath, toward > 0.0 ? 1 : -1, crossing.otherside ); - } - else { - obtuse( plan, crossing.at, crossing.into, size ); - auto const reach { ( rolled_band + size.head + tread_fade ) / std::max( std::tan( crossing.angle ), 0.01 ) + 0.2 }; - tread( crossing.path, crossing.side, { crossing.at - reach, crossing.at + reach }, *crossing.track, crossing.otherpath, 0, crossing.otherside ); - } - } - for( auto &pathguards : guards ) { - std::vector spans[ 2 ]; - for( int side = 0; side < 2; ++side ) { - for( auto const &guard : pathguards[ side ] ) { - spans[ side ].emplace_back( guard.from, guard.to ); - } - } - for( int side = 0; side < 2; ++side ) { - for( auto &guard : pathguards[ 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 ); - } - } - } - } - } - for( int path = 0; path < 2; ++path ) { - for( int side = 0; side < 2; ++side ) { - for( auto const &guard : guards[ path ][ side ] ) { - layout.fills[ path ][ side ].push_back( guard_fill( guard, size ) ); - } - } - } - std::sort( corners.begin(), corners.end(), []( frame_corner const &A, frame_corner const &B ) { return A.at < B.at; } ); - auto const glued = [&]( frame_corner const &Corner ) { - return Corner.at + ( flangeway + std::max( guardoffset * Corner.facing, ( guardoffset + railhead ) * Corner.facing ) ) / Corner.into; }; - auto const gluedmiter = [&]( frame_corner const &Corner ) { - return glm::dvec2 { Corner.at + flangeway / Corner.into, Corner.facing / Corner.into }; }; - for( std::size_t i = 0; i < corners.size(); ++i ) { - auto const &start { corners[ i ] }; - if( start.into <= 0.0 ) { - continue; - } - for( std::size_t j = i + 1; j < corners.size(); ++j ) { - auto const &end { corners[ j ] }; - if( end.track != start.track || end.path != start.path || end.side != start.side || end.otherpath != start.otherpath ) { - continue; - } - if( end.into < 0.0 ) { - TTrack::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 : corners ) { - if( opposite.track != start.track || opposite.path != start.path || opposite.side != -start.side || opposite.otherpath != start.otherpath ) { - continue; - } - auto const margin { ( flangeway + railhead + 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->at, corner->into, corner->facing, -corner->track->rail_dimensions().head, flangeway, guardoffset, guardoffset + railhead ); - 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; - } - } - if( guard.to - guard.from > 0.1 ) { - guards[ start.path ][ start.side > 0 ].push_back( guard ); - } - } - break; - } - } - } - if( eType == tt_Switch && cut_rails( *this ) ) { - for( int path = 0; path < 2; ++path ) { - tread( path, -blade_side( path ), { 0.0, blade_length( *this, 1 - path ) }, *this, 1 - path, 0, -blade_side( path ) ); - } - } - if( eType == tt_Switch ) { - auto const frog { this->frog() }; - if( frog.valid ) { - glm::dvec3 point, tangents[ 2 ]; - glm::dvec2 normals[ 2 ]; - for( int path = 0; path < 2; ++path ) { - auto const &line { rail_line( *this, 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 ] ) }; - auto const slope { std::tan( frog.angle ) }; - for( int path = 0; path < 2; ++path ) { - auto const into { glm::dot( glm::normalize( plan( tangents[ path ] ) ), normals[ 1 - path ] ) }; - auto &fills { layout.fills[ path ][ frog.side[ path ] > 0 ] }; - if( size.grooved ) { - layout.bands[ path ][ frog.side[ path ] > 0 ].push_back( groove_band( frog.at[ path ], into, facing, path == 0, size ) ); - std::vector vee; - vee_fill( vee, frog.at[ path ], into, facing, size, size, true ); - vee_fill( vee, frog.at[ path ], into, facing, size, size, false ); - glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) }; - layout.blocks[ path ][ frog.side[ path ] > 0 ].push_back( block ); - tread( path, frog.side[ path ], block, *this, 1 - path ); - if( path == 0 ) { - fills.insert( fills.end(), vee.begin(), vee.end() ); - } - continue; - } - wingends[ path ][ frog.side[ path ] > 0 ].emplace_back( frog.at[ path ] + wing_reach( slope ), 1.0 ); - acute( plans[ path ][ frog.side[ path ] > 0 ], frog.at[ path ], -1.0, slope, - wing_extension( *this, checks, path, frog.side[ path ], frog.at[ path ], -1.0, slope, *this, 1 - path, frog.side[ 1 - path ], frog.at[ 1 - path ], 1.0 ), - nose_extents( *this, path, frog.side[ path ], *this, 1 - path, frog.side[ 1 - path ], frog.at[ path ], -1.0, slope, size ) ); - auto const nose { plans[ path ][ frog.side[ path ] > 0 ].cuts.back().start }; - auto const shared { frog.at[ path ] + ( rolled_band + size.head + tread_fade ) / slope }; - tread( path, frog.side[ path ], { std::min( nose, shared ), std::max( nose, shared ) }, *this, 1 - path, -1, frog.side[ 1 - path ] ); - } - } - } - return layout; -} - -TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const &Bank ) { - - rail_drawing drawing; - drawing.bank = Bank; - drawing.size = rail_dimensions(); - drawing.halfwidth = 0.5f * std::abs( fTrackWidth ); - // check rail: a narrow head facing the running rail across the flangeway, on a thin web with a foot turned away from it, sized after the rail - gfx::vertex_array checkshape { - {{ -0.041f, -0.170f, 0.f }, { 1.000f, 0.000f, 0.f }, { 0.15f, 0.f }}, - {{ -0.041f, -0.010f, 0.f }, { 0.707f, 0.707f, 0.f }, { 0.40f, 0.f }}, - {{ -0.051f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.45f, 0.f }}, - {{ -0.071f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.55f, 0.f }}, - {{ -0.081f, -0.010f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.60f, 0.f }}, - {{ -0.081f, -0.045f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.65f, 0.f }}, - {{ -0.066f, -0.060f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.75f, 0.f }}, - {{ -0.066f, -0.150f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.85f, 0.f }}, - {{ -0.101f, -0.170f, 0.f }, { -1.000f, 0.000f, 0.f }, { 1.00f, 0.f }} }; - for( auto &point : checkshape ) { - point.position.x = -0.041f + ( point.position.x + 0.041f ) * static_cast( drawing.size.head / 0.0735 ); - point.position.y *= static_cast( drawing.size.height / 0.172 ); - } - create_track_rail_profile( drawing.checkshapes[ 0 ], drawing.checkshapes[ 1 ], &checkshape ); - for( auto &shape : drawing.checkshapes ) { - shape.resize( checkshape.size() ); - } - if( false == m_profiles.empty() ) { - auto const &outline { track_rail_profile( m_profile1.second ) }; - auto texture { 0.f }; - auto highest { -1e9f }; - for( std::size_t i = 0; i < outline.size() / 2; ++i ) { - if( outline[ i ].position.y > highest ) { - highest = outline[ i ].position.y; - texture = outline[ i ].texture.s; - } - } - if( drawing.size.grooved ) { - auto sum { 0.f }; - auto points { 0 }; - for( std::size_t i = 0; i < outline.size() / 2; ++i ) { - if( outline[ i ].position.y > highest - 0.0005f && outline[ i ].position.x > -0.02f ) { - sum += outline[ i ].texture.s; - ++points; - } - } - texture = sum / std::max( points, 1 ); - } - auto const top { static_cast( ( drawing.size.grooved ? drawing.size.liptop : highest ) - 0.0005 ) }; - auto const bottom { static_cast( -drawing.size.height ) }; - gfx::vertex_array plate { - {{ 1.f, bottom, 0.f }, { 1.f, 0.f, 0.f }, { texture, 0.f }}, - {{ 1.f, top, 0.f }, { 1.f, 0.f, 0.f }, { texture, 0.f }}, - {{ 1.f, top, 0.f }, { 0.f, 1.f, 0.f }, { texture, 0.f }}, - {{ -1.f, top, 0.f }, { 0.f, 1.f, 0.f }, { texture, 0.f }}, - {{ -1.f, top, 0.f }, { -1.f, 0.f, 0.f }, { texture, 0.f }}, - {{ -1.f, bottom, 0.f }, { -1.f, 0.f, 0.f }, { texture, 0.f }} }; - drawing.platetexture = texture; - create_track_rail_profile( drawing.plates[ 0 ], drawing.plates[ 1 ], &plate ); - for( auto &shape : drawing.plates ) { - shape.resize( plate.size() ); - } - auto const neck { static_cast( drawing.size.neck ) }; - auto webtexture { texture }; - for( std::size_t i = 0; i < outline.size() / 2; ++i ) { - auto const &point { outline[ i ] }; - if( point.position.y < neck && point.normal.y > 0.9f ) { - webtexture = point.texture.s; - break; - } - } - gfx::vertex_array filler { - {{ 1.f, bottom, 0.f }, { 1.f, 0.f, 0.f }, { webtexture, 0.f }}, - {{ 1.f, neck, 0.f }, { 1.f, 0.f, 0.f }, { webtexture, 0.f }}, - {{ 1.f, neck, 0.f }, { 0.f, 1.f, 0.f }, { webtexture, 0.f }}, - {{ -1.f, neck, 0.f }, { 0.f, 1.f, 0.f }, { webtexture, 0.f }}, - {{ -1.f, neck, 0.f }, { -1.f, 0.f, 0.f }, { webtexture, 0.f }}, - {{ -1.f, bottom, 0.f }, { -1.f, 0.f, 0.f }, { webtexture, 0.f }} }; - create_track_rail_profile( drawing.fillers[ 0 ], drawing.fillers[ 1 ], &filler ); - for( auto &shape : drawing.fillers ) { - shape.resize( filler.size() ); - } - } - return drawing; -} - -bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) { - - gfx::userdata_array empty_userdata; - auto const &size { Drawing.size }; - auto const halfwidth { Drawing.halfwidth }; - auto const railhead { size.head }; - auto const guardoffset { -( flangeway + railhead ) }; - auto const &segment { path_segment( *this, Path ) }; - auto const &plan { Layout.plans[ Path ][ Side > 0 ] }; - auto const length { segment.GetLength() }; - auto const &treads { Layout.treads[ Path ][ Side > 0 ] }; - material_handle current { null_handle }; - std::vector tints; - static auto const marked { std::getenv( "EU07_EDITOR_TREADS" ) != nullptr }; - auto const paint = [&]( material_handle const Material, glm::vec3 const &Colour ) { - auto const tinted { tint_material( Material, Colour ) }; - if( tinted != Material ) { - tints.push_back( tinted ); - } - return tinted; }; - std::vector const unramped; - auto const sink = [&]( bool const Matte ) -> geometryhandle_sequence & { - if( current == null_handle ) { - return Matte ? Cuts : Geometry; - } - if( current == m_material1 ) { - return Matte ? GeometryCut1 : Geometry1; - } - if( eType == tt_Switch && current == m_material2 ) { - return Matte ? GeometryCut2 : Geometry2; - } - auto const tinted { std::find( tints.begin(), tints.end(), current ) != tints.end() }; - auto found { std::find_if( GeometryTreads.begin(), GeometryTreads.end(), [&]( tread_geometry const &Tread ) { return Tread.material == current; } ) }; - if( found == GeometryTreads.end() ) { - found = GeometryTreads.insert( GeometryTreads.end(), { current, tinted ? current : cut_material( current ), {}, {} } ); - } - return Matte && false == tinted ? found->cuts : found->geometry; }; - auto const texturelength = [&]() { - return ( current == null_handle ? Texturelength : texture_length( current ) ); }; - auto const aim = [&]( std::vector const &Spans, double const Middle ) -> rail_tread const * { - current = null_handle; - for( auto const &span : Spans ) { - if( Middle > span.from && Middle < span.to ) { - current = span.material; - return &span; - } - } - return nullptr; - }; - // parts no wheel rolls on go in the matte material of the cut faces - glm::dvec2 bendfrom { 0.0 }; - glm::dvec2 bendto { 0.0 }; - auto const bends = [&]( double const Start, double const End ) { - return glm::dvec2 { - std::abs( Start - bendfrom.x ) < 1e-6 ? bendfrom.y : 0.0, - std::abs( End - bendto.x ) < 1e-6 ? bendto.y : 0.0 }; }; - auto const piece = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, bool const Capstart, bool const Capend, gfx::vertex_array const &Shape, bool const Rolled, std::vector const &Inner, std::vector const &Ramp = {}, bool const Walls = false, float const Lift = 0.f, float const Wallu = -1.f, double const Fade = blend ) { - gfx::vertex_array vertices; - gfx::userdata_array userdata; - if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : Fade, Walls, Wallu, bends( Start, End ) ) ) { - cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) ); - gfx::vertex_array triangles; - gfx::userdata_array triangleuserdata; - auto const ring { 2 * Shape.size() }; - for( std::size_t block = 0; block + ring <= vertices.size(); block += ring ) { - for( std::size_t j = 0; j + 1 < Shape.size(); ++j ) { - for( auto const index : { 0, 1, 2, 1, 3, 2 } ) { - auto const vertex { block + 2 * j + index }; - triangles.push_back( vertices[ vertex ] ); - if( false == Rolled ) { - triangleuserdata.push_back( userdata[ vertex ] ); - } - } - } - } - sink( false == Rolled ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) ); - } - }; - auto const level = []( std::vector const &Knots, double const Distance ) { - if( Distance <= Knots.front().x ) { - return Knots.front().y; - } - for( std::size_t i = 1; i < Knots.size(); ++i ) { - if( Distance <= Knots[ i ].x ) { - return glm::mix( Knots[ i - 1 ].y, Knots[ i ].y, ( Distance - Knots[ i - 1 ].x ) / std::max( Knots[ i ].x - Knots[ i - 1 ].x, 1e-6 ) ); - } - } - return Knots.back().y; }; - auto const bound = [&]( std::vector const &A, std::vector const &B, bool const Lower ) { - if( A.empty() || B.empty() ) { - return ( A.empty() ? B : A ); - } - std::vector stations; - for( auto const *knots : { &A, &B } ) { - for( auto const &knot : *knots ) { - stations.push_back( knot.x ); - } - } - std::sort( stations.begin(), stations.end() ); - auto const count { stations.size() }; - for( std::size_t i = 1; i < count; ++i ) { - auto const a { stations[ i - 1 ] }; - auto const b { stations[ i ] }; - auto const before { level( A, a ) - level( B, a ) }; - auto const after { level( A, b ) - level( B, b ) }; - if( before * after < 0.0 ) { - stations.push_back( a + ( b - a ) * before / ( before - after ) ); - } - } - std::sort( stations.begin(), stations.end() ); - std::vector result; - for( auto const station : stations ) { - result.emplace_back( station, Lower ? std::min( level( A, station ), level( B, station ) ) : std::max( level( A, station ), level( B, station ) ) ); - } - return result; }; - // the running surface on top of the head is split along the edge of the wheel treads, the part of it out of their reach goes matte; the rest of the profile and the end faces are laid whole - std::size_t topfirst { Profile.size() }; - std::size_t toplast { 0 }; - for( std::size_t j = 0; j < Profile.size(); ++j ) { - if( Profile[ j ].position.y > static_cast( -0.087 * size.height ) ) { - topfirst = std::min( topfirst, j ); - toplast = std::max( toplast, j ); - } - } - auto const strip = [&]( std::size_t const First, std::size_t const Last ) { - return gfx::vertex_array( Profile.begin() + First, Profile.begin() + Last + 1 ); }; - auto const shifted = []( std::vector Knots, double const Lateral ) { - for( auto &knot : Knots ) { - knot.y += Lateral; - } - return Knots; }; - auto const crossings = [&]( std::vector const &A, std::vector const &B, double const Start, double const End, double const Margin, std::vector &Splits ) { - if( A.empty() || B.empty() ) { - return; - } - std::vector stations; - for( auto const *knots : { &A, &B } ) { - for( auto const &knot : *knots ) { - stations.push_back( knot.x ); - } - } - std::sort( stations.begin(), stations.end() ); - for( std::size_t i = 1; i < stations.size(); ++i ) { - auto const before { level( A, stations[ i - 1 ] ) - level( B, stations[ i - 1 ] ) }; - auto const after { level( A, stations[ i ] ) - level( B, stations[ i ] ) }; - if( before * after < 0.0 ) { - auto const distance { stations[ i - 1 ] + ( stations[ i ] - stations[ i - 1 ] ) * before / ( before - after ) }; - if( distance > Start + Margin && distance < End - Margin ) { - Splits.push_back( distance ); - } - } - } - }; - auto const running = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, std::vector const &Inner, std::vector const &Rolled, bool const Capstart, bool const Capend ) { - gfx::vertex_array vertices; - if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner, nullptr, {}, 0.0, false, -1.f, bends( Start, End ) ) ) { - cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); - } - piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); - piece( Start, End, Offsets, Extents, false, false, strip( toplast, Profile.size() - 1 ), true, Inner ); - auto const top { strip( topfirst, toplast ) }; - auto low { 1e9 }; - auto high { -1e9 }; - for( auto const &point : top ) { - low = std::min( low, static_cast( Side * point.position.x - halfwidth ) ); - high = std::max( high, static_cast( Side * point.position.x - halfwidth ) ); - } - auto const inner { shifted( Rolled, -blend ) }; - std::vector splits { Start, End }; - std::vector const lowline { { Start, low }, { End, low } }; - std::vector const highline { { Start, high }, { End, high } }; - for( auto const *knots : { &Rolled, &inner } ) { - for( auto const *limit : { &lowline, &highline, &Extents, &Inner } ) { - crossings( *knots, *limit, Start, End, 0.0, splits ); - } - } - std::sort( splits.begin(), splits.end() ); - auto const shiny { bound( Extents, inner, true ) }; - auto const blended { bound( Extents, Rolled, true ) }; - for( std::size_t i = 1; i < splits.size(); ++i ) { - auto const middle { 0.5 * ( splits[ i - 1 ] + splits[ i ] ) }; - auto const reach { level( Rolled, middle ) }; - auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; - auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; - auto const shown = [&]( double const Lowest, double const Highest ) { - return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; - if( shown( -1e9, reach - blend ) ) { - piece( splits[ i - 1 ], splits[ i ], Offsets, shiny, false, false, top, true, Inner ); - } - auto const plain { current }; - auto const own { current == null_handle ? rail_material( Path ) : current }; - if( shown( reach - blend, reach ) ) { - current = ( marked ? paint( own, { 1.f, 1.f, 0.f } ) : plain ); - piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, bound( inner, Inner, false ), marked ? unramped : Rolled ); - } - if( shown( reach, 1e9 ) ) { - current = ( marked ? paint( own, { 1.f, 0.f, 1.f } ) : plain ); - piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, bound( Rolled, Inner, false ) ); - } - current = plain; - } - }; - auto const treaded = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, std::vector const &Inner, bool const Capstart, bool const Capend, rail_tread const &Span ) { - current = null_handle; - gfx::vertex_array vertices; - if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner, nullptr, {}, 0.0, false, -1.f, bends( Start, End ) ) ) { - cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); - } - piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); - piece( Start, End, Offsets, Extents, false, false, strip( toplast, Profile.size() - 1 ), true, Inner ); - auto const top { strip( topfirst, toplast ) }; - auto low { 1e9 }; - auto high { -1e9 }; - for( auto const &point : top ) { - low = std::min( low, static_cast( Side * point.position.x - halfwidth ) ); - high = std::max( high, static_cast( Side * point.position.x - halfwidth ) ); - } - auto const &lower { Span.below ? Span.gauge : Span.edge }; - auto const &upper { Span.below ? Span.edge : Span.gauge }; - auto const own { rail_material( Path ) }; - auto const colour { 0.5f * ( tread_colour( Span.material, tread_band() ) + tread_colour( own, tread_band() ) ) }; - auto const tint = [&]( material_handle const Material, glm::vec3 const &Mark ) { - return paint( Material, marked ? Mark : colour ); }; - auto const owntint { tint( own, { 1.f, 0.f, 0.f } ) }; - auto const sharedtint { tint( Span.material, { 0.f, 1.f, 0.f } ) }; - auto const shared { marked ? tint( Span.material, { 0.f, 0.f, 1.f } ) : Span.material }; - auto const ramp = [&]( std::vector const &Knots ) -> std::vector const & { - return marked ? unramped : Knots; }; - std::vector centre; - { - std::vector stations; - for( auto const *knots : { &lower, &upper } ) { - for( auto const &knot : *knots ) { - stations.push_back( knot.x ); - } - } - std::sort( stations.begin(), stations.end() ); - for( auto const station : stations ) { - centre.emplace_back( station, 0.5 * ( level( lower, station ) + level( upper, station ) ) ); - } - } - auto const beneath { shifted( lower, -tread_fade ) }; - auto const beyond { shifted( upper, tread_fade ) }; - auto const lowfade { bound( shifted( lower, tread_fade ), centre, true ) }; - auto const highfade { bound( shifted( upper, -tread_fade ), centre, false ) }; - std::vector splits { Start, End }; - std::vector const lowline { { Start, low }, { End, low } }; - std::vector const highline { { Start, high }, { End, high } }; - for( auto const *knots : { &lower, &upper, &lowfade, &highfade, &beneath, &beyond } ) { - for( auto const *limit : { &lowline, &highline, &Extents, &Inner } ) { - crossings( *knots, *limit, Start, End, 0.001, splits ); - } - } - crossings( shifted( lower, 2.0 * tread_fade ), upper, Start, End, 0.001, splits ); - std::sort( splits.begin(), splits.end() ); - for( std::size_t i = 1; i < splits.size(); ++i ) { - auto const a { splits[ i - 1 ] }; - auto const b { splits[ i ] }; - auto const middle { 0.5 * ( a + b ) }; - auto const from { level( lower, middle ) }; - auto const to { level( upper, middle ) }; - auto const inner { level( lowfade, middle ) }; - auto const outer { level( highfade, middle ) }; - auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; - auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; - auto const shown = [&]( double const Lowest, double const Highest ) { - return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; - if( shown( -1e9, from - tread_fade ) ) { - current = null_handle; - piece( a, b, Offsets, bound( Extents, beneath, true ), false, false, top, true, Inner ); - } - if( shown( from - tread_fade, from ) ) { - current = owntint; - piece( a, b, Offsets, bound( Extents, lower, true ), false, false, top, false, bound( Inner, beneath, false ), ramp( lower ), false, 0.f, -1.f, tread_fade ); - } - if( shown( from, inner ) ) { - current = sharedtint; - piece( a, b, Offsets, bound( Extents, lowfade, true ), false, false, top, false, bound( Inner, lower, false ), ramp( lower ), false, 0.f, -1.f, -tread_fade ); - } - if( shown( inner, outer ) ) { - current = shared; - piece( a, b, Offsets, bound( Extents, highfade, true ), false, false, top, false == marked, bound( Inner, lowfade, false ) ); - } - if( shown( outer, to ) ) { - current = sharedtint; - piece( a, b, Offsets, bound( Extents, upper, true ), false, false, top, false, bound( Inner, highfade, false ), ramp( upper ), false, 0.f, -1.f, tread_fade ); - } - if( shown( to, to + tread_fade ) ) { - current = owntint; - piece( a, b, Offsets, bound( Extents, beyond, true ), false, false, top, false, bound( Inner, upper, false ), ramp( upper ), false, 0.f, -1.f, -tread_fade ); - } - if( shown( to + tread_fade, 1e9 ) ) { - current = null_handle; - piece( a, b, Offsets, Extents, false, false, top, true, bound( Inner, beyond, false ) ); - } - } - current = null_handle; - }; - for( auto const &guard : Layout.guards[ Path ][ Side > 0 ] ) { - auto const flare { std::min( 0.3, 0.25 * ( guard.to - guard.from ) ) }; - std::vector offsets; - if( guard.flarefrom ) { - offsets.insert( offsets.end(), { { guard.from, guardoffset - 0.02 }, { guard.from + flare, guardoffset } } ); - } - else { - offsets.push_back( { guard.from, guardoffset } ); - } - if( guard.flareto ) { - offsets.insert( offsets.end(), { { guard.to - flare, guardoffset }, { guard.to, guardoffset - 0.02 } } ); - } - else { - offsets.push_back( { guard.to, guardoffset } ); - } - auto const top { flangeway + railhead + size.low - 0.002 }; - auto const mitered = [&]( glm::dvec2 const &Miter, double const Square, bool const Latest ) { - if( Miter.y == 0.0 ) { - return Square; - } - auto const inside { Miter.x + Miter.y * ( size.low + guardoffset ) }; - auto const outside { Miter.x + Miter.y * ( top + guardoffset ) }; - return Latest ? std::max( inside, outside ) : std::min( inside, outside ); }; - auto const envelope = [&]( std::vector const &Lines, bool const Upper, double const A, double const B ) { - std::vector knots; - if( Lines.empty() ) { - return knots; - } - std::vector stations { A, B }; - for( std::size_t i = 0; i < Lines.size(); ++i ) { - for( std::size_t j = i + 1; j < Lines.size(); ++j ) { - if( std::abs( Lines[ i ].x - Lines[ j ].x ) > 1e-12 ) { - auto const d { ( Lines[ j ].y - Lines[ i ].y ) / ( Lines[ i ].x - Lines[ j ].x ) }; - if( d > A && d < B ) { - stations.push_back( d ); - } - } - } - } - std::sort( stations.begin(), stations.end() ); - for( auto const d : stations ) { - auto value { Upper ? 1e6 : -1e6 }; - for( auto const &line : Lines ) { - value = Upper ? std::min( value, line.x * d + line.y ) : std::max( value, line.x * d + line.y ); - } - knots.emplace_back( d, value ); - } - return knots; }; - auto cuts { guard.plan.cuts }; - std::sort( cuts.begin(), cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } ); - auto start { guard.from }; - auto startmiter { guard.frommiter }; - for( std::size_t i = 0; i <= cuts.size(); ++i ) { - auto const end { i < cuts.size() ? cuts[ i ].end : guard.to }; - auto const endmiter { i < cuts.size() ? cuts[ i ].endmiter : guard.tomiter }; - auto const a { std::max( { mitered( startmiter, start, false ), From, 0.0 } ) }; - auto const b { std::min( { mitered( endmiter, end, true ), length, mitered( guard.tomiter, guard.to, true ) } ) }; - if( b - a > 0.01 ) { - std::vector uppers { { 0.0, top } }; - std::vector lowers; - for( auto const &[ miter, ending ] : { std::pair{ startmiter, false }, std::pair{ endmiter, true } } ) { - if( miter.y != 0.0 ) { - ( ending == ( miter.y < 0.0 ) ? uppers : lowers ).emplace_back( 1.0 / miter.y, -miter.x / miter.y - guardoffset ); - } - } - piece( a, b, offsets, envelope( uppers, true, a, b ), true, true, Profile, false, envelope( lowers, false, a, b ) ); - } - if( i < cuts.size() && cuts[ i ].start > start ) { - start = cuts[ i ].start; - startmiter = cuts[ i ].startmiter; - } - } - } - auto const &shape { Drawing.checkshapes[ Side > 0 ? 0 : 1 ] }; - for( auto const &check : Layout.checks[ Path ][ Side > 0 ] ) { - auto const replaced { std::any_of( Layout.wingends[ Path ][ Side > 0 ].begin(), Layout.wingends[ Path ][ Side > 0 ].end(), [&]( glm::dvec2 const &Wing ) { - auto farend { 0.0 }; - return replaced_check( { check }, Wing.x, Wing.y, farend ); } ) }; - auto const from { check.from }; - auto const to { check.to }; - auto const a { std::max( from, From ) }; - auto const b { std::min( to, length ) }; - if( replaced || b - a <= 0.01 ) { - continue; - } - auto const flare { std::min( 0.6, 0.4 * ( to - from ) ) }; - std::vector offsets; - if( check.flarefrom > 0.0 ) { - offsets.insert( offsets.end(), { { from, -check.flarefrom }, { from + flare, 0.0 } } ); - } - else { - offsets.push_back( { from, 0.0 } ); - } - if( check.flareto > 0.0 ) { - offsets.insert( offsets.end(), { { to - flare, 0.0 }, { to, -check.flareto } } ); - } - else { - offsets.push_back( { to, 0.0 } ); - } - bendfrom = { a, a == from ? check.frombend : 0.0 }; - bendto = { b, b == to ? check.tobend : 0.0 }; - piece( a, b, offsets, {}, a > 0.0 || from >= 0.0, to <= length, shape, false, {} ); - bendfrom = bendto = glm::dvec2 { 0.0 }; - } - for( auto const &fill : Layout.fills[ Path ][ Side > 0 ] ) { - auto const a { std::max( fill.from, From ) }; - auto const b { std::min( fill.to, length ) }; - if( b - a > 0.01 ) { - current = null_handle; - if( false == fill.web ) { - aim( treads, 0.5 * ( a + b ) ); - } - auto const &shapes { fill.web ? Drawing.fillers : Drawing.plates }; - piece( a, b, {}, fill.outer, a == fill.from && fill.capfrom, b == fill.to && fill.capto, shapes[ Side > 0 ? 0 : 1 ], true, fill.inner, {}, true, static_cast( fill.lift ) ); - current = null_handle; - } - } - auto const &bands { Layout.bands[ Path ][ Side > 0 ] }; - if( plan.cuts.empty() && bands.empty() && treads.empty() ) { - return false; - } - auto const byposition = []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; }; - auto const reaches = [&]( gfx::vertex_array const &Shape ) { - glm::dvec2 result { 1e9, -1e9 }; - for( auto const &point : Shape ) { - auto const reach { static_cast( Side * point.position.x - halfwidth ) }; - result = { std::min( result.x, reach ), std::max( result.y, reach ) }; - } - return result; }; - auto const band_cut = [&]( rail_band const &Band, glm::dvec2 const &Reach ) { - rail_cut cut; - cut.at = Band.at; - auto const before { Band.into > 0.0 ? Band.from : Band.to }; - auto const after { Band.into > 0.0 ? Band.to : Band.from }; - auto const meets = [&]( double const Edge, double const Reach ) { - return glm::dvec2{ Band.at + ( Edge + Band.facing * Reach ) / Band.into, Reach }; }; - if( std::abs( Band.facing ) < 1e-3 ) { - cut.end = meets( before, 0.0 ).x; - cut.start = meets( after, 0.0 ).x; - return cut; - } - std::vector endknots { meets( before, Reach.x ), meets( before, Reach.y ) }; - std::vector startknots { meets( after, Reach.x ), meets( after, Reach.y ) }; - std::sort( endknots.begin(), endknots.end(), byposition ); - std::sort( startknots.begin(), startknots.end(), byposition ); - cut.end = endknots.back().x; - cut.start = startknots.front().x; - cut.endcap = false; - cut.startcap = false; - auto const outwards { Band.facing / Band.into > 0.0 }; - ( outwards ? cut.endinner : cut.endextents ) = endknots; - ( outwards ? cut.startextents : cut.startinner ) = startknots; - return cut; }; - auto const lay = [&]( gfx::vertex_array const &Shape, std::vector cuts, bool const Wheels, std::vector const &Spans, float const Wallu = -1.f ) { - std::sort( cuts.begin(), cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } ); - auto start { -1e9 }; - auto startcap { true }; - auto startbend { 0.0 }; - std::vector rolledfrom; - std::vector offsets, extents, inner; - for( std::size_t i = 0; i <= cuts.size(); ++i ) { - auto const end { i < cuts.size() ? cuts[ i ].end : 1e9 }; - auto pieceoffsets { offsets }; - auto pieceextents { extents }; - auto pieceinner { inner }; - if( i < cuts.size() ) { - pieceoffsets.insert( pieceoffsets.end(), cuts[ i ].endoffsets.begin(), cuts[ i ].endoffsets.end() ); - pieceextents.insert( pieceextents.end(), cuts[ i ].endextents.begin(), cuts[ i ].endextents.end() ); - pieceinner.insert( pieceinner.end(), cuts[ i ].endinner.begin(), cuts[ i ].endinner.end() ); - } - std::sort( pieceoffsets.begin(), pieceoffsets.end(), byposition ); - std::sort( pieceextents.begin(), pieceextents.end(), byposition ); - std::sort( pieceinner.begin(), pieceinner.end(), byposition ); - auto const a { std::max( start, From ) }; - auto const b { std::min( end, length ) }; - auto const rolled { i < cuts.size() ? bound( rolledfrom, cuts[ i ].endrolled, true ) : rolledfrom }; - auto const capstart { start > From ? startcap : Openstart }; - auto const capend { end < length ? cuts[ i ].endcap : Openend }; - bendfrom = { a, start > From ? startbend : 0.0 }; - bendto = { b, end < length ? cuts[ i ].endbend : 0.0 }; - if( b - a > 0.01 ) { - std::vector active; - for( auto const &span : Spans ) { - if( span.anchor == 0 - || ( span.anchor > 0 && b > span.from + 0.01 && b < span.to + 0.01 ) - || ( span.anchor < 0 && a > span.from - 0.01 && a < span.to - 0.01 ) ) { - active.push_back( span ); - } - } - std::vector splits; - for( auto const &span : active ) { - for( auto const edge : { span.from, span.to } ) { - if( edge > a + 0.01 && edge < b - 0.01 ) { - splits.push_back( edge ); - } - } - } - std::sort( splits.begin(), splits.end() ); - splits.erase( std::unique( splits.begin(), splits.end(), []( double const A, double const B ) { return B - A < 0.002; } ), splits.end() ); - splits.insert( splits.begin(), a ); - splits.push_back( b ); - for( std::size_t k = 1; k < splits.size(); ++k ) { - auto const first { k == 1 }; - auto const last { k + 1 == splits.size() }; - auto const *span { aim( active, 0.5 * ( splits[ k - 1 ] + splits[ k ] ) ) }; - if( Wheels && span != nullptr && false == span->edge.empty() && topfirst < toplast ) { - treaded( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, pieceinner, first && capstart, last && capend, *span ); - } - else if( false == Wheels || rolled.empty() || topfirst >= toplast ) { - piece( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, first && capstart, last && capend, Shape, true, pieceinner, {}, false == Wheels, 0.f, Wallu ); - } - else { - running( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, pieceinner, rolled, first && capstart, last && capend ); - } - } - current = null_handle; - } - bendfrom = bendto = glm::dvec2 { 0.0 }; - if( i < cuts.size() ) { - rolledfrom = cuts[ i ].startrolled; - start = cuts[ i ].start; - startcap = cuts[ i ].startcap; - startbend = cuts[ i ].startbend; - offsets = cuts[ i ].startoffsets; - extents = cuts[ i ].startextents; - inner = cuts[ i ].startinner; - } - } - }; - auto const &outline { track_rail_profile( m_profile1.second ) }; - auto const count { Profile.size() }; - if( false == size.grooved || outline.size() != 2 * count ) { - auto cuts { plan.cuts }; - auto const reach { reaches( Profile ) }; - for( auto const &band : bands ) { - cuts.push_back( band_cut( band, reach ) ); - } - lay( Profile, cuts, true, treads ); - return true; - } - auto const height = [&]( std::size_t const Index ) { - return static_cast( outline[ Side > 0 ? Index : count - 1 - Index ].position.y ) - size.groove; }; - auto const above = [&]( std::size_t const Index ) { - return height( Index ) > 1e-4; }; - auto const floorpoint = [&]( std::size_t const A, std::size_t const B ) { - auto const t { static_cast( height( A ) / ( height( A ) - height( B ) ) ) }; - auto point { Profile[ above( A ) ? A : B ] }; - point.position = glm::mix( Profile[ A ].position, Profile[ B ].position, t ); - point.texture = glm::mix( Profile[ A ].texture, Profile[ B ].texture, t ); - return point; }; - auto const flattened = [&]( std::size_t const Index ) { - auto point { Profile[ Index ] }; - auto const &file { outline[ Side > 0 ? Index : count - 1 - Index ].position }; - if( file.y > size.crest - 0.0005 || ( file.x < -0.02 && file.y > size.liptop - 0.0005 ) ) { - point.normal = { 0.f, 1.f, 0.f }; - point.texture.x = Drawing.platetexture; - } - return point; }; - std::vector tops, flats; - gfx::vertex_array body; - std::vector floors; - auto floortexture { 0.f }; - auto floortextures { 0 }; - for( std::size_t i = 0; i < count; ++i ) { - if( false == above( i ) ) { - if( height( i ) > -1e-4 ) { - floors.push_back( body.size() ); - floortexture += Profile[ i ].texture.x; - ++floortextures; - } - body.push_back( Profile[ i ] ); - continue; - } - auto const first { i }; - while( i + 1 < count && above( i + 1 ) ) { - ++i; - } - gfx::vertex_array top, flat; - if( first > 0 ) { - if( height( first - 1 ) > -1e-4 ) { - top.push_back( Profile[ first - 1 ] ); - flat.push_back( flattened( first - 1 ) ); - } - else { - top.push_back( floorpoint( first - 1, first ) ); - flat.push_back( top.back() ); - body.push_back( top.back() ); - floors.push_back( body.size() ); - body.push_back( top.back() ); - } - } - for( auto k { first }; k <= i; ++k ) { - top.push_back( Profile[ k ] ); - flat.push_back( flattened( k ) ); - } - if( i + 1 < count ) { - if( height( i + 1 ) > -1e-4 ) { - top.push_back( Profile[ i + 1 ] ); - flat.push_back( flattened( i + 1 ) ); - } - else { - top.push_back( floorpoint( i, i + 1 ) ); - flat.push_back( top.back() ); - floors.push_back( body.size() ); - body.push_back( top.back() ); - body.push_back( top.back() ); - } - } - for( auto *strip : { &top, &flat } ) { - gfx::vertex_array doubled { strip->front(), strip->front() }; - for( std::size_t k = 1; k + 1 < strip->size(); ++k ) { - doubled.insert( doubled.end(), 2, ( *strip )[ k ] ); - } - doubled.insert( doubled.end(), 2, strip->back() ); - *strip = doubled; - } - tops.push_back( top ); - flats.push_back( flat ); - } - if( floors.size() > 1 ) { - auto const across { body[ floors.back() ].position - body[ floors.front() ].position }; - auto up { glm::normalize( glm::vec3{ -across.y, across.x, 0.f } ) }; - if( up.y < 0.f ) { - up = -up; - } - for( auto const index : floors ) { - body[ index ].normal = up; - if( floortextures > 0 ) { - body[ index ].texture.x = floortexture / floortextures; - } - } - } - std::vector wholecuts { plan.cuts }; - auto const bodyreach { reaches( body ) }; - for( auto const &band : bands ) { - if( band.whole ) { - wholecuts.push_back( band_cut( band, bodyreach ) ); - } - } - lay( body, wholecuts, false, {} ); - auto const wallu { floortextures > 0 ? floortexture / floortextures : -1.f }; - for( std::size_t k = 0; k < tops.size(); ++k ) { - auto const reach { reaches( tops[ k ] ) }; - auto const head { reach.x + reach.y > 0.0 }; - auto edge { head ? 1e9 : -1e9 }; - for( auto const &point : tops[ k ] ) { - if( point.position.y > ( head ? size.crest : size.liptop ) - 0.0005 ) { - auto const across { static_cast( Side * point.position.x - halfwidth ) }; - edge = ( head ? std::min( edge, across ) : std::max( edge, across ) ); - } - } - std::vector cuts { plan.cuts }; - for( auto const &band : bands ) { - auto cut { band_cut( band, reach ) }; - if( false == band.whole && std::abs( band.facing ) >= 1e-3 && std::abs( edge ) < 1.0 ) { - auto const blunt { edge + ( head ? nose_tip : -nose_tip ) }; - if( head == ( band.facing / band.into > 0.0 ) ) { - cut.start = std::max( cut.start, band.at + ( ( band.into > 0.0 ? band.to : band.from ) + band.facing * blunt ) / band.into ); - cut.startcap = true; - } - else { - cut.end = std::min( cut.end, band.at + ( ( band.into > 0.0 ? band.from : band.to ) + band.facing * blunt ) / band.into ); - cut.endcap = true; - } - } - cuts.push_back( cut ); - } - std::vector spans { Layout.blocks[ Path ][ Side > 0 ] }; - for( auto const &cut : cuts ) { - for( auto &span : spans ) { - if( cut.at > span.x && cut.at < span.y ) { - span = { std::min( span.x, cut.end ), std::max( span.y, cut.start ) }; - } - } - } - std::sort( spans.begin(), spans.end(), byposition ); - std::vector merged; - for( auto const &span : spans ) { - if( false == merged.empty() && span.x <= merged.back().y ) { - merged.back().y = std::max( merged.back().y, span.y ); - } - else { - merged.push_back( span ); - } - } - if( merged.empty() ) { - lay( tops[ k ], cuts, false, treads, wallu ); - continue; - } - std::vector blocktreads; - for( auto const &span : merged ) { - for( auto const &tread : treads ) { - if( tread.from < span.y && tread.to > span.x ) { - blocktreads.push_back( { span.x, span.y, tread.material } ); - break; - } - } - } - auto const seam = []( double const At, double const End, double const Start ) { - rail_cut cut; - cut.at = At; - cut.end = End; - cut.start = Start; - cut.endcap = false; - cut.startcap = false; - return cut; }; - std::vector outside, inside; - for( auto const &cut : cuts ) { - auto const within { std::any_of( merged.begin(), merged.end(), [&]( glm::dvec2 const &Span ) { return cut.at > Span.x && cut.at < Span.y; } ) }; - ( within ? inside : outside ).push_back( cut ); - } - inside.push_back( seam( merged.front().x - 0.001, -1e9, merged.front().x ) ); - for( std::size_t m = 0; m < merged.size(); ++m ) { - outside.push_back( seam( 0.5 * ( merged[ m ].x + merged[ m ].y ), merged[ m ].x, merged[ m ].y ) ); - inside.push_back( m + 1 < merged.size() ? - seam( 0.5 * ( merged[ m ].y + merged[ m + 1 ].x ), merged[ m ].y, merged[ m + 1 ].x ) : - seam( merged[ m ].y + 0.001, merged[ m ].y, 1e9 ) ); - } - lay( tops[ k ], outside, false, treads, wallu ); - lay( flats[ k ], inside, false, blocktreads, wallu ); - } - return true; -} diff --git a/world/TrackRails.h b/world/TrackRails.h index 62f97b3f..f8da7206 100644 --- a/world/TrackRails.h +++ b/world/TrackRails.h @@ -10,26 +10,12 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "world/Track.h" +#include "world/GaugeLine.h" namespace track_rails { -double constexpr flangeway { 0.045 }; -double constexpr blend { 0.015 }; -double constexpr tread_fade { 0.02 }; -double constexpr wheel_width { 0.14 }; -double constexpr back_to_back { 1.36 }; -double constexpr rolled_band { wheel_width - ( 1.435 - back_to_back ) / 2.0 }; -double constexpr nose_tip { 0.012 }; -double constexpr middle_angle { 0.35 }; -double constexpr wide_angle { 0.785 }; -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(); +rail_size measure_rail( gfx::vertex_array const &Profile ); // 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 ); @@ -47,6 +33,14 @@ 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 ); +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 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 );