mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-09-01 09:19:19 +02:00
TSegment::GetTFromS() is a significant hotspot in the non-rendering part of the state_manager update (about 13%). This change introduces a FastGetTFromS() version that interpolates from precomputed values instead of doing the whole calculation on every call. Computing position from track distance is now done in constant time. This reduces the contribution to about 0.3%.
614 lines
26 KiB
C++
614 lines
26 KiB
C++
/*
|
|
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/Segment.h"
|
|
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "utilities/parser.h"
|
|
#include "utilities/utilities.h"
|
|
#include "world/Track.h"
|
|
#include "rendering/renderer.h"
|
|
|
|
void
|
|
segment_data::deserialize( cParser &Input, glm::dvec3 const &Offset ) {
|
|
|
|
points[ segment_data::point::start ] = LoadPoint( Input ) + Offset;
|
|
Input.getTokens();
|
|
Input >> rolls[ 0 ];
|
|
points[ segment_data::point::control1 ] = LoadPoint( Input );
|
|
points[ segment_data::point::control2 ] = LoadPoint( Input );
|
|
points[ segment_data::point::end ] = LoadPoint( Input ) + Offset;
|
|
Input.getTokens( 2 );
|
|
Input
|
|
>> rolls[ 1 ]
|
|
>> radius;
|
|
}
|
|
|
|
|
|
TSegment::TSegment(TTrack *owner) :
|
|
pOwner( owner )
|
|
{}
|
|
|
|
bool TSegment::Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep, double fNewRoll1, double fNewRoll2)
|
|
{ // wersja dla prostego - wyliczanie punktów kontrolnych
|
|
glm::dvec3 dir;
|
|
|
|
// NOTE: we're enforcing division also for straight track, to ensure dense enough mesh for per-vertex lighting
|
|
/*
|
|
if (fNewRoll1 == fNewRoll2)
|
|
{ // faktyczny prosty
|
|
dir = Normalize(NewPoint2 - NewPoint1); // wektor kierunku o długości 1
|
|
return TSegment::Init(NewPoint1, dir, -dir, NewPoint2, fNewStep, fNewRoll1, fNewRoll2,
|
|
false);
|
|
}
|
|
else
|
|
*/
|
|
{ // prosty ze zmienną przechyłką musi być segmentowany jak krzywe
|
|
dir = (NewPoint2 - NewPoint1) / 3.0; // punkty kontrolne prostego są w 1/3 długości
|
|
return TSegment::Init(
|
|
NewPoint1, NewPoint1 + dir,
|
|
NewPoint2 - dir, NewPoint2,
|
|
fNewStep, fNewRoll1, fNewRoll2, true);
|
|
}
|
|
};
|
|
|
|
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)
|
|
Point1 = NewPoint1;
|
|
CPointOut = NewCPointOut;
|
|
CPointIn = NewCPointIn;
|
|
Point2 = NewPoint2;
|
|
// poprawienie przechyłki
|
|
fRoll1 = glm::radians(fNewRoll1); // Ra: przeliczone jest bardziej przydatne do obliczeń
|
|
fRoll2 = glm::radians(fNewRoll2);
|
|
bCurve = bIsCurve;
|
|
if (Global.bRollFix)
|
|
{ // Ra: poprawianie przechyłki
|
|
// Przechyłka powinna być na środku wewnętrznej szyny, a standardowo jest w osi
|
|
// toru. Dlatego trzeba podnieść tor oraz odpowiednio podwyższyć podsypkę.
|
|
// Nie wykonywać tej funkcji, jeśli podwyższenie zostało uwzględnione w edytorze.
|
|
// Problematyczne mogą byc rozjazdy na przechyłce - lepiej je modelować w edytorze.
|
|
// Na razie wszystkie scenerie powinny być poprawiane.
|
|
// Jedynie problem będzie z podwójną rampą przechyłkową, która w środku będzie
|
|
// mieć moment wypoziomowania, ale musi on być również podniesiony.
|
|
if (fRoll1 != 0.0)
|
|
{ // tylko jeśli jest przechyłka
|
|
double w1 = std::abs(std::sin(fRoll1) * 0.75); // 0.5*w2+0.0325; //0.75m dla 1.435
|
|
Point1.y += w1; // modyfikacja musi być przed policzeniem dalszych parametrów
|
|
if (bCurve)
|
|
CPointOut.y += w1; // prosty ma wektory jednostkowe
|
|
pOwner->MovedUp1(w1); // zwrócić trzeba informację o podwyższeniu podsypki
|
|
}
|
|
if (fRoll2 != 0.f)
|
|
{
|
|
double w2 = std::abs(std::sin(fRoll2) * 0.75); // 0.5*w2+0.0325; //0.75m dla 1.435
|
|
Point2.y += w2; // modyfikacja musi być przed policzeniem dalszych parametrów
|
|
if (bCurve)
|
|
CPointIn.y += w2; // prosty ma wektory jednostkowe
|
|
// zwrócić trzeba informację o podwyższeniu podsypki
|
|
}
|
|
}
|
|
// kąt w planie, żeby nie liczyć wielokrotnie
|
|
// Ra: ten kąt jeszcze do przemyślenia jest
|
|
fDirection = -std::atan2(Point2.x - Point1.x, Point2.z - Point1.z);
|
|
if (bCurve)
|
|
{ // przeliczenie współczynników wielomianu, będzie mniej mnożeń i można policzyć pochodne
|
|
vC = 3.0 * (CPointOut - Point1); // t^1
|
|
vB = 3.0 * (CPointIn - CPointOut) - vC; // t^2
|
|
vA = Point2 - Point1 - vC - vB; // t^3
|
|
fLength = ComputeLength();
|
|
}
|
|
else {
|
|
fLength = glm::length(Point1 - Point2);
|
|
}
|
|
if (fLength <= 0) {
|
|
|
|
ErrorLog( "Bad track: zero length spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" );
|
|
fLength = 0.01; // crude workaround TODO: fix this properly
|
|
}
|
|
|
|
fStoop = std::atan2(Point2.y - Point1.y, fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie
|
|
|
|
fStep = fNewStep;
|
|
// NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches
|
|
if( pOwner->eType == tt_Switch
|
|
&& fStep * (3.0 * Global.SplineFidelity) > fLength ) {
|
|
// NOTE: a workaround for too short switches (less than 3 segments) messing up animation/generation of blades
|
|
fStep = fLength / ( 3.0 * Global.SplineFidelity );
|
|
}
|
|
// iSegCount = static_cast<int>( std::ceil( fLength / fStep ) ); // potrzebne do VBO
|
|
iSegCount = pOwner->eType == tt_Switch ? 6 * Global.SplineFidelity : static_cast<int>(std::ceil(fLength / fStep)); // potrzebne do VBO
|
|
|
|
fStep = fLength / iSegCount; // update step to equalize size of individual pieces
|
|
|
|
InitTFromSInterpolateData();
|
|
return true;
|
|
}
|
|
|
|
void TSegment::InitTFromSInterpolateData()
|
|
{
|
|
// pre-compute the curve parameter t at equal distance intervals
|
|
fTsBuffer.resize( iSegCount + 1 );
|
|
fTsBuffer[ 0 ] = 0.0;
|
|
for( int i = 1; i < iSegCount; ++i ) {
|
|
fTsBuffer[ i ] = GetTFromS( i * fStep );
|
|
}
|
|
fTsBuffer[ iSegCount ] = 1.0;
|
|
|
|
// pre-compute Hermite slopes dt/du = fStep / |B'(t)| for FastGetTFromS
|
|
// (dt/ds = 1/|B'(t)|, scaled by fStep to the interval coordinate u)
|
|
fTsSlope.resize( iSegCount + 1 );
|
|
for( int i = 0; i <= iSegCount; ++i ) {
|
|
// compute ds/dt derivative = |B'(t)|
|
|
double const speed = glm::length( GetFirstDerivative( fTsBuffer[ i ] ) );
|
|
// compute dt/du derivative = fStep / (ds/dt)
|
|
fTsSlope[ i ] = speed > 0.0 ? fStep / speed : 0.0;
|
|
}
|
|
// clamp slopes to keep t(s) monotone (PCHIP / Fritsch-Carlson condition)
|
|
for( int i = 0; i <= iSegCount; ++i ) {
|
|
double limit = std::numeric_limits<double>::max();
|
|
if( i > 0 ) { limit = std::min( limit, 3.0 * ( fTsBuffer[ i ] - fTsBuffer[ i - 1 ] ) ); }
|
|
if( i < iSegCount ) { limit = std::min( limit, 3.0 * ( fTsBuffer[ i + 1 ] - fTsBuffer[ i ] ) ); }
|
|
fTsSlope[ i ] = std::min( fTsSlope[ i ], limit );
|
|
}
|
|
}
|
|
|
|
glm::dvec3 TSegment::GetFirstDerivative(double const fTime) const
|
|
{
|
|
|
|
double fOmTime = 1.0 - fTime;
|
|
double fPowTime = fTime;
|
|
glm::dvec3 kResult = fOmTime * (CPointOut - Point1);
|
|
|
|
// int iDegreeM1 = 3 - 1;
|
|
|
|
double fCoeff = 2 * fPowTime;
|
|
kResult = (kResult + fCoeff * (CPointIn - CPointOut)) * fOmTime;
|
|
fPowTime *= fTime;
|
|
|
|
kResult += fPowTime * (Point2 - CPointIn);
|
|
kResult *= 3;
|
|
|
|
return kResult;
|
|
}
|
|
|
|
double TSegment::RombergIntegral(double const fA, double const fB) const
|
|
{
|
|
double fH = fB - fA;
|
|
|
|
const int ms_iOrder = 5;
|
|
|
|
double ms_apfRom[2][ms_iOrder];
|
|
|
|
ms_apfRom[0][0] =
|
|
0.5 * fH * (glm::length(GetFirstDerivative(fA)) + glm::length(GetFirstDerivative(fB)));
|
|
for (int i0 = 2, iP0 = 1; i0 <= ms_iOrder; i0++, iP0 *= 2, fH *= 0.5)
|
|
{
|
|
// approximations via the trapezoid rule
|
|
double fSum = 0.0;
|
|
int i1;
|
|
for (i1 = 1; i1 <= iP0; i1++)
|
|
fSum += glm::length(GetFirstDerivative(fA + fH * (i1 - 0.5)));
|
|
|
|
// Richardson extrapolation
|
|
ms_apfRom[1][0] = 0.5 * (ms_apfRom[0][0] + fH * fSum);
|
|
for (int i2 = 1, iP2 = 4; i2 < i0; i2++, iP2 *= 4)
|
|
{
|
|
ms_apfRom[1][i2] = (iP2 * ms_apfRom[1][i2 - 1] - ms_apfRom[0][i2 - 1]) / (iP2 - 1);
|
|
}
|
|
|
|
for (i1 = 0; i1 < i0; i1++)
|
|
ms_apfRom[0][i1] = ms_apfRom[1][i1];
|
|
}
|
|
|
|
return ms_apfRom[0][ms_iOrder - 1];
|
|
}
|
|
|
|
double TSegment::GetTFromS(double const s) const
|
|
{
|
|
// initial guess for Newton's method
|
|
double fTolerance = 0.001;
|
|
double fRatio = s / fLength; // fLength is approx RombergIntegral(0, 1), good enough to start Newton
|
|
double fTime = std::lerp( 0.0, 1.0, fRatio );
|
|
|
|
int iteration = 0;
|
|
double fDifference {}; // exposed for debug down the road
|
|
do {
|
|
fDifference = RombergIntegral(0, fTime) - s;
|
|
if( std::abs( fDifference ) < fTolerance ) {
|
|
return fTime;
|
|
}
|
|
fTime -= fDifference / glm::length(GetFirstDerivative(fTime));
|
|
++iteration;
|
|
}
|
|
while( iteration < 10 ); // arbitrary limit
|
|
|
|
// Newton's method failed. If this happens, increase iterations or
|
|
// tolerance or integration accuracy.
|
|
// return -1; //Ra: tu nigdy nie dojdzie
|
|
ErrorLog( "Bad track: shape estimation failed for spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" );
|
|
// MessageBox(0,"Too many iterations","GetTFromS",MB_OK);
|
|
return fTime;
|
|
};
|
|
|
|
double TSegment::FastGetTFromS(double const s) const
|
|
{ // Faster version of GetTFromS: cubic Hermite interpolation using
|
|
// the precomputed values in fTsBuffer and slopes in fTsSlope. It
|
|
// avoids the expensive RombergIntegral+Newton per call. Hermite
|
|
// interpolation is C1-continuous, so no sudden speed changes when
|
|
// moving from one interval to the next.
|
|
// NOTE: for s outside <0, fLength> the result is extrapolated
|
|
// outside <0, 1>, which is the expected behavior (see e.g. usage
|
|
// in GetDirection())
|
|
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;
|
|
double const u3 = u2 * u;
|
|
double const h01 = -2.0 * u3 + 3.0 * u2;
|
|
double const t = ( ( 1.0 - h01 ) * fTsBuffer[ i ] +
|
|
( u3 - 2.0 * u2 + u ) * fTsSlope [ i ] +
|
|
( h01 ) * fTsBuffer[ i + 1 ] +
|
|
( u3 - u2 ) * fTsSlope [ i + 1 ] );
|
|
return t;
|
|
}
|
|
|
|
glm::dvec3 TSegment::RaInterpolate(double const t) const
|
|
{ // wyliczenie XYZ na krzywej Beziera z użyciem współczynników
|
|
return t * (t * (t * vA + vB) + vC) + Point1; // 9 mnożeń, 9 dodawań
|
|
};
|
|
|
|
glm::dvec3 TSegment::RaInterpolate0(double const t) const
|
|
{ // wyliczenie XYZ na krzywej Beziera, na użytek liczenia długości nie jest dodawane Point1
|
|
return t * (t * (t * vA + vB) + vC); // 9 mnożeń, 6 dodawań
|
|
};
|
|
|
|
double TSegment::ComputeLength() const // McZapkie-150503: dlugosc miedzy punktami krzywej
|
|
{ // obliczenie długości krzywej Beziera za pomocą interpolacji odcinkami
|
|
// Ra: zamienić na liczenie rekurencyjne średniej z cięciwy i łamanej po kontrolnych
|
|
// 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
|
|
double t, l = 0;
|
|
glm::dvec3 last{0, 0, 0}; // długość liczona po przesunięciu odcinka do początku układu
|
|
glm::dvec3 tmp = Point2 - Point1;
|
|
int m = 20.0 * glm::length(tmp); // było zawsze do 10000, teraz jest liczone odcinkami po około 5cm
|
|
for (int i = 1; i <= m; i++)
|
|
{
|
|
t = double(i) / double(m); // wyznaczenie parametru na krzywej z przedziału (0,1>
|
|
// tmp=std::lerp(t,p1,cp1,cp2,p2);
|
|
tmp = RaInterpolate0(t); // obliczenie punktu dla tego parametru
|
|
t = glm::length(tmp - last); // obliczenie długości wektora
|
|
l += t; // zwiększenie wyliczanej długości
|
|
last = tmp;
|
|
}
|
|
return l;
|
|
}
|
|
|
|
// finds point on segment closest to specified point in 3d space. returns: point on segment as value in range 0-1
|
|
double
|
|
TSegment::find_nearest_point( glm::dvec3 const &Point ) const {
|
|
|
|
if( false == bCurve || iSegCount == 1 ) {
|
|
// for straight track just treat it as a single segment
|
|
return
|
|
nearest_segment_point(
|
|
glm::dvec3{ FastGetPoint_0() },
|
|
glm::dvec3{ FastGetPoint_1() },
|
|
Point );
|
|
}
|
|
else {
|
|
// for curves iterate through segment chunks, and find the one which gives us the least distance to the specified point
|
|
double distance = std::numeric_limits<double>::max();
|
|
double nearest;
|
|
// NOTE: we're reusing already created segment chunks, which are created based on splinefidelity setting
|
|
// this means depending on splinefidelity the results can be potentially slightly different
|
|
for( int segmentidx = 0; segmentidx < iSegCount; ++segmentidx ) {
|
|
|
|
auto const segmentpoint =
|
|
std::clamp(
|
|
nearest_segment_point(
|
|
glm::dvec3{ FastGetPoint( fTsBuffer[ segmentidx ] ) },
|
|
glm::dvec3{ FastGetPoint( fTsBuffer[ segmentidx + 1 ] ) },
|
|
Point ) // point in range 0-1 on current segment
|
|
* ( fTsBuffer[ segmentidx + 1 ] - fTsBuffer[ segmentidx ] ) // segment length
|
|
+ fTsBuffer[ segmentidx ], // segment offset
|
|
0.0, 1.0 ); // we clamp the range in case there's some floating point math inaccuracies
|
|
|
|
auto const segmentdistance = glm::length2( Point - glm::dvec3{ FastGetPoint( segmentpoint ) } );
|
|
if( segmentdistance < distance ) {
|
|
|
|
nearest = segmentpoint;
|
|
distance = segmentdistance;
|
|
}
|
|
}
|
|
//
|
|
return nearest;
|
|
}
|
|
}
|
|
|
|
const double fDirectionOffset = 0.1; // długość wektora do wyliczenia kierunku
|
|
|
|
glm::dvec3 TSegment::GetDirection(double const fDistance) const
|
|
{ // takie toporne liczenie pochodnej dla podanego dystansu od Point1
|
|
double t1 = FastGetTFromS(fDistance - fDirectionOffset);
|
|
if (t1 <= 0.0)
|
|
return CPointOut - Point1; // na zewnątrz jako prosta
|
|
double t2 = FastGetTFromS(fDistance + fDirectionOffset);
|
|
if (t2 >= 1.0)
|
|
return Point1 - CPointIn; // na zewnątrz jako prosta
|
|
return FastGetPoint(t2) - FastGetPoint(t1);
|
|
}
|
|
|
|
glm::dvec3 TSegment::FastGetDirection(double fDistance, double fOffset)
|
|
{ // takie toporne liczenie pochodnej dla parametru 0.0÷1.0
|
|
double t1 = fDistance - fOffset;
|
|
if (t1 <= 0.0)
|
|
return CPointOut - Point1; // wektor na początku jest stały
|
|
double t2 = fDistance + fOffset;
|
|
if (t2 >= 1.0)
|
|
return Point2 - CPointIn; // wektor na końcu jest stały
|
|
return FastGetPoint(t2) - FastGetPoint(t1);
|
|
}
|
|
/*
|
|
Math3D::vector3 TSegment::GetPoint(double const fDistance) const
|
|
{ // wyliczenie współrzędnych XYZ na torze w odległości (fDistance) od Point1
|
|
if (bCurve)
|
|
{ // można by wprowadzić uproszczony wzór dla okręgów płaskich
|
|
double t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera
|
|
// return std::lerp(t,Point1,CPointOut,CPointIn,Point2);
|
|
return RaInterpolate(t);
|
|
}
|
|
else {
|
|
// wyliczenie dla odcinka prostego jest prostsze
|
|
return
|
|
std::lerp(
|
|
Point1, Point2,
|
|
std::clamp(
|
|
fDistance / fLength,
|
|
0.0, 1.0 ) );
|
|
}
|
|
};
|
|
*/
|
|
|
|
void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const {
|
|
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 );
|
|
// przechyłka w danym miejscu (zmienia się liniowo)
|
|
rotation.x = std::lerp( fRoll1, fRoll2, t );
|
|
// pochodna jest 3*A*t^2+2*B*t+C
|
|
auto const tangent = t * ( t * 3.0 * vA + vB + vB ) + vC;
|
|
// pochylenie krzywej (w pionie)
|
|
rotation.y = std::atan( tangent.y );
|
|
// kierunek krzywej w planie
|
|
rotation.z = -std::atan2( tangent.x, tangent.z );
|
|
}
|
|
else {
|
|
// wyliczenie dla odcinka prostego jest prostsze
|
|
auto const t = fDistance / fLength; // zerowych torów nie ma
|
|
position = FastGetPoint( t );
|
|
// przechyłka w danym miejscu (zmienia się liniowo)
|
|
rotation.x = std::lerp( fRoll1, fRoll2, t );
|
|
rotation.y = fStoop; // pochylenie toru prostego
|
|
rotation.z = fDirection; // kierunek toru w planie
|
|
}
|
|
};
|
|
|
|
glm::dvec3 TSegment::FastGetPoint(double const t) const
|
|
{
|
|
// return (bCurve?std::lerp(t,Point1,CPointOut,CPointIn,Point2):((1.0-t)*Point1+(t)*Point2));
|
|
return true == bCurve || iSegCount != 1 ? RaInterpolate(t) : glm::mix(Point1, Point2, t);
|
|
}
|
|
|
|
bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, const gfx::vertex_array &ShapePoints, bool const Transition, double fTextureLength, double Texturescale, int iSkip, int iEnd, std::pair<float, float> fOffsetX, glm::vec3 **p, bool bRender)
|
|
{ // generowanie trójkątów dla odcinka trajektorii ruchu
|
|
// standardowo tworzy triangle_strip dla prostego albo ich zestaw dla łuku
|
|
// po modyfikacji - dla ujemnego (iNumShapePoints) w dodatkowych polach tabeli podany jest przekrój końcowy
|
|
|
|
if( fTsBuffer.empty() )
|
|
return false; // prowizoryczne zabezpieczenie przed wysypem - ustalić faktyczną przyczynę
|
|
|
|
glm::vec3 pos1, pos2, dir, parallel1, parallel2, pt, norm;
|
|
float s, step, fOffset, tv1, tv2, t, fEnd;
|
|
auto const iNumShapePoints = Transition ? ShapePoints.size() / 2 : ShapePoints.size();
|
|
float const texturelength = fTextureLength * Texturescale;
|
|
float const texturescale = Texturescale;
|
|
|
|
float m1, jmm1, m2, jmm2; // pozycje względne na odcinku 0...1 (ale nie parametr Beziera)
|
|
step = fStep;
|
|
tv1 = 1.0; // Ra: to by można było wyliczać dla odcinka, wyglądało by lepiej
|
|
s = fStep * iSkip; // iSkip - ile odcinków z początku pominąć
|
|
int i = iSkip; // domyślnie 0
|
|
t = fTsBuffer[ i ]; // tabela wattości t dla segmentów
|
|
// BUG: length of spline can be 0, we should skip geometry generation for such cases
|
|
fOffset = 0.1 / fLength; // pierwsze 10cm
|
|
pos1 = glm::dvec3{ FastGetPoint( t ) - Origin }; // wektor początku segmentu
|
|
dir = glm::dvec3{ FastGetDirection( t, fOffset ) }; // wektor kierunku
|
|
parallel1 = glm::vec3{ -dir.z, 0.f, dir.x }; // wektor poprzeczny
|
|
if( glm::length2( parallel1 ) == 0.f ) {
|
|
// temporary workaround for malformed situations with control points placed above endpoints
|
|
parallel1 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } };
|
|
}
|
|
parallel1 = glm::normalize( parallel1 );
|
|
if( iEnd == 0 )
|
|
iEnd = iSegCount;
|
|
fEnd = fLength * double( iEnd ) / double( iSegCount );
|
|
/*
|
|
m2 = s / fEnd;
|
|
*/
|
|
m2 = static_cast<float>( i - iSkip ) / ( iEnd - iSkip );
|
|
|
|
jmm2 = 1.f - m2;
|
|
|
|
while( i < iEnd ) {
|
|
|
|
++i; // kolejny punkt łamanej
|
|
s += step; // końcowa pozycja segmentu [m]
|
|
m1 = m2;
|
|
jmm1 = jmm2; // stara pozycja
|
|
/*
|
|
m2 = s / fEnd;
|
|
*/
|
|
m2 = static_cast<float>( i - iSkip ) / ( iEnd - iSkip );
|
|
|
|
jmm2 = 1.f - m2; // nowa pozycja
|
|
if( i == iEnd ) { // gdy przekroczyliśmy koniec - stąd dziury w torach...
|
|
step -= s - fEnd; // jeszcze do wyliczenia mapowania potrzebny
|
|
s = fEnd;
|
|
m2 = 1.f;
|
|
jmm2 = 0.f;
|
|
}
|
|
/*
|
|
while( tv1 < 0.0 ) {
|
|
tv1 += 1.0;
|
|
}
|
|
*/
|
|
tv1 = clamp_circular( tv1, 1.0f );
|
|
tv2 = tv1 - step / texturelength; // mapowanie na końcu segmentu
|
|
|
|
t = fTsBuffer[ i ]; // szybsze od GetTFromS(s);
|
|
pos2 = glm::dvec3{ FastGetPoint( t ) - Origin };
|
|
dir = glm::dvec3{ FastGetDirection( t, fOffset ) }; // nowy wektor kierunku
|
|
parallel2 = glm::vec3{ -dir.z, 0.f, dir.x }; // wektor poprzeczny
|
|
if( glm::length2( parallel2 ) == 0.f ) {
|
|
// temporary workaround for malformed situations with control points placed above endpoints
|
|
parallel2 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } };
|
|
}
|
|
parallel2 = glm::normalize( parallel2 );
|
|
|
|
// TODO: refactor the loop, there's no need to calculate starting points for each segment when we can copy the end points of the previous one
|
|
if( Transition ) {
|
|
for( int j = 0; j < iNumShapePoints; ++j ) {
|
|
pt = parallel1 * ( jmm1 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m1 * ( ShapePoints[ j + iNumShapePoints ].position.x - fOffsetX.second ) ) + pos1;
|
|
pt.y += jmm1 * ShapePoints[ j ].position.y + m1 * ShapePoints[ j + iNumShapePoints ].position.y;
|
|
norm = ( jmm1 * ShapePoints[ j ].normal.x + m1 * ShapePoints[ j + iNumShapePoints ].normal.x ) * parallel1;
|
|
norm.y += jmm1 * ShapePoints[ j ].normal.y + m1 * ShapePoints[ j + iNumShapePoints ].normal.y;
|
|
if( bRender ) {
|
|
// skrzyżowania podczas łączenia siatek mogą nie renderować poboczy, ale potrzebować punktów
|
|
Output.emplace_back(
|
|
pt,
|
|
glm::normalize( norm ),
|
|
glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } );
|
|
}
|
|
if( p ) // jeśli jest wskaźnik do tablicy
|
|
if( *p )
|
|
if( !j ) // to dla pierwszego punktu
|
|
{
|
|
**p = pt;
|
|
( *p )++;
|
|
} // zapamiętanie brzegu jezdni
|
|
// dla trapezu drugi koniec ma inne współrzędne
|
|
pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j + iNumShapePoints ].position.x - fOffsetX.second ) ) + pos2;
|
|
pt.y += jmm2 * ShapePoints[ j ].position.y + m2 * ShapePoints[ j + iNumShapePoints ].position.y;
|
|
norm = ( jmm2 * ShapePoints[ j ].normal.x + m2 * ShapePoints[ j + iNumShapePoints ].normal.x ) * parallel2;
|
|
norm.y += jmm2 * ShapePoints[ j ].normal.y + m2 * ShapePoints[ j + iNumShapePoints ].normal.y;
|
|
if( bRender ) {
|
|
// skrzyżowania podczas łączenia siatek mogą nie renderować poboczy, ale potrzebować punktów
|
|
Output.emplace_back(
|
|
pt,
|
|
glm::normalize( norm ),
|
|
glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } );
|
|
}
|
|
if( p ) // jeśli jest wskaźnik do tablicy
|
|
if( *p )
|
|
if( !j ) // to dla pierwszego punktu
|
|
if( i == iSegCount ) {
|
|
**p = pt;
|
|
( *p )++;
|
|
} // zapamiętanie brzegu jezdni
|
|
}
|
|
}
|
|
else {
|
|
if( bRender ) {
|
|
for( int j = 0; j < iNumShapePoints; ++j ) {
|
|
//łuk z jednym profilem
|
|
pt = parallel1 * ( jmm1 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m1 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos1;
|
|
pt.y += ShapePoints[ j ].position.y;
|
|
norm = ShapePoints[ j ].normal.x * parallel1;
|
|
norm.y += ShapePoints[ j ].normal.y;
|
|
|
|
Output.emplace_back(
|
|
pt,
|
|
glm::normalize( norm ),
|
|
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } );
|
|
|
|
pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos2;
|
|
pt.y += ShapePoints[ j ].position.y;
|
|
norm = ShapePoints[ j ].normal.x * parallel2;
|
|
norm.y += ShapePoints[ j ].normal.y;
|
|
|
|
Output.emplace_back(
|
|
pt,
|
|
glm::normalize( norm ),
|
|
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } );
|
|
}
|
|
}
|
|
}
|
|
pos1 = pos2;
|
|
parallel1 = parallel2;
|
|
tv1 = tv2;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
void TSegment::render_lines(std::vector<gfx::basic_vertex> &out, float quality) const
|
|
{
|
|
float step = 1.0f / iSegCount / quality;
|
|
|
|
float x;
|
|
|
|
glm::vec3 previous = FastGetPoint(0.0);
|
|
|
|
for (x = step; x <= 1.0f; x += step) {
|
|
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
|
|
|
|
previous = glm::vec3(FastGetPoint(x));
|
|
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
|
|
}
|
|
|
|
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
|
|
|
|
previous = glm::vec3(FastGetPoint(1.0));
|
|
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
|
|
}
|
|
|
|
glm::vec3 TSegment::get_nearest_point(const glm::dvec3 &point, float quality) const
|
|
{
|
|
float step = 1.0f / iSegCount / quality;
|
|
|
|
float x;
|
|
|
|
glm::vec3 nearest;
|
|
float min = std::numeric_limits<float>::max();
|
|
|
|
for (x = step; x <= 1.0f; x += step) {
|
|
glm::vec3 p1 = FastGetPoint(x);
|
|
glm::vec3 p2 = FastGetPoint(glm::min(1.0f, x + step));
|
|
|
|
if (p1 != p2) {
|
|
float l2 = glm::distance2(p1, p2);
|
|
float t = glm::max(0.0f, glm::min(1.0f, glm::dot((glm::vec3)point - p1, p2 - p1) / l2));
|
|
glm::vec3 proj = p1 + t * (p2 - p1);
|
|
p1 = proj;
|
|
}
|
|
|
|
float dist2 = glm::distance2(p1, (glm::vec3)point);
|
|
|
|
if (dist2 < min) {
|
|
nearest = p1;
|
|
min = dist2;
|
|
}
|
|
}
|
|
|
|
return nearest;
|
|
}
|