mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-04 03:02:05 +01:00
feat(editor): a switch path can be a chain of pieces: the attributes pieces1 and pieces2 keep the parts of each path after the classic bezier, which stays as the approximation older versions read and log the attributes as unknown; the simulation drives, draws and maps a path along its pieces by distance, pieces that don't join the ends of the path are reported and the bezier used; the catalogue Rz switches get the diverging path as the arc of their radius and the straight after it, moving, rotating and mirroring a switch carry the pieces, reshaping a path by hand drops them; self-test commands switchpieces and switchbreak
This commit is contained in:
@@ -33,6 +33,16 @@ segment_data::deserialize( cParser &Input, glm::dvec3 const &Offset ) {
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
segment_data::translate( glm::dvec3 const &Offset ) {
|
||||
|
||||
points[ segment_data::point::start ] += Offset;
|
||||
points[ segment_data::point::end ] += Offset;
|
||||
for( auto &piece : pieces ) {
|
||||
piece.translate( Offset );
|
||||
}
|
||||
}
|
||||
|
||||
TSegment::TSegment(TTrack *owner) :
|
||||
pOwner( owner )
|
||||
{}
|
||||
@@ -62,6 +72,8 @@ bool TSegment::Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep,
|
||||
|
||||
bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1, double fNewRoll2, bool bIsCurve)
|
||||
{ // wersja uniwersalna (dla krzywej i prostego)
|
||||
m_pieces.clear();
|
||||
m_starts.clear();
|
||||
Point1 = NewPoint1;
|
||||
CPointOut = NewCPointOut;
|
||||
CPointIn = NewCPointIn;
|
||||
@@ -133,6 +145,45 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSegment::InitChain(std::vector<std::shared_ptr<TSegment>> Pieces)
|
||||
{
|
||||
if( Pieces.empty() )
|
||||
return false;
|
||||
m_pieces = std::move( Pieces );
|
||||
m_starts.clear();
|
||||
fLength = 0.0;
|
||||
iSegCount = 0;
|
||||
for( auto const &piece : m_pieces ) {
|
||||
m_starts.push_back( fLength );
|
||||
fLength += piece->fLength;
|
||||
iSegCount += piece->iSegCount;
|
||||
}
|
||||
auto const &first { *m_pieces.front() };
|
||||
auto const &last { *m_pieces.back() };
|
||||
Point1 = first.Point1;
|
||||
CPointOut = first.CPointOut;
|
||||
CPointIn = last.CPointIn;
|
||||
Point2 = last.Point2;
|
||||
fRoll1 = first.fRoll1;
|
||||
fRoll2 = last.fRoll2;
|
||||
bCurve = true;
|
||||
fDirection = -std::atan2(Point2.x - Point1.x, Point2.z - Point1.z);
|
||||
fStoop = std::atan2(Point2.y - Point1.y, fLength);
|
||||
fStep = fLength / iSegCount;
|
||||
fTsBuffer.resize( iSegCount + 1 );
|
||||
for( int i = 0; i <= iSegCount; ++i ) {
|
||||
fTsBuffer[ i ] = static_cast<double>( i ) / iSegCount;
|
||||
}
|
||||
fTsSlope.assign( iSegCount + 1, fStep / fLength );
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t TSegment::piece_at(double const s) const
|
||||
{
|
||||
auto const next { std::upper_bound( std::begin( m_starts ), std::end( m_starts ), s ) };
|
||||
return next == std::begin( m_starts ) ? 0 : static_cast<std::size_t>( std::distance( std::begin( m_starts ), next ) - 1 );
|
||||
}
|
||||
|
||||
void TSegment::InitTFromSInterpolateData()
|
||||
{
|
||||
// pre-compute the curve parameter t at equal distance intervals
|
||||
@@ -235,6 +286,8 @@ double TSegment::FastGetTFromS(double const s) const
|
||||
// NOTE: for s outside <0, fLength> the result is extrapolated
|
||||
// outside <0, 1>, which is the expected behavior (see e.g. usage
|
||||
// in GetDirection())
|
||||
if( false == m_pieces.empty() )
|
||||
return s / fLength;
|
||||
int const i = std::clamp(static_cast<int>(s / fStep), 0, iSegCount - 1); // interval index
|
||||
double const u = (s - i * fStep) / fStep; // position within interval (possibly outside)
|
||||
double const u2 = u * u;
|
||||
@@ -263,6 +316,8 @@ double TSegment::ComputeLength() const // McZapkie-150503: dlugosc miedzy punkta
|
||||
// Ra: koniec rekurencji jeśli po podziale suma długości nie różni się więcej niż 0.5mm od
|
||||
// poprzedniej
|
||||
// Ra: ewentualnie rozpoznać łuk okręgu płaskiego i liczyć ze wzoru na długość łuku
|
||||
if( false == m_pieces.empty() )
|
||||
return fLength;
|
||||
double t;
|
||||
double l = 0;
|
||||
glm::dvec3 last{0, 0, 0}; // długość liczona po przesunięciu odcinka do początku układu
|
||||
@@ -366,7 +421,11 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) const
|
||||
*/
|
||||
|
||||
void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const {
|
||||
if (bCurve) {
|
||||
if( false == m_pieces.empty() ) {
|
||||
auto const piece { piece_at( fDistance ) };
|
||||
m_pieces[ piece ]->RaPositionGet( fDistance - m_starts[ piece ], position, rotation );
|
||||
}
|
||||
else if (bCurve) {
|
||||
// można by wprowadzić uproszczony wzór dla okręgów płaskich
|
||||
auto const t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t)
|
||||
position = FastGetPoint( t );
|
||||
@@ -393,6 +452,12 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::
|
||||
glm::dvec3 TSegment::FastGetPoint(double const t) const
|
||||
{
|
||||
// return (bCurve?std::lerp(t,Point1,CPointOut,CPointIn,Point2):((1.0-t)*Point1+(t)*Point2));
|
||||
if( false == m_pieces.empty() ) {
|
||||
auto const distance { t * fLength };
|
||||
auto const index { piece_at( distance ) };
|
||||
auto const &piece { *m_pieces[ index ] };
|
||||
return piece.FastGetPoint( piece.FastGetTFromS( distance - m_starts[ index ] ) );
|
||||
}
|
||||
return true == bCurve || iSegCount != 1 ? RaInterpolate(t) : glm::mix(Point1, Point2, t);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "utilities/Classes.h"
|
||||
#include "rendering/geometrybank.h"
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
struct map_colored_paths {
|
||||
std::vector<gfx::geometrybank_handle> switches;
|
||||
@@ -32,10 +34,21 @@ struct segment_data {
|
||||
std::array<glm::dvec3, 4> points {};
|
||||
std::array<float, 2> rolls {};
|
||||
float radius {};
|
||||
std::vector<segment_data> pieces; // consecutive parts of the path, which then is their approximation for older versions
|
||||
// constructors
|
||||
segment_data() = default;
|
||||
// methods
|
||||
void deserialize( cParser &Input, glm::dvec3 const &Offset );
|
||||
void translate( glm::dvec3 const &Offset );
|
||||
// maps the ends of the path and of its pieces with Point and their control vectors with Vector
|
||||
template <typename Pointmap, typename Vectormap>
|
||||
void transform( Pointmap const &Point, Vectormap const &Vector ) {
|
||||
for( auto const index : { point::start, point::end } ) {
|
||||
points[ index ] = Point( points[ index ] ); }
|
||||
for( auto const index : { point::control1, point::control2 } ) {
|
||||
points[ index ] = Vector( points[ index ] ); }
|
||||
for( auto &piece : pieces ) {
|
||||
piece.transform( Point, Vector ); } }
|
||||
};
|
||||
|
||||
class TSegment
|
||||
@@ -58,6 +71,8 @@ class TSegment
|
||||
glm::dvec3 vB;
|
||||
glm::dvec3 vC; // współczynniki wielomianów trzeciego stopnia vD==Point1
|
||||
TTrack *pOwner = nullptr; // wskaźnik na właściciela
|
||||
std::vector<std::shared_ptr<TSegment>> m_pieces; // parts of a path made of several curves, parametrized by distance
|
||||
std::vector<double> m_starts; // distance from Point1 to the start of each part
|
||||
|
||||
glm::dvec3 GetFirstDerivative(double const fTime) const;
|
||||
double RombergIntegral(double const fA, double const fB) const;
|
||||
@@ -66,6 +81,7 @@ class TSegment
|
||||
double FastGetTFromS(double const s) const;
|
||||
glm::dvec3 RaInterpolate(double const t) const;
|
||||
glm::dvec3 RaInterpolate0(double const t) const;
|
||||
std::size_t piece_at(double const s) const;
|
||||
|
||||
public:
|
||||
bool bCurve = false;
|
||||
@@ -73,6 +89,11 @@ public:
|
||||
explicit TSegment(TTrack *owner);
|
||||
bool Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0);
|
||||
bool Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0, bool bIsCurve = true);
|
||||
bool InitChain(std::vector<std::shared_ptr<TSegment>> Pieces);
|
||||
inline
|
||||
std::size_t
|
||||
PieceCount() const {
|
||||
return m_pieces.size(); };
|
||||
double
|
||||
ComputeLength() const; // McZapkie-150503
|
||||
// finds point on segment closest to specified point in 3d space. returns: point on segment as value in range 0-1
|
||||
@@ -118,6 +139,9 @@ public:
|
||||
inline
|
||||
float
|
||||
GetRoll(double const Distance) const {
|
||||
if( false == m_pieces.empty() ) {
|
||||
auto const piece { piece_at( Distance ) };
|
||||
return m_pieces[ piece ]->GetRoll( Distance - m_starts[ piece ] ); }
|
||||
return std::lerp( fRoll1, fRoll2, static_cast<float>(Distance / fLength) ); }
|
||||
inline
|
||||
void
|
||||
|
||||
106
world/Track.cpp
106
world/Track.cpp
@@ -45,6 +45,40 @@ const int iProsto3[4] = {1, -1, 2, 1}; // segmenty do jazdy prosto
|
||||
const int iEnds3[13] = {3, 0, 2, 1, 2, 0, -1, 1, 0, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1"
|
||||
TIsolated *TIsolated::pRoot = nullptr;
|
||||
|
||||
namespace {
|
||||
|
||||
// a part of a switch path, shaped like the path itself: heights on the rail heads, straights made of their own points
|
||||
void init_switch_piece( TSegment &Segment, segment_data const &Piece, bool const Rail ) {
|
||||
|
||||
auto p1 { Piece.points[ segment_data::point::start ] };
|
||||
auto cp1 { Piece.points[ segment_data::point::control1 ] };
|
||||
auto cp2 { Piece.points[ segment_data::point::control2 ] };
|
||||
auto p2 { Piece.points[ segment_data::point::end ] };
|
||||
if( Rail ) {
|
||||
p1.y += 0.18;
|
||||
p2.y += 0.18;
|
||||
}
|
||||
if( glm::length2( ( p1 + p1 + p2 ) / 3.0 - p1 - cp1 ) < sq( 0.02 )
|
||||
|| glm::length2( ( p1 + p2 + p2 ) / 3.0 - p2 - cp2 ) < sq( 0.02 ) ) {
|
||||
cp1 = cp2 = glm::dvec3( 0, 0, 0 );
|
||||
}
|
||||
auto const straight { cp1 == glm::dvec3() && cp2 == glm::dvec3() };
|
||||
auto const segsize { (
|
||||
Piece.radius != 0.f ?
|
||||
std::clamp( std::abs( Piece.radius ) * ( 0.02 / Global.SplineFidelity ), 2.0 / Global.SplineFidelity, 10.0 / Global.SplineFidelity ) :
|
||||
straight ?
|
||||
10.0 :
|
||||
std::clamp( glm::length( p1 - p2 ) * 0.1, 2.0 / Global.SplineFidelity, 10.0 / Global.SplineFidelity ) ) };
|
||||
if( straight ) {
|
||||
Segment.Init( p1, p2, segsize, Piece.rolls[ 0 ], Piece.rolls[ 1 ] );
|
||||
}
|
||||
else {
|
||||
Segment.Init( p1, p1 + cp1, p2 + cp2, p2, segsize, Piece.rolls[ 0 ], Piece.rolls[ 1 ] );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TTrack::profiles_array TTrack::m_profiles;
|
||||
TTrack::profiles_map TTrack::m_profilesmap;
|
||||
|
||||
@@ -518,8 +552,6 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
init_segments( true );
|
||||
|
||||
// optional attributes
|
||||
parser->getTokens();
|
||||
*parser >> token;
|
||||
@@ -668,12 +700,30 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
|
||||
// called after segment initialisation so the path geometry is final.
|
||||
}
|
||||
}
|
||||
else if( str == "pieces1" || str == "pieces2" ) {
|
||||
// consecutive parts of a path, kept after the path itself for versions which know only the path
|
||||
auto const index { static_cast<std::size_t>( str.back() - '1' ) };
|
||||
auto const count { parser->getToken<int>() };
|
||||
std::vector<segment_data> pieces;
|
||||
for( int piece = 0; piece < count; ++piece ) {
|
||||
pieces.emplace_back();
|
||||
pieces.back().deserialize( *parser, pOrigin );
|
||||
}
|
||||
if( index < m_paths.size() ) {
|
||||
m_paths[ index ].pieces = std::move( pieces );
|
||||
}
|
||||
else {
|
||||
ErrorLog( "Bad track: \"" + str + "\" defined for track \"" + m_name + "\" which has no such path" );
|
||||
}
|
||||
}
|
||||
else
|
||||
ErrorLog("Bad track: unknown property: \"" + str + "\" defined for track \"" + m_name + "\"");
|
||||
parser->getTokens();
|
||||
*parser >> token;
|
||||
str = token;
|
||||
}
|
||||
|
||||
init_segments( true );
|
||||
// alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru
|
||||
if ((i = m_name.find("@")) != std::string::npos && i < m_name.length())
|
||||
{
|
||||
@@ -958,6 +1008,35 @@ void TTrack::init_segments( bool const Initial ) {
|
||||
}
|
||||
}
|
||||
|
||||
if( eType == tt_Switch ) {
|
||||
for( std::size_t index = 0; index < 2; ++index ) {
|
||||
auto const &pieces { m_paths[ index ].pieces };
|
||||
if( pieces.empty() ) {
|
||||
continue;
|
||||
}
|
||||
auto joined {
|
||||
glm::distance( pieces.front().points[ segment_data::point::start ], m_paths[ index ].points[ segment_data::point::start ] ) < 0.02
|
||||
&& glm::distance( pieces.back().points[ segment_data::point::end ], m_paths[ index ].points[ segment_data::point::end ] ) < 0.02 };
|
||||
for( std::size_t piece = 1; piece < pieces.size(); ++piece ) {
|
||||
joined = joined && glm::distance( pieces[ piece - 1 ].points[ segment_data::point::end ], pieces[ piece ].points[ segment_data::point::start ] ) < 0.02;
|
||||
}
|
||||
if( false == joined ) {
|
||||
ErrorLog( "Bad track: pieces of path " + std::to_string( index + 1 ) + " of switch \"" + m_name + "\" don't join its ends, the path is used instead" );
|
||||
continue;
|
||||
}
|
||||
auto const texheight { fTexHeight1 };
|
||||
auto const texheightoffset { fTexHeightOffset };
|
||||
std::vector<std::shared_ptr<TSegment>> chain;
|
||||
for( auto const &piece : pieces ) {
|
||||
chain.emplace_back( std::make_shared<TSegment>( this ) );
|
||||
init_switch_piece( *chain.back(), piece, ( iCategoryFlag & 1 ) != 0 );
|
||||
}
|
||||
fTexHeight1 = texheight;
|
||||
fTexHeightOffset = texheightoffset;
|
||||
SwitchExtension->Segments[ index ]->InitChain( std::move( chain ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( Initial ) {
|
||||
Switch(0);
|
||||
} // na stałe w położeniu 0 - nie ma początkowego stanu zwrotnicy we wpisie
|
||||
@@ -2432,6 +2511,29 @@ TTrack::export_as_text_( std::ostream &Output ) const {
|
||||
if( fVerticalRadius != 0.f ) {
|
||||
Output << "vradius " << fVerticalRadius << ' ';
|
||||
}
|
||||
for( std::size_t index = 0; index < m_paths.size(); ++index ) {
|
||||
auto const &pieces { m_paths[ index ].pieces };
|
||||
if( pieces.empty() ) {
|
||||
continue;
|
||||
}
|
||||
auto const piecesprecision { Output.precision( std::numeric_limits<double>::digits10 ) };
|
||||
Output << "pieces" << index + 1 << ' ' << pieces.size() << ' ';
|
||||
for( auto const &piece : pieces ) {
|
||||
for( auto const point : { segment_data::point::start, segment_data::point::control1, segment_data::point::control2, segment_data::point::end } ) {
|
||||
Output
|
||||
<< piece.points[ point ].x << ' '
|
||||
<< piece.points[ point ].y << ' '
|
||||
<< piece.points[ point ].z << ' ';
|
||||
if( point == segment_data::point::start ) {
|
||||
Output << piece.rolls[ 0 ] << ' ';
|
||||
}
|
||||
}
|
||||
Output
|
||||
<< piece.rolls[ 1 ] << ' '
|
||||
<< piece.radius << ' ';
|
||||
}
|
||||
Output.precision( piecesprecision );
|
||||
}
|
||||
if( eType == tt_Switch
|
||||
&& SwitchExtension->m_material3 != null_handle) {
|
||||
auto texturefile { GfxRenderer->Material( SwitchExtension->m_material3 )->GetName() };
|
||||
|
||||
Reference in New Issue
Block a user