mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-22 12:03:20 +02:00
merge feat/rail-crossings: the split rail code, mitred wide crossings, guard rail treads, check rail spacers and frogs for every rail profile, with the geometric switch blades moved into RailCrossings, their shape in track_rails::switch_blade and the lowering of the head in loft_span
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<std::uint64_t>(static_cast<std::int64_t>(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<unsigned char>(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<TTrack::geometryhandle_sequence const *> 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<unsigned long long>(hash)));
|
||||
WriteLog(format("SELFTEST geom %s: chunks %d, vertices %zu, hash %016llx, exact %016llx", track->name().c_str(), chunks, count, static_cast<unsigned long long>(hash), static_cast<unsigned long long>(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<unsigned long long>(total)));
|
||||
WriteLog(format("SELFTEST geom total: tracks %d, vertices %zu, hash %016llx, exact %016llx", tracks, vertices, static_cast<unsigned long long>(total), static_cast<unsigned long long>(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 <key>=<value>... 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)
|
||||
|
||||
65
world/GaugeLine.cpp
Normal file
65
world/GaugeLine.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "world/GaugeLine.h"
|
||||
|
||||
namespace track_rails {
|
||||
|
||||
gauge_line make_gauge_line( TSegment const &Segment, double const Offset, double const From ) {
|
||||
|
||||
gauge_line line;
|
||||
line.segment = &Segment;
|
||||
line.offset = Offset;
|
||||
line.from = From;
|
||||
auto const length { Segment.GetLength() };
|
||||
auto const add = [&]( double const Distance ) {
|
||||
glm::dvec3 point, tangent;
|
||||
line.at( Distance, point, tangent );
|
||||
line.points.push_back( point );
|
||||
line.distances.push_back( Distance ); };
|
||||
auto const count { std::max( { 1, Segment.RaSegCount(), static_cast<int>( std::ceil( ( length - From ) / 5.0 ) ) } ) };
|
||||
for( int i = 0; i <= count; ++i ) {
|
||||
add( From + ( length - From ) * i / count );
|
||||
}
|
||||
line.low = line.high = plan( line.points.front() );
|
||||
for( auto const &point : line.points ) {
|
||||
line.low = glm::min( line.low, plan( point ) );
|
||||
line.high = glm::max( line.high, plan( point ) );
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
double lateral( gauge_line const &Line, int const Side, glm::dvec3 const &Point, double &Distance ) {
|
||||
|
||||
glm::dvec3 point, tangent;
|
||||
for( int step = 0; step < 8; ++step ) {
|
||||
Line.at( Distance, point, tangent );
|
||||
Distance += glm::dot( plan( Point - point ), glm::normalize( plan( tangent ) ) );
|
||||
}
|
||||
Line.at( Distance, point, tangent );
|
||||
return -glm::dot( plan( Point - point ), plan( Line.normal( Distance, Side ) ) );
|
||||
}
|
||||
|
||||
double nearest_distance( gauge_line const &Line, glm::dvec3 const &Point ) {
|
||||
|
||||
auto result { Line.distances.front() };
|
||||
auto nearest { std::numeric_limits<double>::max() };
|
||||
for( std::size_t i = 0; i < Line.points.size(); ++i ) {
|
||||
auto const gap { glm::length( plan( Line.points[ i ] - Point ) ) };
|
||||
if( gap < nearest ) {
|
||||
nearest = gap;
|
||||
result = Line.distances[ i ];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace track_rails
|
||||
72
world/GaugeLine.h
Normal file
72
world/GaugeLine.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "world/Segment.h"
|
||||
|
||||
namespace track_rails {
|
||||
|
||||
inline glm::dvec2 plan( glm::dvec3 const &V ) {
|
||||
|
||||
return glm::dvec2{ V.x, V.z };
|
||||
}
|
||||
|
||||
inline double cross( glm::dvec2 const &A, glm::dvec2 const &B ) {
|
||||
|
||||
return A.x * B.y - A.y * B.x;
|
||||
}
|
||||
|
||||
inline bool meet( glm::dvec2 const &A0, glm::dvec2 const &A, glm::dvec2 const &B0, glm::dvec2 const &B, double const Parallel, double &U, double &V ) {
|
||||
|
||||
auto const denominator { cross( A, B ) };
|
||||
if( std::abs( denominator ) < Parallel ) {
|
||||
return false;
|
||||
}
|
||||
U = cross( B0 - A0, B ) / denominator;
|
||||
V = cross( B0 - A0, A ) / denominator;
|
||||
return true;
|
||||
}
|
||||
|
||||
// gauge line of a rail: the running edge of its head, along a path and on past its ends along the tangents
|
||||
struct gauge_line {
|
||||
TSegment const *segment { nullptr };
|
||||
double offset { 0.0 }; // across the path, to the running edge
|
||||
double from { 0.0 };
|
||||
std::vector<glm::dvec3> points;
|
||||
std::vector<double> distances;
|
||||
glm::dvec2 low { 0.0 };
|
||||
glm::dvec2 high { 0.0 };
|
||||
|
||||
void at( double const Distance, glm::dvec3 &Point, glm::dvec3 &Tangent ) const {
|
||||
auto const length { segment->GetLength() };
|
||||
auto const local = [&]( double const D ) {
|
||||
glm::dvec3 point, across;
|
||||
segment->plan_frame( D, point, across );
|
||||
return point + across * offset; };
|
||||
auto const inside { std::clamp( Distance, 0.0, length ) };
|
||||
auto const delta { std::min( 0.05, 0.5 * length ) };
|
||||
auto const a { std::max( inside - delta, 0.0 ) };
|
||||
auto const b { std::min( inside + delta, length ) };
|
||||
Tangent = local( b ) - local( a );
|
||||
Tangent = ( glm::length2( Tangent ) > 0.0 ? glm::normalize( Tangent ) : glm::dvec3{ 1.0, 0.0, 0.0 } );
|
||||
Point = local( inside ) + Tangent * ( Distance - inside );
|
||||
}
|
||||
glm::dvec3 normal( double const Distance, int const Side ) const {
|
||||
glm::dvec3 point, across;
|
||||
segment->plan_frame( std::clamp( Distance, 0.0, segment->GetLength() ), point, across );
|
||||
return across * static_cast<double>( -Side );
|
||||
}
|
||||
};
|
||||
|
||||
gauge_line make_gauge_line( TSegment const &Segment, double const Offset, double const From );
|
||||
double lateral( gauge_line const &Line, int const Side, glm::dvec3 const &Point, double &Distance );
|
||||
double nearest_distance( gauge_line const &Line, glm::dvec3 const &Point );
|
||||
|
||||
} // namespace track_rails
|
||||
110
world/Knots.h
Normal file
110
world/Knots.h
Normal file
@@ -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 <algorithm>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace knots {
|
||||
|
||||
using line = std::vector<glm::dvec2>;
|
||||
|
||||
inline bool by_position( glm::dvec2 const &A, glm::dvec2 const &B ) {
|
||||
|
||||
return A.x < B.x;
|
||||
}
|
||||
|
||||
inline void sort( line &Line ) {
|
||||
|
||||
std::sort( Line.begin(), Line.end(), by_position );
|
||||
}
|
||||
|
||||
inline double sample( line const &Line, double const Position, double const Default ) {
|
||||
|
||||
if( Line.empty() ) {
|
||||
return Default;
|
||||
}
|
||||
if( Position <= Line.front().x ) {
|
||||
return Line.front().y;
|
||||
}
|
||||
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<double> stations( line const &A, line const &B ) {
|
||||
|
||||
std::vector<double> result;
|
||||
for( auto const *each : { &A, &B } ) {
|
||||
for( auto const &knot : *each ) {
|
||||
result.push_back( knot.x );
|
||||
}
|
||||
}
|
||||
std::sort( result.begin(), result.end() );
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::vector<double> meetings( line const &A, line const &B ) {
|
||||
|
||||
std::vector<double> result;
|
||||
if( A.empty() || B.empty() ) {
|
||||
return result;
|
||||
}
|
||||
auto const all { stations( A, B ) };
|
||||
for( std::size_t i = 1; i < all.size(); ++i ) {
|
||||
auto const before { sample( A, all[ i - 1 ], 0.0 ) - sample( B, all[ i - 1 ], 0.0 ) };
|
||||
auto const after { sample( A, all[ i ], 0.0 ) - sample( B, all[ i ], 0.0 ) };
|
||||
if( before * after < 0.0 ) {
|
||||
result.push_back( all[ i - 1 ] + ( all[ i ] - all[ i - 1 ] ) * before / ( before - after ) );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline line bound( line const &A, line const &B, bool const Lower ) {
|
||||
|
||||
if( A.empty() || B.empty() ) {
|
||||
return ( A.empty() ? B : A );
|
||||
}
|
||||
auto all { stations( A, B ) };
|
||||
auto const crossed { meetings( A, B ) };
|
||||
all.insert( all.end(), crossed.begin(), crossed.end() );
|
||||
std::sort( all.begin(), all.end() );
|
||||
line result;
|
||||
for( auto const position : all ) {
|
||||
auto const a { sample( A, position, 0.0 ) };
|
||||
auto const b { sample( B, position, 0.0 ) };
|
||||
result.emplace_back( position, Lower ? std::min( a, b ) : std::max( a, b ) );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline line middle( line const &A, line const &B ) {
|
||||
|
||||
line result;
|
||||
for( auto const position : stations( A, B ) ) {
|
||||
result.emplace_back( position, 0.5 * ( sample( A, position, 0.0 ) + sample( B, position, 0.0 ) ) );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace knots
|
||||
813
world/RailCrossings.cpp
Normal file
813
world/RailCrossings.cpp
Normal file
@@ -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<gauge_line> lines[ 2 ][ 2 ][ 2 ]; // path, right side, from the blades
|
||||
std::optional<std::vector<track_rails::rail_crossing>> own;
|
||||
std::optional<std::vector<track_rails::rail_crossing>> joined;
|
||||
std::optional<track_rails::check_rail_set> checks;
|
||||
std::optional<track_rails::switch_blade> blades[ 2 ];
|
||||
};
|
||||
std::unordered_map<TTrack const *, rails_memo> 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<int>( std::ceil( Track.SwitchExtension->Segments[ Path ]->RaSegCount() * 0.65 ) );
|
||||
}
|
||||
|
||||
double blade_length( TTrack const &Track, int const Path ) {
|
||||
|
||||
if( auto const &shape { Track.blade( Path ) }; shape.valid ) {
|
||||
return shape.heel;
|
||||
}
|
||||
auto const &segment { *Track.SwitchExtension->Segments[ Path ] };
|
||||
return segment.GetLength() * blade_steps( Track, 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<check_rail> 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<TTrack *> nearby_rails( TTrack &Track ) {
|
||||
|
||||
std::vector<TTrack *> others;
|
||||
auto const sections { simulation::Region->sections( Track.location(), static_cast<float>( Track.radius() + 50.0 ) ) };
|
||||
for( auto *section : sections ) {
|
||||
for( auto const &cell : section->m_cells ) {
|
||||
if( glm::length( cell.area().center - Track.location() ) > cell.area().radius + Track.radius() + 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<rail_crossing> &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<rail_crossing> own_crossings( TTrack &Track ) {
|
||||
|
||||
std::vector<rail_crossing> 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<rail_crossing> &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<std::array<std::vector<planned_check>, 2>, 2>; // path, right side
|
||||
|
||||
void add_acute_check( std::vector<planned_check> &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<rail_crossing> 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<planned_check> &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<gauge_line> 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<planned_check> 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<gauge_line> 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<check_rail> &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<track_rails::rail_crossing> 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<rail_crossing> 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<glm::dvec2> lines[ 2 ];
|
||||
std::vector<double> 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<int>( 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<int>( std::ceil( length / 0.1 ) ) ) };
|
||||
auto &gauge { result.gauge };
|
||||
glm::dvec3 point, tangent;
|
||||
ours.at( 0.0, point, tangent );
|
||||
auto otherat { nearest_distance( theirs, point ) };
|
||||
for( int i = 0; i <= count; ++i ) {
|
||||
auto const distance { length * i / count };
|
||||
ours.at( distance, point, tangent );
|
||||
lateral( theirs, side, point, otherat );
|
||||
glm::dvec3 line;
|
||||
theirs.at( otherat, line, tangent );
|
||||
auto at { distance };
|
||||
auto const gap { lateral( ours, side, line, at ) };
|
||||
gauge.emplace_back( at, gap );
|
||||
}
|
||||
std::sort( gauge.begin(), gauge.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } );
|
||||
if( std::abs( gauge.front().y ) > 0.002 ) {
|
||||
return result;
|
||||
}
|
||||
auto const reach = [&]( double const Width ) {
|
||||
for( std::size_t i = 1; i < gauge.size(); ++i ) {
|
||||
if( gauge[ i ].y >= Width ) {
|
||||
auto const &a { gauge[ i - 1 ] };
|
||||
auto const &b { gauge[ i ] };
|
||||
return glm::mix( a.x, b.x, std::clamp( ( Width - a.y ) / std::max( b.y - a.y, 1e-9 ), 0.0, 1.0 ) );
|
||||
}
|
||||
}
|
||||
return -1.0; };
|
||||
result.tip = reach( blade_point );
|
||||
result.full = reach( size.head + blade_gap );
|
||||
auto const clear { reach( size.head + blade_clearance ) };
|
||||
auto const foot { reach( size.high + blade_gap ) };
|
||||
auto const limit { 0.9 * length };
|
||||
if( result.tip < 0.0 || result.full < 0.0 || clear < 0.0 || clear > limit ) {
|
||||
return result;
|
||||
}
|
||||
auto const bend = [&]( double const Distance, double const Heel ) {
|
||||
auto const u { std::clamp( ( Heel - Distance ) / std::max( Heel - result.tip, 1e-6 ), 0.0, 1.0 ) };
|
||||
return ( result.rigid ? u : u * u * ( 3.0 - u ) * 0.5 ); };
|
||||
auto const opening = [&]( double const Heel, double &At ) {
|
||||
auto least { 1e9 };
|
||||
for( auto const &sample : gauge ) {
|
||||
if( sample.x < result.tip || sample.x > Heel ) {
|
||||
continue;
|
||||
}
|
||||
auto const gap { std::max( 0.0, sample.y - size.head ) + blade_throw * bend( sample.x, Heel ) };
|
||||
if( gap < least ) {
|
||||
least = gap;
|
||||
At = sample.x;
|
||||
}
|
||||
}
|
||||
return least; };
|
||||
auto at { 0.0 };
|
||||
if( opening( limit, at ) < blade_clearance ) {
|
||||
result.rigid = true;
|
||||
if( opening( limit, at ) < blade_clearance ) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
auto low { clear };
|
||||
auto high { limit };
|
||||
for( int step = 0; step < 40; ++step ) {
|
||||
auto const middle { 0.5 * ( low + high ) };
|
||||
( opening( middle, at ) >= blade_clearance ? high : low ) = middle;
|
||||
}
|
||||
result.heel = high;
|
||||
result.opening = opening( result.heel, result.openingat );
|
||||
for( std::size_t i = 1; i < gauge.size() && gauge[ i ].x <= result.heel; ++i ) {
|
||||
if( gauge[ i ].y < gauge[ i - 1 ].y - 1e-4 ) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
auto const &profile { track_rail_profile( m_profile1.second ) };
|
||||
auto const half { profile.size() / 2 };
|
||||
auto bladetop { -1e9 };
|
||||
for( auto i { half }; i < profile.size(); ++i ) {
|
||||
bladetop = std::max( bladetop, static_cast<double>( profile[ i ].position.y ) );
|
||||
}
|
||||
result.drop = ( half > 0 ? std::max( 0.0, size.crest - bladetop ) : 0.0 );
|
||||
auto const level = [&]( double const Distance ) {
|
||||
for( std::size_t i = 1; i < gauge.size(); ++i ) {
|
||||
if( gauge[ i ].x >= Distance ) {
|
||||
auto const &a { gauge[ i - 1 ] };
|
||||
auto const &b { gauge[ i ] };
|
||||
return glm::mix( a.y, b.y, std::clamp( ( Distance - a.x ) / std::max( b.x - a.x, 1e-9 ), 0.0, 1.0 ) );
|
||||
}
|
||||
}
|
||||
return gauge.back().y; };
|
||||
auto const lowered { reach( blade_lowered + blade_gap ) };
|
||||
auto const cutto { std::min( result.heel, foot >= 0.0 ? foot + blade_spacing : result.heel ) };
|
||||
for( auto distance { result.tip }; distance < cutto + 0.5 * blade_spacing; distance += blade_spacing ) {
|
||||
auto const at { std::min( distance, cutto ) };
|
||||
auto const width { level( at ) - blade_gap };
|
||||
result.extents.emplace_back( at, width );
|
||||
if( result.drop > 0.0 && ( lowered < 0.0 || at <= lowered + blade_spacing ) ) {
|
||||
result.drops.emplace_back( at, result.drop * std::clamp( 1.0 - width / blade_lowered, 0.0, 1.0 ) );
|
||||
}
|
||||
}
|
||||
auto const steps { std::max( 1, static_cast<int>( std::ceil( ( result.heel - result.tip ) / blade_spacing ) ) ) };
|
||||
for( int i = 0; i <= steps; ++i ) {
|
||||
auto const distance { glm::mix( result.tip, result.heel, static_cast<double>( i ) / steps ) };
|
||||
result.bends.emplace_back( distance, bend( distance, result.heel ) );
|
||||
}
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
1148
world/RailLayer.cpp
Normal file
1148
world/RailLayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
185
world/RailLayout.h
Normal file
185
world/RailLayout.h
Normal file
@@ -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 <array>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include "utilities/Classes.h"
|
||||
#include "world/Knots.h"
|
||||
|
||||
class TTrack;
|
||||
|
||||
namespace track_rails {
|
||||
|
||||
double constexpr flangeway { 0.045 };
|
||||
double constexpr blend { 0.015 };
|
||||
double constexpr tread_fade { 0.02 };
|
||||
double constexpr wheel_width { 0.14 };
|
||||
double constexpr back_to_back { 1.36 };
|
||||
double constexpr rolled_band { wheel_width - ( 1.435 - back_to_back ) / 2.0 };
|
||||
double constexpr 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<glm::dvec2> gauge;
|
||||
std::vector<glm::dvec2> extents;
|
||||
std::vector<glm::dvec2> drops;
|
||||
std::vector<glm::dvec2> bends;
|
||||
};
|
||||
// where a rail of this track crosses a rail of another one, found from their gauge lines
|
||||
struct rail_crossing {
|
||||
TTrack *track { nullptr };
|
||||
int path { 0 };
|
||||
int side { 0 }; // rail profile of the crossing rail, 1 right, -1 left
|
||||
double at { 0.0 }; // distance of the crossing along the path, past the ends where the track goes on
|
||||
double angle { 0.0 };
|
||||
double into { 0.0 }; // the path direction against the gauge normal of the other rail, above zero: going into the other track
|
||||
double facing { 0.0 }; // the gauge normals of both rails against each other, below zero: an acute frog
|
||||
bool nose { false }; // at an acute frog the rail goes on as the nose rail, the other one as the rail beside it
|
||||
int otherpath { 0 };
|
||||
int otherside { 0 }; // rail profile of the other rail, 1 right, -1 left
|
||||
double otherat { 0.0 }; // distance of the crossing along the other path
|
||||
bool aligned { true }; // both paths run the same way at the crossing
|
||||
TTrack *owner { nullptr };
|
||||
};
|
||||
// the size of the rail profile, out from the gauge face: positive outwards, the side of the head away from the track
|
||||
struct rail_size {
|
||||
double head { 0.0735 }; // how far the head reaches out
|
||||
double 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<std::array<std::vector<check_rail>, 2>, 2>; // path, right side
|
||||
inline double check_flare( check_rail const &Check ) {
|
||||
|
||||
return std::min( guard_flare, 0.4 * ( Check.to - Check.from ) );
|
||||
}
|
||||
// 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<rail_cut> cuts;
|
||||
};
|
||||
struct rail_tread {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
material_handle material { 0 };
|
||||
int anchor { 0 };
|
||||
knots::line edge;
|
||||
knots::line gauge;
|
||||
bool below { false };
|
||||
double fade { tread_fade };
|
||||
};
|
||||
// guard rails of a crossing at a wide angle: the rail profile laid inside the running rail across the flangeway, broken like the rails
|
||||
struct guard_rail {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
rail_plan plan;
|
||||
bool flarefrom { true };
|
||||
bool flareto { true };
|
||||
glm::dvec2 frommiter { 0.0 };
|
||||
glm::dvec2 tomiter { 0.0 };
|
||||
bool through { true };
|
||||
std::vector<rail_tread> treads;
|
||||
knots::line castings;
|
||||
};
|
||||
struct rail_band {
|
||||
double at { 0.0 };
|
||||
double into { 0.0 };
|
||||
double facing { 0.0 };
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
bool whole { true };
|
||||
};
|
||||
struct rail_fill {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
knots::line inner, outer;
|
||||
bool capfrom { false };
|
||||
bool capto { false };
|
||||
double lift { 0.0 };
|
||||
bool web { false };
|
||||
};
|
||||
struct rail_layout {
|
||||
rail_plan plans[ 2 ][ 2 ]; // path, right side
|
||||
std::vector<rail_band> bands[ 2 ][ 2 ];
|
||||
std::vector<rail_fill> fills[ 2 ][ 2 ];
|
||||
knots::line blocks[ 2 ][ 2 ];
|
||||
std::vector<rail_tread> treads[ 2 ][ 2 ];
|
||||
std::vector<guard_rail> guards[ 2 ][ 2 ];
|
||||
knots::line wingends[ 2 ][ 2 ]; // ends of the wing rails of other rails running along each rail, and which way they run there
|
||||
check_rail_set checks;
|
||||
std::vector<TTrack *> crossed; // tracks whose rails cross the rails of this one
|
||||
};
|
||||
|
||||
} // namespace track_rails
|
||||
207
world/RailMaterials.cpp
Normal file
207
world/RailMaterials.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "world/TrackRails.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "stb/stb_image.h"
|
||||
|
||||
namespace track_rails {
|
||||
|
||||
material_handle cut_material( material_handle const Material ) {
|
||||
|
||||
static auto const unrolled { std::getenv( "EU07_EDITOR_UNROLLED" ) != nullptr };
|
||||
return unrolled ? GfxRenderer->Fetch_Material( "editor_unrolled" ) : GfxRenderer->Matte_Material( Material );
|
||||
}
|
||||
|
||||
material_handle tint_material( material_handle const Material, glm::vec3 const &Colour ) {
|
||||
|
||||
return GfxRenderer->Tinted_Material( Material, Colour );
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct colour_mean {
|
||||
double sum { 0.0 };
|
||||
glm::dvec3 colour { 0.0 };
|
||||
std::size_t count { 0 };
|
||||
|
||||
void add( float const Red, float const Green, float const Blue ) {
|
||||
sum += 0.2126 * Red + 0.7152 * Green + 0.0722 * Blue;
|
||||
colour += glm::pow( glm::dvec3{ Red, Green, Blue } / 255.0, glm::dvec3{ 2.2 } );
|
||||
++count; }
|
||||
};
|
||||
|
||||
std::pair<int, int> band_columns( glm::vec2 const &Band, int const Width ) {
|
||||
|
||||
auto const first { std::clamp( static_cast<int>( std::floor( std::min( Band.x, Band.y ) * Width ) ), 0, Width - 1 ) };
|
||||
auto const last { std::clamp( static_cast<int>( std::ceil( std::max( Band.x, Band.y ) * Width ) ) - 1, first, Width - 1 ) };
|
||||
return { first, last };
|
||||
}
|
||||
|
||||
glm::vec3 unpack_565( unsigned const Color ) {
|
||||
|
||||
return glm::vec3{ ( ( Color >> 11 ) & 31 ) * 255.f / 31.f, ( ( Color >> 5 ) & 63 ) * 255.f / 63.f, ( Color & 31 ) * 255.f / 31.f };
|
||||
}
|
||||
|
||||
void add_dxt_block( unsigned char const *Block, bool const Alpha, int const Column, int const First, int const Last, colour_mean &Mean ) {
|
||||
|
||||
auto const low { static_cast<unsigned>( Block[ 0 ] | Block[ 1 ] << 8 ) };
|
||||
auto const top { static_cast<unsigned>( Block[ 2 ] | Block[ 3 ] << 8 ) };
|
||||
auto const opaque { Alpha || low > top };
|
||||
glm::vec3 const colors[ 4 ] {
|
||||
unpack_565( low ),
|
||||
unpack_565( top ),
|
||||
opaque ? ( 2.f * unpack_565( low ) + unpack_565( top ) ) / 3.f : 0.5f * ( unpack_565( low ) + unpack_565( top ) ),
|
||||
opaque ? ( unpack_565( low ) + 2.f * unpack_565( top ) ) / 3.f : glm::vec3{ 0.f } };
|
||||
auto const bits { static_cast<std::uint32_t>( Block[ 4 ] ) | static_cast<std::uint32_t>( Block[ 5 ] ) << 8 | static_cast<std::uint32_t>( Block[ 6 ] ) << 16 | static_cast<std::uint32_t>( Block[ 7 ] ) << 24 };
|
||||
for( int texel = 0; texel < 16; ++texel ) {
|
||||
auto const x { Column * 4 + texel % 4 };
|
||||
auto const index { ( bits >> ( 2 * texel ) ) & 3 };
|
||||
if( x < First || x > Last || ( false == opaque && index == 3 ) ) {
|
||||
continue;
|
||||
}
|
||||
Mean.add( colors[ index ].r, colors[ index ].g, colors[ index ].b );
|
||||
}
|
||||
}
|
||||
|
||||
void sample_dds( std::string const &File, glm::vec2 const &Band, colour_mean &Mean ) {
|
||||
|
||||
std::ifstream input( File, std::ios::binary );
|
||||
std::array<unsigned char, 128> header {};
|
||||
if( !input.read( reinterpret_cast<char *>( header.data() ), header.size() ) || false == std::equal( header.begin(), header.begin() + 4, "DDS " ) ) {
|
||||
return;
|
||||
}
|
||||
auto const word = [&]( std::size_t const Offset ) {
|
||||
return static_cast<std::uint32_t>( header[ Offset ] ) | static_cast<std::uint32_t>( header[ Offset + 1 ] ) << 8 | static_cast<std::uint32_t>( header[ Offset + 2 ] ) << 16 | static_cast<std::uint32_t>( header[ Offset + 3 ] ) << 24; };
|
||||
auto const height { static_cast<int>( word( 12 ) ) };
|
||||
auto const width { static_cast<int>( word( 16 ) ) };
|
||||
auto const fourcc { word( 84 ) };
|
||||
auto const blocksize { fourcc == 0x31545844u ? 8 : ( fourcc == 0x33545844u || fourcc == 0x35545844u ) ? 16 : 0 };
|
||||
if( blocksize == 0 || width <= 0 || height <= 0 ) {
|
||||
return;
|
||||
}
|
||||
auto const wide { ( width + 3 ) / 4 };
|
||||
auto const high { ( height + 3 ) / 4 };
|
||||
std::vector<unsigned char> data( static_cast<std::size_t>( wide ) * high * blocksize );
|
||||
if( !input.read( reinterpret_cast<char *>( data.data() ), data.size() ) ) {
|
||||
return;
|
||||
}
|
||||
auto const [ first, last ] { band_columns( Band, width ) };
|
||||
for( int row = 0; row < high; ++row ) {
|
||||
for( int column = first / 4; column <= last / 4; ++column ) {
|
||||
add_dxt_block( &data[ ( static_cast<std::size_t>( row ) * wide + column ) * blocksize + ( blocksize - 8 ) ], blocksize == 16, column, first, last, Mean );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sample_image( std::string const &File, glm::vec2 const &Band, colour_mean &Mean ) {
|
||||
|
||||
int width, height, components;
|
||||
auto *pixels { stbi_load( File.c_str(), &width, &height, &components, 4 ) };
|
||||
if( pixels == nullptr ) {
|
||||
return;
|
||||
}
|
||||
auto const [ first, last ] { band_columns( Band, width ) };
|
||||
for( int y = 0; y < height; ++y ) {
|
||||
for( int x = first; x <= last; ++x ) {
|
||||
auto const *pixel { &pixels[ ( static_cast<std::size_t>( y ) * width + x ) * 4 ] };
|
||||
Mean.add( pixel[ 0 ], pixel[ 1 ], pixel[ 2 ] );
|
||||
}
|
||||
}
|
||||
stbi_image_free( pixels );
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::pair<float, glm::vec3> const &tread_sample( material_handle const Material, glm::vec2 const &Band ) {
|
||||
|
||||
static std::map<std::tuple<material_handle, float, float>, std::pair<float, glm::vec3>> samples;
|
||||
auto const key { std::make_tuple( Material, Band.x, Band.y ) };
|
||||
if( auto const lookup { samples.find( key ) }; lookup != samples.end() ) {
|
||||
return lookup->second;
|
||||
}
|
||||
auto &result { samples[ key ] };
|
||||
result = { -1.f, glm::vec3{ 0.f } };
|
||||
if( Material == null_handle ) {
|
||||
return result;
|
||||
}
|
||||
auto const texture { GfxRenderer->Material( Material )->GetTexture( 0 ) };
|
||||
if( texture == null_handle ) {
|
||||
return result;
|
||||
}
|
||||
auto const &image { GfxRenderer->Texture( texture ) };
|
||||
auto const type { std::string( image.get_type() ) };
|
||||
auto const file { std::string( image.get_name() ) + type };
|
||||
colour_mean mean;
|
||||
if( type == ".dds" ) {
|
||||
sample_dds( file, Band, mean );
|
||||
}
|
||||
else {
|
||||
sample_image( file, Band, mean );
|
||||
}
|
||||
if( mean.count > 0 ) {
|
||||
result = { static_cast<float>( mean.sum / mean.count ), glm::vec3{ mean.colour / static_cast<double>( mean.count ) } };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
float tread_brightness( material_handle const Material, glm::vec2 const &Band ) {
|
||||
|
||||
return tread_sample( Material, Band ).first;
|
||||
}
|
||||
|
||||
glm::vec3 tread_colour( material_handle const Material, glm::vec2 const &Band ) {
|
||||
|
||||
return tread_sample( Material, Band ).second;
|
||||
}
|
||||
|
||||
} // namespace track_rails
|
||||
|
||||
material_handle TTrack::rail_material( int const Path ) const {
|
||||
|
||||
return ( Path == 1 && eType == tt_Switch ? m_material2 : m_material1 );
|
||||
}
|
||||
|
||||
glm::vec2 TTrack::tread_band() const {
|
||||
|
||||
glm::vec2 band { 0.45f, 0.55f };
|
||||
if( m_profiles.empty() ) {
|
||||
return band;
|
||||
}
|
||||
auto const &outline { track_rail_profile( m_profile1.second ) };
|
||||
auto const count { outline.size() / 2 };
|
||||
auto top { -1e9f };
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
top = std::max( top, outline[ i ].position.y );
|
||||
}
|
||||
glm::vec2 found { 1e9f, -1e9f };
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
if( outline[ i ].position.y > top - 0.0005f ) {
|
||||
found = { std::min( found.x, outline[ i ].texture.s ), std::max( found.y, outline[ i ].texture.s ) };
|
||||
}
|
||||
}
|
||||
if( found.x > found.y ) {
|
||||
return band;
|
||||
}
|
||||
auto const middle { 0.5f * ( found.x + found.y ) };
|
||||
auto const half { std::max( 0.5f * ( found.y - found.x ), 0.01f ) };
|
||||
return { middle - half, middle + half };
|
||||
}
|
||||
|
||||
material_handle TTrack::shared_tread( int const Path, TTrack const &Other, int const Otherpath ) const {
|
||||
|
||||
auto const own { rail_material( Path ) };
|
||||
auto const theirs { Other.rail_material( Otherpath ) };
|
||||
if( theirs == null_handle || theirs == own ) {
|
||||
return own;
|
||||
}
|
||||
return ( track_rails::tread_brightness( theirs, Other.tread_band() ) > track_rails::tread_brightness( own, tread_band() ) ? theirs : own );
|
||||
}
|
||||
736
world/RailPlan.cpp
Normal file
736
world/RailPlan.cpp
Normal file
@@ -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<glm::dvec2> wing_extension( TTrack &Track, track_rails::check_rail_set const &Checks, int const Path, int const Side, double const At, double const Toward, double const Slope, TTrack &Partner, int const Partnerpath, int const Partnerside, double const Partnerat, double const Partnerdirection ) {
|
||||
|
||||
std::vector<glm::dvec2> knots;
|
||||
auto const partnerchecks { &Partner == &Track ? Checks : rail_checks( Partner ) };
|
||||
auto const wingend { Partnerat + Partnerdirection * wing_reach( Slope ) };
|
||||
auto farend { 0.0 };
|
||||
if( false == replaced_check( partnerchecks[ Partnerpath ][ Partnerside > 0 ], wingend, Partnerdirection, farend ) ) {
|
||||
return knots;
|
||||
}
|
||||
auto const theirs { make_gauge_line( path_segment( Partner, Partnerpath ), Partnerside * ( 0.5 * std::abs( Partner.fTrackWidth ) - flangeway ), 0.0 ) };
|
||||
auto const ours { make_gauge_line( path_segment( Track, Path ), Side * ( 0.5f * std::abs( Track.fTrackWidth ) ), 0.0 ) };
|
||||
auto s { At - Toward * wing_reach( Slope ) };
|
||||
auto const count { std::max( 1, static_cast<int>( std::ceil( std::abs( farend - wingend ) / 0.5 ) ) ) };
|
||||
for( int i = 1; i <= count; ++i ) {
|
||||
glm::dvec3 target, tangent, point;
|
||||
theirs.at( glm::mix( wingend, farend, static_cast<double>( i ) / count ), target, tangent );
|
||||
for( int step = 0; step < 4; ++step ) {
|
||||
ours.at( s, point, tangent );
|
||||
s += glm::dot( plan( target - point ), glm::normalize( plan( tangent ) ) );
|
||||
}
|
||||
ours.at( s, point, tangent );
|
||||
knots.emplace_back( s, -glm::dot( plan( target - point ), plan( ours.normal( s, Side ) ) ) );
|
||||
}
|
||||
return knots;
|
||||
}
|
||||
|
||||
// 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<glm::dvec2> const &Extension, std::vector<glm::dvec2> 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<glm::dvec2> 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<glm::dvec2> 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<track_rails::rail_fill> spacer_fills( track_rails::rail_fill const &Whole, double const From, double const To, knots::line const &Castings ) {
|
||||
|
||||
knots::line pieces;
|
||||
for( auto const &casting : Castings ) {
|
||||
if( false == pieces.empty() && casting.x <= pieces.back().y ) {
|
||||
pieces.back().y = std::max( pieces.back().y, casting.y );
|
||||
}
|
||||
else {
|
||||
pieces.push_back( casting );
|
||||
}
|
||||
}
|
||||
knots::line spacers;
|
||||
auto const place = [&]( double const Start, double const End, double const Step ) {
|
||||
auto const count { static_cast<int>( std::floor( ( End - Start - 0.5 * spacer_length ) / spacer_pitch ) ) };
|
||||
for( int i = 1; i <= count; ++i ) {
|
||||
auto const at { ( Step > 0.0 ? Start : End ) + Step * i * spacer_pitch };
|
||||
spacers.emplace_back( at - 0.5 * spacer_length, at + 0.5 * spacer_length );
|
||||
} };
|
||||
auto const first { From + spacer_length };
|
||||
auto const last { To - spacer_length };
|
||||
if( pieces.empty() ) {
|
||||
place( first - spacer_pitch, last, 1.0 );
|
||||
}
|
||||
else {
|
||||
place( first, pieces.front().x, -1.0 );
|
||||
for( std::size_t i = 1; i < pieces.size(); ++i ) {
|
||||
auto const middle { 0.5 * ( pieces[ i - 1 ].y + pieces[ i ].x ) };
|
||||
place( pieces[ i - 1 ].y, middle, 1.0 );
|
||||
place( middle, pieces[ i ].x, -1.0 );
|
||||
}
|
||||
place( pieces.back().y, last, 1.0 );
|
||||
}
|
||||
pieces.insert( pieces.end(), spacers.begin(), spacers.end() );
|
||||
std::vector<track_rails::rail_fill> fills;
|
||||
for( auto const &piece : pieces ) {
|
||||
auto fill { Whole };
|
||||
fill.from = std::max( piece.x, From );
|
||||
fill.to = std::min( piece.y, To );
|
||||
fill.capfrom = true;
|
||||
fill.capto = true;
|
||||
if( fill.to - fill.from > 0.01 ) {
|
||||
fills.push_back( fill );
|
||||
}
|
||||
}
|
||||
return fills;
|
||||
}
|
||||
|
||||
std::vector<track_rails::rail_fill> guard_fills( track_rails::guard_rail const &Guard, track_rails::rail_size const &Size ) {
|
||||
|
||||
auto const offset { Size.web - ( flangeway + Size.head ) };
|
||||
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<track_rails::rail_fill> check_fills( track_rails::check_rail const &Check, track_rails::rail_size const &Size ) {
|
||||
|
||||
auto const from { Check.from + check_flare( Check ) };
|
||||
auto const to { Check.to - check_flare( Check ) };
|
||||
track_rails::rail_fill whole;
|
||||
whole.web = true;
|
||||
whole.inner = { { from, -check_flangeway }, { to, -check_flangeway } };
|
||||
whole.outer = { { from, Size.web }, { to, Size.web } };
|
||||
return spacer_fills( whole, from, to, {} );
|
||||
}
|
||||
|
||||
bool goes_through( TTrack const &Track, TTrack const &Other ) {
|
||||
|
||||
if( Track.name() != Other.name() ) {
|
||||
return Track.name() < Other.name();
|
||||
}
|
||||
auto const here { Track.location() };
|
||||
auto const there { Other.location() };
|
||||
return ( here.x != there.x ? here.x < there.x : here.z < there.z );
|
||||
}
|
||||
|
||||
track_rails::rail_band groove_band( double const At, double const Into, double const Facing, bool const Through, track_rails::rail_size const &Other ) {
|
||||
|
||||
auto const seam { 0.001 };
|
||||
track_rails::rail_band band { At, Into, Facing };
|
||||
band.whole = ( false == Through );
|
||||
band.from = ( Through ? 0.0 : seam - Other.tread );
|
||||
band.to = ( Through ? ( Other.grooved ? -Other.lip : flangeway ) : -Other.ledge - seam );
|
||||
return band;
|
||||
}
|
||||
|
||||
void vee_fill( std::vector<track_rails::rail_fill> &Fills, double const At, double const Into, double const Facing, track_rails::rail_size const &Own, track_rails::rail_size const &Other, bool const Outwards ) {
|
||||
|
||||
auto const seam { 0.001 };
|
||||
auto const sign { Outwards ? 1.0 : -1.0 };
|
||||
auto const edge { Outwards ? std::max( Own.head, Own.crown ) : Own.rim };
|
||||
auto const flat { Outwards ? Own.tread : Own.ledge };
|
||||
auto const inside { Outwards == ( Facing > 0.0 ) };
|
||||
auto const closest { inside ? -Other.rim : -std::max( Other.head, Other.crown ) };
|
||||
auto const rounded { inside ? Other.ledge - Other.rim : std::max( Other.head, Other.crown ) - Other.tread };
|
||||
auto const own { Outwards ? Own.high - edge : edge - Own.low };
|
||||
auto const other { inside ? Other.rim - Other.low : Other.high - std::max( Other.head, Other.crown ) };
|
||||
auto const slant { std::max( std::abs( Facing ), 0.1 ) };
|
||||
auto const gap { own + other / slant };
|
||||
auto const meets = [&]( double const Width ) {
|
||||
return At + ( closest + Facing * ( edge + sign * Width ) ) / Into; };
|
||||
auto const apex { meets( 0.0 ) };
|
||||
auto const end { meets( gap ) };
|
||||
auto const reach { seam + rounded / slant };
|
||||
track_rails::rail_fill fill;
|
||||
fill.from = std::min( apex, end );
|
||||
fill.to = std::max( apex, end );
|
||||
fill.capfrom = ( end < apex );
|
||||
fill.capto = ( end > apex );
|
||||
if( Own.grooved ) {
|
||||
fill.lift = std::min( Outwards ? Own.crest : Own.liptop, inside ? Other.liptop : Other.crest ) - Own.liptop;
|
||||
}
|
||||
std::vector<glm::dvec2> line { { apex, edge + sign * reach }, { end, edge + sign * ( gap + reach ) } };
|
||||
knots::sort( line );
|
||||
std::vector<glm::dvec2> fixed { { fill.from, flat - sign * seam }, { fill.to, flat - sign * seam } };
|
||||
fill.outer = ( Outwards ? line : fixed );
|
||||
fill.inner = ( Outwards ? fixed : line );
|
||||
Fills.push_back( fill );
|
||||
}
|
||||
|
||||
track_rails::rail_tread wide_tread( track_rails::rail_crossing const &Crossing, material_handle const Material, double const Base, double const Head, double const Reach ) {
|
||||
|
||||
auto const facing { std::abs( Crossing.facing ) > 0.02 ? Crossing.facing : std::copysign( 0.02, Crossing.facing ) };
|
||||
auto const slope { Crossing.into / facing };
|
||||
track_rails::rail_tread tread { 1e9, -1e9, Material };
|
||||
if( std::abs( slope ) > square_slope ) {
|
||||
for( auto const edge : { 0.0, -Reach } ) {
|
||||
auto const distance { Crossing.at + ( edge + Crossing.facing * ( Base + 0.5 * Head ) ) / Crossing.into };
|
||||
tread.from = std::min( tread.from, distance );
|
||||
tread.to = std::max( tread.to, distance );
|
||||
}
|
||||
return tread;
|
||||
}
|
||||
for( auto const across : { Base, Base + Head } ) {
|
||||
for( auto const edge : { tread_fade, -Reach - tread_fade } ) {
|
||||
auto const distance { Crossing.at + ( edge + Crossing.facing * across ) / Crossing.into };
|
||||
tread.from = std::min( tread.from, distance - 0.01 );
|
||||
tread.to = std::max( tread.to, distance + 0.01 );
|
||||
}
|
||||
}
|
||||
auto const line = [&]( double const Edge ) {
|
||||
auto const across = [&]( double const Distance ) {
|
||||
return ( ( Distance - Crossing.at ) * Crossing.into - Edge ) / facing - Base; };
|
||||
return knots::line { { tread.from, across( tread.from ) }, { tread.to, across( tread.to ) } }; };
|
||||
tread.gauge = line( 0.0 );
|
||||
tread.edge = line( -Reach );
|
||||
tread.below = ( facing > 0.0 );
|
||||
tread.fade = tread_fade * std::sqrt( 1.0 + slope * slope );
|
||||
return tread;
|
||||
}
|
||||
|
||||
std::vector<glm::dvec2> tread_edge( TTrack const &Track, int const Path, int const Side, TTrack const &Other, int const Otherpath, int const Otherside, double const From, double const To, bool &Below, std::vector<glm::dvec2> &Gauge ) {
|
||||
|
||||
auto const &ours { rail_line( Track, Path, Side > 0, false ) };
|
||||
auto const &theirs { rail_line( Other, Otherpath, Otherside > 0, false ) };
|
||||
glm::dvec3 point, tangent;
|
||||
ours.at( From, point, tangent );
|
||||
auto otherat { nearest_distance( theirs, point ) };
|
||||
std::vector<glm::dvec2> knots;
|
||||
auto below { 0 };
|
||||
auto const count { std::max( 2, static_cast<int>( std::ceil( ( To - From ) / 0.1 ) ) + 1 ) };
|
||||
for( int i = 0; i < count; ++i ) {
|
||||
auto distance { glm::mix( From, To, static_cast<double>( i ) / ( count - 1 ) ) };
|
||||
ours.at( distance, point, tangent );
|
||||
lateral( theirs, Otherside, point, otherat );
|
||||
glm::dvec3 line;
|
||||
theirs.at( otherat, line, tangent );
|
||||
auto const normal { theirs.normal( otherat, Otherside ) };
|
||||
auto at { distance };
|
||||
auto const gauge { lateral( ours, Side, line, at ) };
|
||||
Gauge.emplace_back( at, gauge );
|
||||
at = distance;
|
||||
auto const edge { lateral( ours, Side, line - normal * rolled_band, at ) };
|
||||
below += ( gauge < edge ? 1 : -1 );
|
||||
knots.emplace_back( at, edge );
|
||||
}
|
||||
knots::sort( knots );
|
||||
knots::sort( Gauge );
|
||||
Below = ( below > 0 );
|
||||
return knots;
|
||||
}
|
||||
|
||||
std::vector<glm::dvec2> 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<glm::dvec2> 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<int>( std::ceil( 3.0 * std::abs( blunt - nose ) / step ) ) };
|
||||
std::vector<glm::dvec2> 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<track_rails::guard_rail> &Guards, track_rails::guard_rail const &Guard ) {
|
||||
|
||||
for( auto &span : Guards ) {
|
||||
if( Guard.from < span.to + 2.0 && Guard.to > span.from - 2.0 ) {
|
||||
span.from = std::min( span.from, Guard.from );
|
||||
span.to = std::max( span.to, Guard.to );
|
||||
span.plan.cuts.insert( span.plan.cuts.end(), Guard.plan.cuts.begin(), Guard.plan.cuts.end() );
|
||||
span.through = ( span.through && Guard.through );
|
||||
span.treads.insert( span.treads.end(), Guard.treads.begin(), Guard.treads.end() );
|
||||
span.castings.insert( span.castings.end(), Guard.castings.begin(), Guard.castings.end() );
|
||||
knots::sort( span.castings );
|
||||
return;
|
||||
}
|
||||
}
|
||||
Guards.push_back( Guard );
|
||||
}
|
||||
|
||||
void join_opposite_guards( std::vector<track_rails::guard_rail> ( &Guards )[ 2 ] ) {
|
||||
|
||||
knots::line spans[ 2 ];
|
||||
for( int side = 0; side < 2; ++side ) {
|
||||
for( auto const &guard : Guards[ side ] ) {
|
||||
spans[ side ].emplace_back( guard.from, guard.to );
|
||||
}
|
||||
}
|
||||
for( int side = 0; side < 2; ++side ) {
|
||||
for( auto &guard : Guards[ side ] ) {
|
||||
for( auto const &opposite : spans[ 1 - side ] ) {
|
||||
if( opposite.x < guard.to && opposite.y > guard.from ) {
|
||||
guard.from = std::min( guard.from, opposite.x );
|
||||
guard.to = std::max( guard.to, opposite.y );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool framed( track_rails::rail_crossing const &Corner, track_rails::rail_crossing const &Other, int const Side ) {
|
||||
|
||||
return Other.track == Corner.track && Other.path == Corner.path && Other.side == Side && Other.otherpath == Corner.otherpath;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class TTrack::rail_planner {
|
||||
|
||||
public:
|
||||
// constructors
|
||||
rail_planner( TTrack &Track, bool const Crossings );
|
||||
// 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<rail_crossing> 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<rail_crossing> crossings;
|
||||
if( m_crossings ) {
|
||||
crossings = m_track.rail_crossings();
|
||||
}
|
||||
m_layout.checks = ( m_crossings ? rail_checks( m_track ) : m_track.check_rails( crossings ) );
|
||||
if( m_crossings ) {
|
||||
for( auto const &crossing : crossings ) {
|
||||
add_crossing( crossing );
|
||||
}
|
||||
for( auto &pathguards : m_layout.guards ) {
|
||||
join_opposite_guards( pathguards );
|
||||
}
|
||||
for( int path = 0; path < 2; ++path ) {
|
||||
for( int side = 0; side < 2; ++side ) {
|
||||
for( auto const &guard : m_layout.guards[ path ][ side ] ) {
|
||||
auto const fills { guard_fills( guard, m_size ) };
|
||||
m_layout.fills[ path ][ side ].insert( m_layout.fills[ path ][ side ].end(), fills.begin(), fills.end() );
|
||||
}
|
||||
}
|
||||
}
|
||||
frame_middle_crossings();
|
||||
}
|
||||
if( m_track.eType == tt_Switch && cut_rails( m_track ) ) {
|
||||
for( int path = 0; path < 2; ++path ) {
|
||||
add_tread( path, -m_track.blade_side( path ), { 0.0, blade_length( m_track, 1 - path ) }, m_track, 1 - path, 0, -m_track.blade_side( path ) );
|
||||
}
|
||||
}
|
||||
if( m_track.eType == tt_Switch ) {
|
||||
switch_frog();
|
||||
}
|
||||
for( int path = 0; path < 2; ++path ) {
|
||||
for( int side = 0; side < 2; ++side ) {
|
||||
for( auto const &check : m_layout.checks[ path ][ side ] ) {
|
||||
if( false == replaced_by_wing( check, m_layout.wingends[ path ][ side ] ) ) {
|
||||
auto const fills { check_fills( check, m_size ) };
|
||||
m_layout.fills[ path ][ side ].insert( m_layout.fills[ path ][ side ].end(), fills.begin(), fills.end() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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<rail_fill> vee;
|
||||
vee_fill( vee, Crossing.at, Crossing.into, Crossing.facing, m_size, Other, true );
|
||||
vee_fill( vee, Crossing.at, Crossing.into, Crossing.facing, m_size, Other, false );
|
||||
glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) };
|
||||
m_layout.blocks[ Crossing.path ][ Crossing.side > 0 ].push_back( block );
|
||||
add_tread( Crossing.path, Crossing.side, block, *Crossing.track, Crossing.otherpath );
|
||||
if( Filled ) {
|
||||
auto &fills { m_layout.fills[ Crossing.path ][ Crossing.side > 0 ] };
|
||||
fills.insert( fills.end(), vee.begin(), vee.end() );
|
||||
}
|
||||
}
|
||||
|
||||
// crossings at a middle angle: guard rails along the sides of the quadrilateral between the tracks, glued at its corners to the guard rails of the other track
|
||||
void TTrack::rail_planner::frame_middle_crossings() {
|
||||
|
||||
std::sort( m_corners.begin(), m_corners.end(), []( rail_crossing const &A, rail_crossing const &B ) { return A.at < B.at; } );
|
||||
for( std::size_t i = 0; i < m_corners.size(); ++i ) {
|
||||
auto const &start { m_corners[ i ] };
|
||||
if( start.into <= 0.0 ) {
|
||||
continue;
|
||||
}
|
||||
for( std::size_t j = i + 1; j < m_corners.size(); ++j ) {
|
||||
auto const &end { m_corners[ j ] };
|
||||
if( false == framed( start, end, start.side ) ) {
|
||||
continue;
|
||||
}
|
||||
if( end.into < 0.0 ) {
|
||||
auto const guard { frame_guard( start, end ) };
|
||||
if( guard.to - guard.from > 0.1 ) {
|
||||
m_layout.guards[ start.path ][ start.side > 0 ].push_back( guard );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
track_rails::guard_rail TTrack::rail_planner::frame_guard( rail_crossing const &Start, rail_crossing const &End ) const {
|
||||
|
||||
auto const glued = [&]( rail_crossing const &Corner ) {
|
||||
return Corner.at + ( flangeway + std::max( m_guardoffset * Corner.facing, ( m_guardoffset + m_size.head ) * Corner.facing ) ) / Corner.into; };
|
||||
auto const gluedmiter = [&]( rail_crossing const &Corner ) {
|
||||
return glm::dvec2 { Corner.at + flangeway / Corner.into, Corner.facing / Corner.into }; };
|
||||
track_rails::guard_rail guard { glued( Start ), glued( End ) };
|
||||
guard.flarefrom = false;
|
||||
guard.flareto = false;
|
||||
guard.frommiter = gluedmiter( Start );
|
||||
guard.tomiter = gluedmiter( End );
|
||||
for( auto const *corner : { &Start, &End } ) {
|
||||
if( corner->facing < 0.0 ) {
|
||||
continue;
|
||||
}
|
||||
auto const forward { corner == &End };
|
||||
auto reach { forward ? guard.to : guard.from };
|
||||
for( auto const &opposite : m_corners ) {
|
||||
if( false == framed( Start, opposite, -Start.side ) ) {
|
||||
continue;
|
||||
}
|
||||
auto const margin { ( flangeway + m_size.head + 0.3 ) / std::abs( opposite.into ) };
|
||||
reach = forward ? std::max( reach, opposite.at + margin ) : std::min( reach, opposite.at - margin );
|
||||
}
|
||||
if( reach == ( forward ? guard.to : guard.from ) ) {
|
||||
continue;
|
||||
}
|
||||
wide( guard.plan, *corner, { -corner->track->rail_dimensions().head, flangeway, m_guardoffset, m_guardoffset + m_size.head } );
|
||||
auto &cut { guard.plan.cuts.back() };
|
||||
if( forward ) {
|
||||
cut.end = guard.to;
|
||||
cut.endmiter = guard.tomiter;
|
||||
guard.to = reach;
|
||||
guard.tomiter = {};
|
||||
guard.flareto = true;
|
||||
}
|
||||
else {
|
||||
cut.start = guard.from;
|
||||
cut.startmiter = guard.frommiter;
|
||||
guard.from = reach;
|
||||
guard.frommiter = {};
|
||||
guard.flarefrom = true;
|
||||
}
|
||||
}
|
||||
return guard;
|
||||
}
|
||||
|
||||
void TTrack::rail_planner::switch_frog() {
|
||||
|
||||
auto const frog { m_track.frog() };
|
||||
if( false == frog.valid ) {
|
||||
return;
|
||||
}
|
||||
glm::dvec3 point, tangents[ 2 ];
|
||||
glm::dvec2 normals[ 2 ];
|
||||
for( int path = 0; path < 2; ++path ) {
|
||||
auto const &line { rail_line( m_track, path, frog.side[ path ] > 0, false ) };
|
||||
line.at( frog.at[ path ], point, tangents[ path ] );
|
||||
normals[ path ] = glm::normalize( plan( line.normal( frog.at[ path ], frog.side[ path ] ) ) );
|
||||
}
|
||||
auto const facing { glm::dot( normals[ 0 ], normals[ 1 ] ) };
|
||||
for( int path = 0; path < 2; ++path ) {
|
||||
rail_crossing crossing;
|
||||
crossing.track = &m_track;
|
||||
crossing.path = path;
|
||||
crossing.side = frog.side[ path ];
|
||||
crossing.at = frog.at[ path ];
|
||||
crossing.angle = frog.angle;
|
||||
crossing.into = glm::dot( glm::normalize( plan( tangents[ path ] ) ), normals[ 1 - path ] );
|
||||
crossing.facing = facing;
|
||||
crossing.otherpath = 1 - path;
|
||||
crossing.otherside = frog.side[ 1 - path ];
|
||||
crossing.otherat = frog.at[ 1 - path ];
|
||||
if( m_size.grooved ) {
|
||||
m_layout.bands[ path ][ crossing.side > 0 ].push_back( groove_band( crossing.at, crossing.into, facing, path == 0, m_size ) );
|
||||
add_vee( crossing, m_size, path == 0 );
|
||||
continue;
|
||||
}
|
||||
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();
|
||||
}
|
||||
128
world/RailProfile.cpp
Normal file
128
world/RailProfile.cpp
Normal file
@@ -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<double>( point.y ) );
|
||||
bottom = std::min( bottom, static_cast<double>( point.y ) );
|
||||
result.low = std::min( result.low, static_cast<double>( point.x ) );
|
||||
result.high = std::max( result.high, static_cast<double>( point.x ) );
|
||||
}
|
||||
result.height = top - bottom;
|
||||
result.crest = top;
|
||||
auto const middle { top - 0.5 * result.height };
|
||||
auto webfrom { 1e9 };
|
||||
auto webto { -1e9 };
|
||||
for( std::size_t i = 0; i + 1 < count; ++i ) {
|
||||
auto const &a { Profile[ i ].position };
|
||||
auto const &b { Profile[ i + 1 ].position };
|
||||
if( a.y != b.y && ( a.y - middle ) * ( b.y - middle ) <= 0.0 ) {
|
||||
auto const x { a.x + ( b.x - a.x ) * ( middle - a.y ) / ( b.y - a.y ) };
|
||||
webfrom = std::min( webfrom, x );
|
||||
webto = std::max( webto, x );
|
||||
}
|
||||
}
|
||||
if( webfrom < webto ) {
|
||||
result.web = 0.5 * ( webfrom + webto );
|
||||
result.neck = top;
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { Profile[ i ].position };
|
||||
if( point.y > middle && ( point.x < webfrom - 0.0005 || point.x > webto + 0.0005 ) ) {
|
||||
result.neck = std::min( result.neck, static_cast<double>( point.y ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
result.head = 0.0;
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { Profile[ i ].position };
|
||||
if( point.y > top - 0.3 * result.height ) {
|
||||
result.head = std::max( result.head, static_cast<double>( point.x ) );
|
||||
}
|
||||
}
|
||||
result.tread = result.head;
|
||||
std::vector<std::size_t> heads, lips;
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { Profile[ i ].position };
|
||||
if( point.y > top - 0.015 ) {
|
||||
( point.x < -0.02 ? lips : heads ).push_back( i );
|
||||
}
|
||||
}
|
||||
if( heads.empty() || lips.empty() ) {
|
||||
return result;
|
||||
}
|
||||
auto const first { heads.back() < lips.front() ? heads.back() : lips.back() };
|
||||
auto const last { heads.back() < lips.front() ? lips.front() : heads.front() };
|
||||
if( first >= last || ( heads.back() > lips.front() && lips.back() > heads.front() ) ) {
|
||||
return result;
|
||||
}
|
||||
auto groove { 1e9 };
|
||||
for( auto i { first + 1 }; i < last; ++i ) {
|
||||
groove = std::min( groove, static_cast<double>( Profile[ i ].position.y ) );
|
||||
}
|
||||
if( groove > top ) {
|
||||
return result;
|
||||
}
|
||||
result.grooved = true;
|
||||
result.groove = groove;
|
||||
result.lip = -1e9;
|
||||
result.liptop = -1e9;
|
||||
for( auto const i : lips ) {
|
||||
result.lip = std::max( result.lip, static_cast<double>( Profile[ i ].position.x ) );
|
||||
result.liptop = std::max( result.liptop, static_cast<double>( Profile[ i ].position.y ) );
|
||||
}
|
||||
result.tread = -1e9;
|
||||
for( auto const i : heads ) {
|
||||
if( Profile[ i ].position.y > top - 0.0005 ) {
|
||||
result.tread = std::max( result.tread, static_cast<double>( Profile[ i ].position.x ) );
|
||||
}
|
||||
}
|
||||
result.ledge = 1e9;
|
||||
for( auto const i : lips ) {
|
||||
if( Profile[ i ].position.y > result.liptop - 0.0005 ) {
|
||||
result.ledge = std::min( result.ledge, static_cast<double>( Profile[ i ].position.x ) );
|
||||
}
|
||||
}
|
||||
result.rim = 1e9;
|
||||
result.crown = -1e9;
|
||||
auto const reach = [&]( double const X ) {
|
||||
result.rim = std::min( result.rim, X );
|
||||
result.crown = std::max( result.crown, X ); };
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { Profile[ i ].position };
|
||||
if( point.y > groove + 1e-4 ) {
|
||||
reach( point.x );
|
||||
}
|
||||
if( i + 1 < count ) {
|
||||
auto const &next { Profile[ i + 1 ].position };
|
||||
if( ( point.y - groove ) * ( next.y - groove ) < 0.0 ) {
|
||||
reach( point.x + ( next.x - point.x ) * ( groove - point.y ) / ( next.y - point.y ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace track_rails
|
||||
@@ -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<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents, float const Lift, std::vector<glm::dvec2> const &Inner, gfx::userdata_array *Userdata, std::vector<glm::dvec2> const &Ramp, double const Rampwidth, bool const Walls, float const Wallu, glm::dvec2 const &Bends, std::vector<glm::dvec2> const &Drops, glm::dvec2 const &Head ) const {
|
||||
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<glm::dvec2> const &Knots, double const Distance, double const Default ) {
|
||||
if( Knots.empty() )
|
||||
return Default;
|
||||
if( Distance <= Knots.front().x )
|
||||
return Knots.front().y;
|
||||
for( std::size_t i = 1; i < Knots.size(); ++i ) {
|
||||
if( Distance <= Knots[ i ].x ) {
|
||||
return glm::mix( Knots[ i - 1 ].y, Knots[ i ].y, ( Distance - Knots[ i - 1 ].x ) / std::max( Knots[ i ].x - Knots[ i - 1 ].x, 1e-6 ) );
|
||||
}
|
||||
}
|
||||
return Knots.back().y; };
|
||||
|
||||
std::vector<double> stations { From, To };
|
||||
std::vector<double> 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<double>( Side * point.position.x - Halfwidth ) };
|
||||
auto const reach { static_cast<double>( Span.side * point.position.x - Span.halfwidth ) };
|
||||
auto const t { ( reach - a.y ) / ( b.y - a.y ) };
|
||||
auto const distance { glm::mix( a.x, b.x, t ) };
|
||||
if( t > 0.0 && t < 1.0 && distance > 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<double>( Side * point.position.x - Halfwidth ) ) );
|
||||
widest = std::max( widest, std::abs( static_cast<double>( Span.side * point.position.x - Span.halfwidth ) ) );
|
||||
}
|
||||
auto const frommargin { std::abs( Span.fromend.bend ) * widest + 0.001 };
|
||||
auto const tomargin { std::abs( Span.toend.bend ) * widest + 0.001 };
|
||||
stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) {
|
||||
return ( Distance > 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<gfx::basic_vertex> &Ring, std::vector<float> &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<double>( fLength ) ), ahead, unused );
|
||||
plan_frame( std::max( Distance - delta, 0.0 ), behind, unused );
|
||||
auto tangent { glm::dvec3{ ahead.x - behind.x, 0.0, ahead.z - behind.z } };
|
||||
tangent = ( glm::length2( tangent ) > 0.0 ? glm::normalize( tangent ) : glm::dvec3{ 0.0 } );
|
||||
auto const outward { glm::dvec3{ across } * static_cast<double>( Side ) };
|
||||
auto const slope = [&]( std::vector<glm::dvec2> const &Knots, double const Default ) {
|
||||
return ( sample( Knots, Distance + delta, Default ) - sample( Knots, Distance - delta, Default ) + sample( Offsets, Distance + delta, 0.0 ) - sample( Offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); };
|
||||
outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Extents, 1e6 ) ) };
|
||||
innerwall = glm::vec3{ glm::normalize( tangent * slope( Inner, -1e6 ) - outward ) };
|
||||
auto const outward { glm::dvec3{ across } * static_cast<double>( Span.side ) };
|
||||
auto const slope = [&]( knots::line const &Knots, double const Default ) {
|
||||
return ( knots::sample( Knots, Distance + delta, Default ) - knots::sample( Knots, Distance - delta, Default ) + knots::sample( Span.offsets, Distance + delta, 0.0 ) - knots::sample( Span.offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); };
|
||||
outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Span.extents, 1e6 ) ) };
|
||||
innerwall = glm::vec3{ glm::normalize( tangent * slope( Span.inner, -1e6 ) - outward ) };
|
||||
}
|
||||
auto const worn { 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<float>( bend );
|
||||
}
|
||||
}
|
||||
Ring.clear();
|
||||
Wear.clear();
|
||||
auto const count { ShapePoints.size() };
|
||||
auto const reach = [&]( std::size_t const Index ) {
|
||||
return static_cast<double>( Side * ShapePoints[ Index ].position.x - Halfwidth ); };
|
||||
return static_cast<double>( Span.side * ShapePoints[ Index ].position.x - Span.halfwidth ); };
|
||||
auto const twin = [&]( std::size_t const A, std::size_t const B ) {
|
||||
return ShapePoints[ A ].position.x == ShapePoints[ B ].position.x && ShapePoints[ A ].position.y == ShapePoints[ B ].position.y; };
|
||||
auto const clamped = [&]( std::size_t const Index ) {
|
||||
@@ -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<float>( ( 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<float>( ( limit - reach( neighbour ) ) / ( side > 0 ? std::max( outside - reach( neighbour ), 1e-6 ) : std::min( outside - reach( neighbour ), -1e-6 ) ) ), 0.f, 1.f ) };
|
||||
shape.position.y = glm::mix( other.position.y, shape.position.y, t );
|
||||
shape.texture.x = glm::mix( other.texture.x, shape.texture.x, t );
|
||||
auto const blend { glm::mix( other.normal, shape.normal, t ) };
|
||||
@@ -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<float>( 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<float>( drop * ( shape.position.y - Span.head.x ) / ( Span.head.y - Span.head.x ) );
|
||||
}
|
||||
auto pt { parallel * static_cast<float>( Side * ( Halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) };
|
||||
pt.y += shape.position.y + Lift;
|
||||
auto pt { parallel * static_cast<float>( Span.side * ( Span.halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) };
|
||||
pt.y += shape.position.y + Span.lift;
|
||||
auto norm { shape.normal.x * parallel };
|
||||
norm.y += shape.normal.y;
|
||||
if( glm::length2( wall ) > 0.f ) {
|
||||
norm = wall;
|
||||
if( 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<float>( 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<float>( std::clamp( ( Span.rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Span.rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f );
|
||||
}
|
||||
};
|
||||
|
||||
auto const texturelength { static_cast<float>( fTextureLength ) };
|
||||
auto const texturelength { static_cast<float>( Span.texturelength ) };
|
||||
std::vector<gfx::basic_vertex> ring1, ring2;
|
||||
std::vector<float> wear1, wear2;
|
||||
float tv1 { clamp_circular( 1.f - static_cast<float>( std::fmod( From, fTextureLength ) / fTextureLength ), 1.0f ) };
|
||||
float tv1 { clamp_circular( 1.f - static_cast<float>( std::fmod( Span.from, Span.texturelength ) / Span.texturelength ), 1.0f ) };
|
||||
for( std::size_t i = 1; i < stations.size(); ++i ) {
|
||||
auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) };
|
||||
ring( stations[ i - 1 ], middle, ring1, wear1 );
|
||||
@@ -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;
|
||||
|
||||
@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include "world/Knots.h"
|
||||
|
||||
struct map_colored_paths {
|
||||
std::vector<gfx::geometrybank_handle> switches;
|
||||
@@ -23,6 +24,31 @@ struct map_colored_paths {
|
||||
std::vector<gfx::geometrybank_handle> highlighted;
|
||||
};
|
||||
|
||||
struct loft_end {
|
||||
double bend { 0.0 };
|
||||
};
|
||||
|
||||
struct loft_span {
|
||||
int side { 1 };
|
||||
float halfwidth { 0.f };
|
||||
double texturelength { 1.0 };
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
knots::line offsets;
|
||||
knots::line extents;
|
||||
knots::line inner;
|
||||
float lift { 0.f };
|
||||
gfx::userdata_array *userdata { nullptr };
|
||||
knots::line ramp;
|
||||
double rampwidth { 0.0 };
|
||||
bool walls { false };
|
||||
float wallu { -1.f };
|
||||
loft_end fromend;
|
||||
loft_end toend;
|
||||
knots::line drops;
|
||||
glm::dvec2 head { 0.0 };
|
||||
};
|
||||
|
||||
struct segment_data {
|
||||
// types
|
||||
enum point {
|
||||
@@ -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<float, float> fOffsetX = {0.f, 0.f}, glm::vec3 **p = nullptr, bool bRender = true );
|
||||
// the profile lofted from distance From to To, moved outwards by Offsets and cut on its outer side to Extents beyond the gauge line, both as (distance, value) knots
|
||||
bool
|
||||
RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector<glm::dvec2> const &Offsets, std::vector<glm::dvec2> const &Extents = {}, float const Lift = 0.f, std::vector<glm::dvec2> const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector<glm::dvec2> const &Ramp = {}, double const Rampwidth = 0.0, bool const Walls = false, float const Wallu = -1.f, glm::dvec2 const &Bends = glm::dvec2 { 0.0 }, std::vector<glm::dvec2> 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;
|
||||
|
||||
124
world/Track.cpp
124
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<std::pair<glm::dvec3, glm::dvec3>> 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<double>( point.y ) );
|
||||
bottom = std::min( bottom, static_cast<double>( point.y ) );
|
||||
result.low = std::min( result.low, static_cast<double>( point.x ) );
|
||||
result.high = std::max( result.high, static_cast<double>( point.x ) );
|
||||
}
|
||||
result.height = top - bottom;
|
||||
result.crest = top;
|
||||
auto const middle { top - 0.5 * result.height };
|
||||
auto webfrom { 1e9 };
|
||||
auto webto { -1e9 };
|
||||
for( std::size_t i = 0; i + 1 < count; ++i ) {
|
||||
auto const &a { profile[ i ].position };
|
||||
auto const &b { profile[ i + 1 ].position };
|
||||
if( a.y != b.y && ( a.y - middle ) * ( b.y - middle ) <= 0.0 ) {
|
||||
auto const x { a.x + ( b.x - a.x ) * ( middle - a.y ) / ( b.y - a.y ) };
|
||||
webfrom = std::min( webfrom, x );
|
||||
webto = std::max( webto, x );
|
||||
}
|
||||
}
|
||||
if( webfrom < webto ) {
|
||||
result.web = 0.5 * ( webfrom + webto );
|
||||
result.neck = top;
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { profile[ i ].position };
|
||||
if( point.y > middle && ( point.x < webfrom - 0.0005 || point.x > webto + 0.0005 ) ) {
|
||||
result.neck = std::min( result.neck, static_cast<double>( point.y ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
result.head = 0.0;
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { profile[ i ].position };
|
||||
if( point.y > top - 0.3 * result.height ) {
|
||||
result.head = std::max( result.head, static_cast<double>( point.x ) );
|
||||
}
|
||||
}
|
||||
result.tread = result.head;
|
||||
std::vector<std::size_t> heads, lips;
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { profile[ i ].position };
|
||||
if( point.y > top - 0.015 ) {
|
||||
( point.x < -0.02 ? lips : heads ).push_back( i );
|
||||
}
|
||||
}
|
||||
if( heads.empty() || lips.empty() ) {
|
||||
return result;
|
||||
}
|
||||
auto const first { heads.back() < lips.front() ? heads.back() : lips.back() };
|
||||
auto const last { heads.back() < lips.front() ? lips.front() : heads.front() };
|
||||
if( first >= last || ( heads.back() > lips.front() && lips.back() > heads.front() ) ) {
|
||||
return result;
|
||||
}
|
||||
auto groove { 1e9 };
|
||||
for( auto i { first + 1 }; i < last; ++i ) {
|
||||
groove = std::min( groove, static_cast<double>( profile[ i ].position.y ) );
|
||||
}
|
||||
if( groove > top ) {
|
||||
return result;
|
||||
}
|
||||
result.grooved = true;
|
||||
result.groove = groove;
|
||||
result.lip = -1e9;
|
||||
result.liptop = -1e9;
|
||||
for( auto const i : lips ) {
|
||||
result.lip = std::max( result.lip, static_cast<double>( profile[ i ].position.x ) );
|
||||
result.liptop = std::max( result.liptop, static_cast<double>( profile[ i ].position.y ) );
|
||||
}
|
||||
result.tread = -1e9;
|
||||
for( auto const i : heads ) {
|
||||
if( profile[ i ].position.y > top - 0.0005 ) {
|
||||
result.tread = std::max( result.tread, static_cast<double>( profile[ i ].position.x ) );
|
||||
}
|
||||
}
|
||||
result.ledge = 1e9;
|
||||
for( auto const i : lips ) {
|
||||
if( profile[ i ].position.y > result.liptop - 0.0005 ) {
|
||||
result.ledge = std::min( result.ledge, static_cast<double>( profile[ i ].position.x ) );
|
||||
}
|
||||
}
|
||||
result.rim = 1e9;
|
||||
result.crown = -1e9;
|
||||
auto const reach = [&]( double const X ) {
|
||||
result.rim = std::min( result.rim, X );
|
||||
result.crown = std::max( result.crown, X ); };
|
||||
for( std::size_t i = 0; i < count; ++i ) {
|
||||
auto const &point { profile[ i ].position };
|
||||
if( point.y > groove + 1e-4 ) {
|
||||
reach( point.x );
|
||||
}
|
||||
if( i + 1 < count ) {
|
||||
auto const &next { profile[ i + 1 ].position };
|
||||
if( ( point.y - groove ) * ( next.y - groove ) < 0.0 ) {
|
||||
reach( point.x + ( next.x - point.x ) * ( groove - point.y ) / ( next.y - point.y ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return track_rails::measure_rail( track_rail_profile( m_profile1.second ) );
|
||||
}
|
||||
|
||||
float
|
||||
|
||||
151
world/Track.h
151
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<glm::dvec2> gauge;
|
||||
std::vector<glm::dvec2> extents;
|
||||
std::vector<glm::dvec2> drops;
|
||||
std::vector<glm::dvec2> bends;
|
||||
};
|
||||
switch_blade const &blade( int const Path ) const;
|
||||
// where a rail of this track crosses a rail of another one, found from their gauge lines
|
||||
struct rail_crossing {
|
||||
TTrack *track { nullptr };
|
||||
int path { 0 };
|
||||
int side { 0 }; // rail profile of the crossing rail, 1 right, -1 left
|
||||
double at { 0.0 }; // distance of the crossing along the path, past the ends where the track goes on
|
||||
double angle { 0.0 };
|
||||
double into { 0.0 }; // the path direction against the gauge normal of the other rail, above zero: going into the other track
|
||||
double facing { 0.0 }; // the gauge normals of both rails against each other, below zero: an acute frog
|
||||
bool nose { false }; // at an acute frog the rail goes on as the nose rail, the other one as the rail beside it
|
||||
int otherpath { 0 };
|
||||
int otherside { 0 }; // rail profile of the other rail, 1 right, -1 left
|
||||
double otherat { 0.0 }; // distance of the crossing along the other path
|
||||
bool aligned { true }; // both paths run the same way at the crossing
|
||||
TTrack *owner { nullptr };
|
||||
};
|
||||
std::vector<rail_crossing> rail_crossings( bool const Joined = true );
|
||||
// the size of the rail profile, out from the gauge face: positive outwards, the side of the head away from the track
|
||||
struct rail_size {
|
||||
double head { 0.0735 }; // how far the head reaches out
|
||||
double height { 0.172 };
|
||||
double low { -0.0385 }; // the reach of the profile inwards
|
||||
double high { 0.1115 }; // the reach of the profile outwards
|
||||
bool grooved { false };
|
||||
double groove { 0.0 };
|
||||
double lip { 0.0 };
|
||||
double rim { 0.0 };
|
||||
double crown { 0.0 };
|
||||
double liptop { 0.0 };
|
||||
double crest { 0.0 };
|
||||
double tread { 0.0735 };
|
||||
double ledge { 0.0 };
|
||||
double web { 0.0365 };
|
||||
double neck { -0.0467 };
|
||||
};
|
||||
rail_size rail_dimensions() const;
|
||||
// a check rail beside a rail, how far its ends turn away from the running rail
|
||||
struct check_rail {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
double flarefrom { 0.0 };
|
||||
double flareto { 0.0 };
|
||||
double frombend { 0.0 };
|
||||
double tobend { 0.0 };
|
||||
};
|
||||
using check_rail_set = std::array<std::array<std::vector<check_rail>, 2>, 2>; // path, right side
|
||||
track_rails::switch_frog frog() const;
|
||||
track_rails::switch_blade const &blade( int const Path ) const;
|
||||
std::vector<track_rails::rail_crossing> rail_crossings( bool const Joined = true );
|
||||
track_rails::rail_size rail_dimensions() const;
|
||||
// the check rails beside the rails of each path, kept clear of the blades and the rails of other paths running inside them
|
||||
check_rail_set check_rails( std::vector<rail_crossing> const &Crossings );
|
||||
// a rail broken where another one crosses it: the part coming from below ends at end, the part going on starts at start
|
||||
struct rail_cut {
|
||||
double at { 0.0 };
|
||||
double end { 0.0 };
|
||||
double start { 0.0 };
|
||||
std::vector<glm::dvec2> endoffsets, endextents, startoffsets, startextents;
|
||||
std::vector<glm::dvec2> endrolled, startrolled; // how far out from the gauge line the wheels roll on the head, of the part coming from below and of the part going on
|
||||
glm::dvec2 endmiter { 0.0 };
|
||||
glm::dvec2 startmiter { 0.0 };
|
||||
double endbend { 0.0 };
|
||||
double startbend { 0.0 };
|
||||
std::vector<glm::dvec2> endinner, startinner;
|
||||
bool endcap { true };
|
||||
bool startcap { true };
|
||||
};
|
||||
struct rail_plan {
|
||||
std::vector<rail_cut> cuts;
|
||||
};
|
||||
// guard rails of a crossing at a wide angle: the rail profile laid inside the running rail across the flangeway, broken like the rails
|
||||
struct guard_rail {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
rail_plan plan;
|
||||
bool flarefrom { true };
|
||||
bool flareto { true };
|
||||
glm::dvec2 frommiter { 0.0 };
|
||||
glm::dvec2 tomiter { 0.0 };
|
||||
bool through { true };
|
||||
};
|
||||
struct rail_band {
|
||||
double at { 0.0 };
|
||||
double into { 0.0 };
|
||||
double facing { 0.0 };
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
bool whole { true };
|
||||
};
|
||||
struct rail_fill {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
std::vector<glm::dvec2> inner, outer;
|
||||
bool capfrom { false };
|
||||
bool capto { false };
|
||||
double lift { 0.0 };
|
||||
bool web { false };
|
||||
};
|
||||
struct rail_tread {
|
||||
double from { 0.0 };
|
||||
double to { 0.0 };
|
||||
material_handle material { 0 };
|
||||
int anchor { 0 };
|
||||
std::vector<glm::dvec2> edge;
|
||||
std::vector<glm::dvec2> gauge;
|
||||
bool below { false };
|
||||
};
|
||||
struct rail_layout {
|
||||
rail_plan plans[ 2 ][ 2 ]; // path, right side
|
||||
std::vector<rail_band> bands[ 2 ][ 2 ];
|
||||
std::vector<rail_fill> fills[ 2 ][ 2 ];
|
||||
std::vector<glm::dvec2> blocks[ 2 ][ 2 ];
|
||||
std::vector<rail_tread> treads[ 2 ][ 2 ];
|
||||
std::vector<guard_rail> guards[ 2 ][ 2 ];
|
||||
std::vector<glm::dvec2> wingends[ 2 ][ 2 ]; // ends of the wing rails of other rails running along each rail, and which way they run there
|
||||
check_rail_set checks;
|
||||
std::vector<TTrack *> crossed; // tracks whose rails cross the rails of this one
|
||||
};
|
||||
rail_layout plan_rails( bool const Crossings );
|
||||
track_rails::check_rail_set check_rails( std::vector<track_rails::rail_crossing> 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;
|
||||
|
||||
2446
world/TrackRails.cpp
2446
world/TrackRails.cpp
File diff suppressed because it is too large
Load Diff
@@ -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<check_rail> const &Checks, double const Wingend, double const Direction, double &Far );
|
||||
bool replaced_by_wing( check_rail const &Check, knots::line const &Wingends );
|
||||
gauge_line const &rail_line( TTrack const &Track, int const Path, int const Side, bool const Blades );
|
||||
check_rail_set const &rail_checks( TTrack &Track );
|
||||
glm::dvec3 rail_point( TTrack const &Track, int const Path, int const Side, double const Distance, double const Lateral );
|
||||
void rail_offset( TTrack const &Track, int const Path, int const Side, glm::dvec3 const &Point, double &Distance, double &Lateral );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user