17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-20 19:37:44 +02:00
Files
maszyna/world/Track.cpp
maj00r fe2cd6a09a fix(track): portal tracks were saved as "track none"
The export took the category of the track from the whole flag word, so a portal track, whose flag word carries 0x100 next to the category, was written as "track none". The loader then fails on that track with no segment. The category is now taken from the low four bits.
2026-10-19 17:10:06 +02:00

4156 lines
176 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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak and others
*/
#include "stdafx.h"
#include "world/Track.h"
#include "world/TrackRails.h"
#include "simulation/simulation.h"
#include "scene/scene.h"
#include "utilities/Globals.h"
#include "world/Event.h"
#include "world/MemCell.h"
#include "input/messaging.h"
#include "vehicle/DynObj.h"
#include "vehicle/Driver.h"
#include "model/AnimModel.h"
#include "model/MdlMngr.h"
#include "model/Model3d.h"
#include "utilities/Timer.h"
#include "utilities/Logs.h"
#include "rendering/renderer.h"
// 101206 Ra: trapezoidalne drogi i tory
// 110720 Ra: rozprucie zwrotnicy i odcinki izolowane
static float const fMaxOffset = 0.1f; // double(0.1f)==0.100000001490116
// const int NextMask[4]={0,1,0,1}; //tor następny dla stanów 0, 1, 2, 3
// const int PrevMask[4]={0,0,1,1}; //tor poprzedni dla stanów 0, 1, 2, 3
const int iLewo4[4] = {5, 3, 4, 6}; // segmenty (1..6) do skręcania w lewo
const int iPrawo4[4] = {-4, -6, -3, -5}; // segmenty (1..6) do skręcania w prawo
const int iProsto4[4] = {1, -1, 2, -2}; // segmenty (1..6) do jazdy prosto
const int iEnds4[13] = {3, 0, 2, 1, 2, 0, -1, 1, 3, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1"
const int iLewo3[4] = {1, 3, 2, 1}; // segmenty do skręcania w lewo
const int iPrawo3[4] = {-2, -1, -3, -2}; // segmenty do skręcania w prawo
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;
TSwitchExtension::TSwitchExtension(TTrack *owner, int const what)
{ // na początku wszystko puste
pNexts[0] = nullptr; // wskaźniki do kolejnych odcinków ruchu
pNexts[1] = nullptr;
pPrevs[0] = nullptr;
pPrevs[1] = nullptr;
fOffset1 = fOffset = fDesiredOffset = -fOffsetDelay; // położenie zasadnicze
fOffset2 = 0.f; // w zasadniczym wewnętrzna iglica dolega
Segments[0] = std::make_shared<TSegment>(owner); // z punktu 1 do 2
Segments[1] = std::make_shared<TSegment>(owner); // z punktu 3 do 4 (1=3 dla zwrotnic; odwrócony dla skrzyżowań, ewentualnie 1=4)
Segments[2] = what >= 3 ?
std::make_shared<TSegment>(owner) :
nullptr; // z punktu 2 do 4 skrzyżowanie od góry: wersja "-1":
Segments[3] = what >= 4 ?
std::make_shared<TSegment>(owner) :
nullptr; // z punktu 4 do 1 1 1=4 0 0=3
Segments[4] = what >= 5 ?
std::make_shared<TSegment>(owner) :
nullptr; // z punktu 1 do 3 4 x 3 3 3 x 2 2
Segments[5] = what >= 6 ?
std::make_shared<TSegment>(owner) :
nullptr; // z punktu 3 do 2 2 2 1 1
}
TSwitchExtension::~TSwitchExtension()
{ // nie ma nic do usuwania
}
/*
TIsolated::TIsolated()
{ // utworznie pustego
TIsolated("none", NULL);
};
*/
TIsolated::TIsolated(std::string const &n, TIsolated *i) :
asName( n ), pNext( i )
{
// utworznie obwodu izolowanego. nothing to do here.
};
void TIsolated::DeleteAll() {
while( pRoot ) {
auto *next = pRoot->Next();
delete pRoot;
pRoot = next;
}
}
TIsolated * TIsolated::Find(std::string const &n)
{ // znalezienie obiektu albo utworzenie nowego
TIsolated *p = pRoot;
while (p)
{ // jeśli się znajdzie, to podać wskaźnik
if (p->asName == n)
return p;
p = p->pNext;
}
pRoot = new TIsolated(n, pRoot); // BUG: source of a memory leak
return pRoot;
};
void
TIsolated::AssignEvents() {
evBusy = simulation::Events.FindEvent( asName + ":busy" );
evFree = simulation::Events.FindEvent( asName + ":free" );
evInc = simulation::Events.FindEvent( asName + ":inc" );
evDec = simulation::Events.FindEvent( asName + ":dec" );
}
void TIsolated::Modify(int i, TDynamicObject *o)
{ // dodanie lub odjęcie osi
if (iAxles)
{ // grupa zajęta
iAxles += i;
if (!iAxles)
{ // jeśli po zmianie nie ma żadnej osi na odcinku izolowanym
if (evFree)
simulation::Events.AddToQuery(evFree, o); // dodanie zwolnienia do kolejki
if (Global.iMultiplayer) // jeśli multiplayer
multiplayer::WyslijString(asName, 10); // wysłanie pakietu o zwolnieniu
if (pMemCell) // w powiązanej komórce
pMemCell->UpdateValues( "", 0, int( pMemCell->Value2() ) & ~0xFF,
basic_event::flags::value2 ); //"zerujemy" ostatnią wartość
}
}
else
{ // grupa była wolna
iAxles += i;
if (iAxles)
{
if (evBusy)
simulation::Events.AddToQuery(evBusy, o); // dodanie zajętości do kolejki
if (Global.iMultiplayer) // jeśli multiplayer
{
auto const *owner = o->Mechanik != nullptr && o->Mechanik->primary() ? o->Mechanik : o->ctOwner;
auto textline = owner != nullptr ? Bezogonkow(owner->TrainName(), true) : "none";
if ("none" != textline && Global.bIsolatedTrainName) {
textline = ":" + Bezogonkow(owner->TrainName(), true);
}
else {
textline = "";
}
multiplayer::WyslijString(asName+textline, 11); // wysłanie pakietu o zajęciu
}
if (pMemCell) // w powiązanej komórce
pMemCell->UpdateValues( "", 0, int( pMemCell->Value2() ) | 1, basic_event::flags::value2 ); // zmieniamy ostatnią wartość na nieparzystą
}
}
if (i > 0 && evInc)
simulation::Events.AddToQuery(evInc, o);
if (i < 0 && evDec)
simulation::Events.AddToQuery(evDec, o);
// pass the event to the parent
if( pParent != nullptr ) {
pParent->Modify( i, o );
}
};
// tworzenie nowego odcinka ruchu
TTrack::TTrack( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {
track_rails::forget();
}
TTrack::~TTrack()
{ // likwidacja odcinka
track_rails::forget();
if( eType == tt_Cross ) {
delete SwitchExtension->vPoints; // skrzyżowanie może mieć punkty
}
}
void TTrack::Init()
{ // tworzenie pomocniczych danych
switch (eType)
{
case tt_Switch:
SwitchExtension = std::make_shared<TSwitchExtension>( this, 2 ); // na wprost i na bok
break;
case tt_Cross: // tylko dla skrzyżowania dróg
SwitchExtension = std::make_shared<TSwitchExtension>( this, 6 ); // 6 po³¹czeñ
SwitchExtension->vPoints = nullptr; // brak tablicy punktów
SwitchExtension->bPoints = false; // tablica punktów nie wypełniona
SwitchExtension->iRoads = 4; // domyślnie 4
break;
case tt_Normal:
Segment = std::make_shared<TSegment>( this );
break;
case tt_Table: // oba potrzebne
SwitchExtension = std::make_shared<TSwitchExtension>( this, 1 ); // kopia oryginalnego toru
Segment = std::make_shared<TSegment>(this);
break;
}
}
bool
TTrack::sort_by_material( TTrack const *Left, TTrack const *Right ) {
return std::tie( Left->m_material1, Left->m_material2 ) < std::tie( Right->m_material1, Right->m_material2 );
}
TTrack * TTrack::Create400m(int what, double dx)
{ // tworzenie toru do wstawiania taboru podczas konwersji na E3D
scene::node_data nodedata;
nodedata.name = "auto_400m"; // track isn't visible so only name is needed
auto *trk = new TTrack( nodedata );
trk->m_visible = false; // nie potrzeba pokazywać, zresztą i tak nie ma tekstur
trk->iCategoryFlag = what; // taki sam typ plus informacja, że dodatkowy
trk->Init(); // utworzenie segmentu
trk->Segment->Init(glm::dvec3(-dx, 0, 0), glm::dvec3(-dx, 0, 400), 10.0, 0, 0); // prosty
trk->location( glm::dvec3{ -dx, 0, 200 } ); //środek, aby się mogło wyświetlić
simulation::Paths.insert( trk );
simulation::Region->insert( trk );
return trk;
};
TTrack * TTrack::NullCreate(int dir)
{ // tworzenie toru wykolejającego od strony (dir), albo pętli dla samochodów
TTrack
*trk { nullptr },
*trk2 { nullptr };
scene::node_data nodedata;
nodedata.name = "auto_null"; // track isn't visible so only name is needed
trk = new TTrack( nodedata );
trk->m_visible = false; // nie potrzeba pokazywać, zresztą i tak nie ma tekstur
trk->iCategoryFlag = iCategoryFlag & 15 | 0x80; // taki sam typ plus informacja, że dodatkowy
float r1, r2;
Segment->GetRolls(r1, r2); // pobranie przechyłek na początku toru
glm::dvec3 p1, cv1, cv2, p2; // będziem tworzyć trajektorię lotu
if (iCategoryFlag & 1)
{ // tylko dla kolei
trk->iDamageFlag = 128; // wykolejenie
trk->fVelocity = 0.0; // koniec jazdy
trk->Init(); // utworzenie segmentu
switch (dir)
{ //łączenie z nowym torem
case 0:
p1 = Segment->FastGetPoint_0();
p2 = p1 - 450.0 * glm::normalize(Segment->GetDirection1());
// bo prosty, kontrolne wyliczane przy zmiennej przechyłce
trk->Segment->Init(p1, p2, 5, -glm::degrees(r1), 70.0);
ConnectPrevPrev(trk, 0);
break;
case 1:
p1 = Segment->FastGetPoint_1();
p2 = p1 - 450.0 * glm::normalize(Segment->GetDirection2());
// bo prosty, kontrolne wyliczane przy zmiennej przechyłce
trk->Segment->Init(p1, p2, 5, glm::degrees(r2), 70.0);
ConnectNextPrev(trk, 0);
break;
case 3: // na razie nie możliwe
p1 = SwitchExtension->Segments[1]->FastGetPoint_1(); // koniec toru drugiego zwrotnicy
p2 = p1 - 450.0 * glm::normalize(SwitchExtension->Segments[1]->GetDirection2()); // przedłużenie na wprost
trk->Segment->Init(p1, p2, 5, glm::degrees(r2), 70.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce
ConnectNextPrev(trk, 0);
// trk->ConnectPrevNext(trk,dir);
SetConnections(1); // skopiowanie połączeń
Switch(1); // bo się przełączy na 0, a to coś chce się przecież wykoleić na bok
break; // do drugiego zwrotnicy... nie zadziała?
}
}
else
{ // tworznie pętelki dla samochodów
trk->fVelocity = 20.0; // zawracanie powoli
trk->fRadius = 20.0; // promień, aby się dodawało do tabelki prędkości i liczyło narastająco
trk->Init(); // utworzenie segmentu
trk2 = new TTrack( nodedata );
trk2->iCategoryFlag =
iCategoryFlag & 15 | 0x80; // taki sam typ plus informacja, że dodatkowy
trk2->m_visible = false;
trk2->fVelocity = 20.0; // zawracanie powoli
trk2->fRadius = 20.0; // promień, aby się dodawało do tabelki prędkości i liczyło narastająco
trk2->Init(); // utworzenie segmentu
trk->m_name = m_name + ":loopstart";
trk2->m_name = m_name + ":loopfinish";
switch (dir)
{ //łączenie z nowym torem
case 0:
p1 = Segment->FastGetPoint_0();
cv1 = -20.0 * glm::normalize(Segment->GetDirection1()); // pierwszy wektor kontrolny
p2 = p1 + cv1 + cv1; // 40m
// bo prosty, kontrolne wyliczane przy zmiennej przechyłce
trk->Segment->Init(p1, p1 + cv1, p2 + glm::dvec3(-cv1.z, cv1.y, cv1.x), p2, 2, -glm::degrees(r1), 0.0);
ConnectPrevPrev(trk, 0);
// bo prosty, kontrolne wyliczane przy zmiennej przechyłce
trk2->Segment->Init(p1, p1 + cv1, p2 + glm::dvec3(cv1.z, cv1.y, -cv1.x), p2, 2, -glm::degrees(r1), 0.0);
trk2->iPrevDirection = 0; // zwrotnie do tego samego odcinka
break;
case 1:
p1 = Segment->FastGetPoint_1();
cv1 = -20.0 * glm::normalize(Segment->GetDirection2()); // pierwszy wektor kontrolny
p2 = p1 + cv1 + cv1;
// bo prosty, kontrolne wyliczane przy zmiennej przechyłce
trk->Segment->Init(p1, p1 + cv1, p2 + glm::dvec3(-cv1.z, cv1.y, cv1.x), p2, 2, glm::degrees(r2), 0.0);
ConnectNextPrev(trk, 0);
// bo prosty, kontrolne wyliczane przy zmiennej przechyłce
trk2->Segment->Init(p1, p1 + cv1, p2 + glm::dvec3(cv1.z, cv1.y, -cv1.x), p2, 2, glm::degrees(r2), 0.0);
trk2->iPrevDirection = 1; // zwrotnie do tego samego odcinka
break;
}
trk2->trPrev = this;
trk->ConnectNextNext(trk2, 1); // połączenie dwóch dodatkowych odcinków punktami 2
}
// trzeba jeszcze dodać do odpowiedniego segmentu, aby się renderowały z niego pojazdy
trk->location( glm::dvec3{ 0.5 * ( p1 + p2 ) } ); //środek, aby się mogło wyświetlić
simulation::Paths.insert( trk );
simulation::Region->insert( trk );
if( trk2 ) {
trk2->location( trk->location() ); // ten sam środek jest
simulation::Paths.insert( trk2 );
simulation::Region->insert( trk2 );
}
return trk;
};
void TTrack::ConnectPrevPrev(TTrack *pTrack, int typ)
{ //łączenie torów - Point1 własny do Point1 cudzego
if (pTrack)
{ //(pTrack) może być zwrotnicą, a (this) tylko zwykłym odcinkiem
trPrev = pTrack;
iPrevDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
pTrack->trPrev = this;
pTrack->iPrevDirection = 0;
}
}
void TTrack::ConnectPrevNext(TTrack *pTrack, int typ)
{ //łaczenie torów - Point1 własny do Point2 cudzego
if (pTrack)
{
trPrev = pTrack;
iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
pTrack->trNext = this;
pTrack->iNextDirection = 0;
if (m_visible)
if (pTrack->m_visible)
if (eType == tt_Normal) // jeśli łączone są dwa normalne
if (pTrack->eType == tt_Normal)
if (fTrackWidth != pTrack->fTrackWidth // Ra: jeśli kolejny ma inne wymiary
||
fTexHeight1 != pTrack->fTexHeight1 ||
fTexWidth != pTrack->fTexWidth || fTexSlope != pTrack->fTexSlope)
pTrack->iTrapezoid |= 2; // to rysujemy potworka
}
}
void TTrack::ConnectNextPrev(TTrack *pTrack, int typ)
{ //łaczenie torów - Point2 własny do Point1 cudzego
if (pTrack)
{
trNext = pTrack;
iNextDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
pTrack->trPrev = this;
pTrack->iPrevDirection = 1;
if (m_visible)
if (pTrack->m_visible)
if (eType == tt_Normal) // jeśli łączone są dwa normalne
if (pTrack->eType == tt_Normal)
if (fTrackWidth != pTrack->fTrackWidth // Ra: jeśli kolejny ma inne wymiary
||
fTexHeight1 != pTrack->fTexHeight1 ||
fTexWidth != pTrack->fTexWidth || fTexSlope != pTrack->fTexSlope)
iTrapezoid |= 2; // to rysujemy potworka
}
}
void TTrack::ConnectNextNext(TTrack *pTrack, int typ)
{ //łaczenie torów - Point2 własny do Point2 cudzego
if (pTrack)
{
trNext = pTrack;
iNextDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
pTrack->trNext = this;
pTrack->iNextDirection = 1;
}
}
void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
{ // pobranie obiektu trajektorii ruchu
double a1, a2;
std::string str;
size_t i; //,state; //Ra: teraz już nie ma początkowego stanu zwrotnicy we wpisie
std::string token;
parser->getTokens();
*parser >> str; // typ toru
if (str == "normal")
{
eType = tt_Normal;
iCategoryFlag = 1;
}
else if (str == "switch")
{
eType = tt_Switch;
iCategoryFlag = 1;
}
else if (str == "turn")
{ // Ra: to jest obrotnica
eType = tt_Table;
iCategoryFlag = 1;
}
else if (str == "table")
{ // Ra: obrotnica, przesuwnica albo wywrotnica
eType = tt_Table;
iCategoryFlag = 1;
}
else if (str == "road")
{
eType = tt_Normal;
iCategoryFlag = 2;
}
else if (str == "cross")
{ // Ra: to będzie skrzyżowanie dróg
eType = tt_Cross;
iCategoryFlag = 2;
}
else if (str == "river")
{
eType = tt_Normal;
iCategoryFlag = 4;
}
else if (str == "tributary")
{
eType = tt_Tributary;
iCategoryFlag = 4;
}
else
eType = tt_Unknown;
if (Global.iWriteLogEnabled & 4)
WriteLog(str);
parser->getTokens(4);
float discard {};
*parser
>> discard
>> fTrackWidth
>> fFriction
>> fSoundDistance;
if (eType == tt_Switch)
{
fSoundDistance = 10.0f;
}
fTrackWidth2 = fTrackWidth; // rozstaw/szerokość w punkcie 2, na razie taka sama
parser->getTokens(2);
*parser
>> iQualityFlag
>> iDamageFlag;
if (iDamageFlag & 128)
iAction |= 0x80; // flaga wykolejania z powodu uszkodzenia
parser->getTokens();
*parser >> str; // environment
if (str == "flat")
eEnvironment = e_flat;
else if (str == "mountains" || str == "mountain")
eEnvironment = e_mountains;
else if (str == "canyon")
eEnvironment = e_canyon;
else if (str == "tunnel")
eEnvironment = e_tunnel;
else if (str == "bridge")
eEnvironment = e_bridge;
else if (str == "bank")
eEnvironment = e_bank;
else
{
eEnvironment = e_unknown;
Error( "Unknown track environment: \"" + str + "\"" );
}
parser->getTokens();
*parser >> token;
m_visible = token.compare("vis") == 0; // visible
if (m_visible)
{
parser->getTokens();
*parser >> str; // railtex
replace_slashes(str);
m_material1 = str == "none" ? null_handle : GfxRenderer->Fetch_Material(str);
parser->getTokens();
*parser >> fTexLength; // tex tile length
if (fTexLength < 0.01)
fTexLength = 4; // Ra: zabezpiecznie przed zawieszeniem
parser->getTokens();
*parser >> str; // sub || railtex
replace_slashes(str);
m_material2 = str == "none" ? null_handle : GfxRenderer->Fetch_Material(str);
parser->getTokens(3);
*parser
>> fTexHeight1
>> fTexWidth
>> fTexSlope;
if (iCategoryFlag & 4)
fTexHeight1 = -fTexHeight1; // rzeki mają wysokość odwrotnie niż drogi
}
else if (Global.iWriteLogEnabled & 4)
WriteLog("unvis");
Init(); // ustawia SwitchExtension
// path data
// all subtypes contain at least one path
m_paths.emplace_back();
m_paths.back().deserialize( *parser, pOrigin );
switch( eType ) {
case tt_Switch:
case tt_Cross:
case tt_Tributary: {
// these subtypes contain additional path
m_paths.emplace_back();
m_paths.back().deserialize( *parser, pOrigin );
break;
}
default: {
break;
}
}
// optional attributes
parser->getTokens();
*parser >> token;
str = token;
while (str != "endtrack")
{
if (str == "event0")
{
parser->getTokens();
*parser >> token;
m_events0.emplace_back( token, nullptr );
}
else if (str == "event1")
{
parser->getTokens();
*parser >> token;
m_events1.emplace_back( token, nullptr );
}
else if (str == "event2")
{
parser->getTokens();
*parser >> token;
m_events2.emplace_back( token, nullptr );
}
else if (str == "eventall0")
{
parser->getTokens();
*parser >> token;
m_events0all.emplace_back( token, nullptr );
}
else if (str == "eventall1")
{
parser->getTokens();
*parser >> token;
m_events1all.emplace_back( token, nullptr );
}
else if (str == "eventall2")
{
parser->getTokens();
*parser >> token;
m_events2all.emplace_back( token, nullptr );
}
else if (str == "velocity")
{
parser->getTokens();
*parser >> fVelocity; //*0.28; McZapkie-010602
if (SwitchExtension) // jeśli tor ruchomy
if (std::abs(fVelocity) >= 1.0) //żeby zero nie ograniczało dożywotnio
// zapamiętanie głównego ograniczenia; a np. -40 ogranicza tylko na bok
SwitchExtension->fVelocity = static_cast<float>(fVelocity);
}
else if (str == "isolated")
{ // obwód izolowany, do którego tor należy
parser->getTokens();
*parser >> token;
Isolated.push_back(TIsolated::Find(token));
}
else if (str == "angle1")
{ // kąt ścięcia końca od strony 1
// NOTE: not used/implemented
parser->getTokens();
*parser >> a1;
//Segment->AngleSet(0, a1);
}
else if (str == "angle2")
{ // kąt ścięcia końca od strony 2
// NOTE: not used/implemented
parser->getTokens();
*parser >> a2;
//Segment->AngleSet(1, a2);
}
else if (str == "fouling1")
{ // wskazanie modelu ukresu w kierunku 1
// NOTE: not used/implemented
parser->getTokens();
*parser >> token;
// nFouling[0]=
}
else if (str == "fouling2")
{ // wskazanie modelu ukresu w kierunku 2
// NOTE: not used/implemented
parser->getTokens();
*parser >> token;
// nFouling[1]=
}
else if (str == "overhead")
{ // informacja o stanie sieci: 0-jazda bezprądowa, >0-z opuszczonym i ograniczeniem prędkości
parser->getTokens();
*parser >> fOverhead;
if (fOverhead > 0.0)
iAction |= 0x40; // flaga opuszczenia pantografu (tor uwzględniany w skanowaniu jako
// ograniczenie dla pantografujących)
}
else if( str == "vradius" ) {
// y-axis track radius
// NOTE: not used/implemented
parser->getTokens();
*parser >> fVerticalRadius;
}
else if( str == "trackbed" ) {
// switch trackbed texture
auto const trackbedtexture { parser->getToken<std::string>() };
if( eType == tt_Switch ) {
SwitchExtension->m_material3 = GfxRenderer->Fetch_Material( trackbedtexture );
}
}
else if( str == "railprofile" ) {
// rail profile
auto const railprofile { parser->getToken<std::string>() };
if( iCategoryFlag == 1 ) {
m_profile1 = fetch_track_rail_profile( railprofile );
}
}
else if( str == "frogs" ) {
// frog and check rails made from the rails of the switch
if( eType == tt_Switch ) {
SwitchExtension->m_frogs = true;
}
}
else if( str == "movablenose" ) {
// the frog of the switch with a movable nose
if( eType == tt_Switch ) {
SwitchExtension->m_movablenose = true;
}
}
else if( str == "blades" ) {
// blades <heel> <throw>: where the moving blades of the switch end and how far their tips move over, 0: from the shape of the switch
auto const heel { parser->getToken<double>() };
auto const bladethrow { parser->getToken<double>() };
if( eType == tt_Switch ) {
SwitchExtension->m_bladeheel = std::max( 0.0, heel );
SwitchExtension->m_bladethrow = std::max( 0.0, bladethrow );
}
}
else if( str == "timbermodel" ) {
// timbermodel <model> <skin>: the model of the timbers under the switch or the crossing the track is part of, put together
// from its submodels begin, main and end, or drawn whole; skin none: the textures of the model
auto modelpath { parser->getToken<std::string>( false ) };
auto skinpath { parser->getToken<std::string>( false ) };
replace_slashes( modelpath );
m_timber_model_name = modelpath;
m_timber_skin_name = ( skinpath == "none" ? std::string{} : skinpath );
}
else if( str == "friction" ) {
// memory cell holding friction value modifiers
m_friction.first = parser->getToken<std::string>();
}
else if( str == "sleepermodel" ) {
// sleepermodel <frequency> <model> <skin> <offsetX> <offsetY> <offsetZ> <ballastZ>
// - frequency: meters between consecutive sleeper instances (must be > 0)
// - model: path to the .e3d sleeper model
// - skin: replacable skin path, or "none" for the model's defaults
// - offset: local-space offset applied per-instance (x=left/right, y=forward/back, z=up/down)
// - ballastZ: vertical shift applied to the auto-generated trackbed (ballast). negative pushes ballast down.
float frequency { 0.f };
float offsetx { 0.f }, offsety { 0.f }, offsetz { 0.f };
float ballastz { 0.f };
parser->getTokens( 1, false ); *parser >> frequency;
auto modelpath { parser->getToken<std::string>( false ) };
auto skinpath { parser->getToken<std::string>( false ) };
parser->getTokens( 3, false ); *parser >> offsetx >> offsety >> offsetz;
parser->getTokens( 1, false ); *parser >> ballastz;
if( frequency <= 0.01f ) {
ErrorLog( "Bad track: invalid sleepermodel frequency (" + std::to_string( frequency ) + ") for track \"" + m_name + "\"" );
}
else {
replace_slashes( modelpath );
m_sleeper_enabled = true;
m_sleeper_frequency = frequency;
m_sleeper_model_name = modelpath;
m_sleeper_skin_name = skinpath;
m_sleeper_offset = glm::vec3( offsetx, offsety, offsetz );
m_sleeper_ballast_z = ballastz;
// model and skin are resolved (and instance transforms baked) in build_sleeper_transforms,
// 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;
}
auto const velocity { fVelocity };
init_segments( true );
if( SwitchExtension ) {
fVelocity = velocity;
}
// alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru
if ((i = m_name.find("@")) != std::string::npos && i < m_name.length())
{
Isolated.push_back(TIsolated::Find(m_name.substr(i + 1, m_name.length())));
m_name = m_name.substr(0, i - 1); // usunięcie z nazwy
}
update_location();
// sleeper transforms are baked later in create_geometry(), once the owning cell has
// assigned this track its m_origin (otherwise the local-space matrices would be relative
// to a stale origin and the renderer would draw sleepers in the wrong place).
}
void TTrack::update_location() {
location( (
CurrentSegment()->FastGetPoint_0()
+ CurrentSegment()->FastGetPoint( 0.5 )
+ CurrentSegment()->FastGetPoint_1() )
/ 3.0 );
}
void TTrack::init_segments( bool const Initial ) {
glm::dvec3 p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań
double r1, r2, r3, r4;
double segsize = 5.0; // długość odcinka segmentowania
fTexHeight1 -= fTexHeightOffset;
fTexHeightOffset = 0.f;
iTrapezoid = 0;
switch (eType) {
// Ra: łuki segmentowane co 5m albo 314-kątem foremnym
case tt_Table: {
// obrotnica jest prawie jak zwykły tor
iAction |= 2; // flaga zmiany położenia typu obrotnica
}
case tt_Normal: {
// pobranie współrzędnych P1
auto const &path { m_paths[ 0 ] };
p1 = path.points[ segment_data::point::start ];
// pobranie współrzędnych punktów kontrolnych
cp1 = path.points[ segment_data::point::control1 ];
cp2 = path.points[ segment_data::point::control2 ];
// pobranie współrzędnych P2
p2 = path.points[ segment_data::point::end ];
r1 = path.rolls[ 0 ];
r2 = path.rolls[ 1 ];
fRadius = std::abs( path.radius ); // we wpisie może być ujemny
if (iCategoryFlag & 1)
{ // zero na główce szyny
// TODO: delay these calculations unti rail profile and thus height is known
auto const railheight { 0.18 };
p1.y += railheight;
p2.y += railheight;
// na przechyłce doliczyć jeszcze pół przechyłki
}
// length2 is better than length for comparing because it does not require sqrt function
if( glm::length2((p1 + p1 + p2) / 3.0 - p1 - cp1) < sq(0.02)
|| glm::length2((p1 + p2 + p2) / 3.0 - p2 + cp1) < sq(0.02) ) {
// "prostowanie" prostych z kontrolnymi, dokładność 2cm
cp1 = cp2 = glm::dvec3(0, 0, 0);
}
if( fRadius != 0 ) {
// gdy podany promień
segsize =
std::clamp(
std::abs( fRadius ) * ( 0.02 / Global.SplineFidelity ),
2.0 / Global.SplineFidelity,
10.0 / Global.SplineFidelity );
}
else {
// HACK: crude check whether claimed straight is an actual straight piece
if (cp1 == glm::dvec3() && cp2 == glm::dvec3())
{
segsize = 10.0; // for straights, 10m per segment works good enough
}
else {
// HACK: divide roughly in 10 segments.
segsize =
std::clamp(
glm::length( p1 - p2 ) * 0.1,
2.0 / Global.SplineFidelity,
10.0 / Global.SplineFidelity );
}
}
if (cp1 == glm::dvec3() && cp2 == glm::dvec3())
{
// Ra: hm, czasem dla prostego są podane...
// gdy prosty, kontrolne wyliczane przy zmiennej przechyłce
Segment->Init( p1, p2, segsize, r1, r2 );
}
else {
// gdy łuk (ustawia bCurve=true)
Segment->Init( p1, cp1 + p1, cp2 + p2, p2, segsize, r1, r2 );
}
if (r1 != 0 || r2 != 0)
iTrapezoid = 1; // są przechyłki do uwzględniania w rysowaniu
if (eType == tt_Table) // obrotnica ma doklejkę
{ // SwitchExtension=new TSwitchExtension(this,1); //dodatkowe zmienne dla obrotnicy
SwitchExtension->Segments[0]->Init(p1, p2, segsize, r1, r2 ); // kopia oryginalnego toru
}
else if (iCategoryFlag & 2)
if (m_material1 && fTexLength)
{ // dla drogi trzeba ustalić proporcje boków nawierzchni
auto const &texture1 { GfxRenderer->Texture( GfxRenderer->Material( m_material1 )->GetTexture(0) ) };
if( texture1.get_height() > 0 ) {
fTexRatio1 = static_cast<float>( texture1.get_width() ) / static_cast<float>( texture1.get_height() ); // proporcja boków
}
auto const &texture2 { GfxRenderer->Texture( GfxRenderer->Material( m_material2 )->GetTexture(0) ) };
if( texture2.get_height() > 0 ) {
fTexRatio2 = static_cast<float>( texture2.get_width() ) / static_cast<float>( texture2.get_height() ); // proporcja boków
}
}
break;
}
case tt_Cross: {
// skrzyżowanie dróg - 4 punkty z wektorami kontrolnymi
// segsize = 1.0; // specjalne segmentowanie ze względu na małe promienie
}
case tt_Tributary: // dopływ
case tt_Switch: { // zwrotnica
iAction |= 1; // flaga zmiany położenia typu zwrotnica lub skrzyżowanie dróg
// problemy z animacją iglic powstaje, gdzy odcinek prosty ma zmienną przechyłkę
// wtedy dzieli się na dodatkowe odcinki (po 0.2m, bo R=0) i animację diabli biorą
// Ra: na razie nie podejmuję się przerabiania iglic
// SwitchExtension=new TSwitchExtension(this,eType==tt_Cross?6:2); //zwrotnica ma doklejkę
auto const &path { m_paths[ 0 ] };
p1 = path.points[ segment_data::point::start ];
// pobranie współrzędnych punktów kontrolnych
cp1 = path.points[ segment_data::point::control1 ];
cp2 = path.points[ segment_data::point::control2 ];
// pobranie współrzędnych P2
p2 = path.points[ segment_data::point::end ];
r1 = path.rolls[ 0 ];
r2 = path.rolls[ 1 ];
fRadiusTable[0] = std::abs( path.radius ); // we wpisie może być ujemny
if (iCategoryFlag & 1)
{ // zero na główce szyny
// TODO: delay these calculations unti rail profile and thus height is known
auto const railheight { 0.18 };
p1.y += railheight;
p2.y += railheight;
// na przechyłce doliczyć jeszcze pół przechyłki?
}
if( eType != tt_Cross ) {
// dla skrzyżowań muszą być podane kontrolne
// length2 is better than length for comparing because it does not require sqrt function
if( glm::length2(( p1 + p1 + p2 ) / 3.0 - p1 - cp1 ) < sq(0.02)
|| glm::length2(( p1 + p2 + p2 ) / 3.0 - p2 + cp1 ) < sq(0.02) ) {
// "prostowanie" prostych z kontrolnymi, dokładność 2cm
cp1 = cp2 = glm::dvec3(0, 0, 0);
}
}
if( fRadiusTable[ 0 ] != 0 ) {
// gdy podany promień
segsize =
std::clamp(
std::abs( fRadiusTable[ 0 ] ) * ( 0.02 / Global.SplineFidelity ),
2.0 / Global.SplineFidelity,
10.0 / Global.SplineFidelity );
}
else {
// HACK: crude check whether claimed straight is an actual straight piece
if (cp1 == glm::dvec3() && cp2 == glm::dvec3())
{
segsize = 10.0; // for straights, 10m per segment works good enough
}
else {
// HACK: divide roughly in 10 segments.
segsize =
std::clamp(
glm::length( p1 - p2 ) * 0.1,
2.0 / Global.SplineFidelity,
10.0 / Global.SplineFidelity );
}
}
if (cp1 == glm::dvec3() && cp2 == glm::dvec3())
{
// Ra: hm, czasem dla prostego są podane...
// gdy prosty, kontrolne wyliczane przy zmiennej przechyłce
SwitchExtension->Segments[ 0 ]->Init( p1, p2, segsize, r1, r2 );
}
else {
// gdy łuk (ustawia bCurve=true)
SwitchExtension->Segments[ 0 ]->Init( p1, cp1 + p1, cp2 + p2, p2, segsize, r1, r2 );
}
auto const &path2 { m_paths[ 1 ] };
p3 = path2.points[ segment_data::point::start ];
// pobranie współrzędnych punktów kontrolnych
cp3 = path2.points[ segment_data::point::control1 ];
cp4 = path2.points[ segment_data::point::control2 ];
// pobranie współrzędnych P2
p4 = path2.points[ segment_data::point::end ];
r3 = path2.rolls[ 0 ];
r4 = path2.rolls[ 1 ];
fRadiusTable[1] = std::abs( path2.radius ); // we wpisie może być ujemny
if (iCategoryFlag & 1)
{ // zero na główce szyny
// TODO: delay these calculations unti rail profile and thus height is known
auto const railheight{ 0.18 };
p3.y += railheight;
p4.y += railheight;
// na przechyłce doliczyć jeszcze pół przechyłki?
}
if( eType != tt_Cross ) {
// dla skrzyżowań muszą być podane kontrolne
// length2 is better than length for comparing because it does not require sqrt function
if( glm::length2((p3 + p3 + p4) / 3.0 - p3 - cp3) < sq(0.02)
|| glm::length2((p3 + p4 + p4) / 3.0 - p4 + cp3) < sq(0.02) ) {
// "prostowanie" prostych z kontrolnymi, dokładność 2cm
cp3 = cp4 = glm::dvec3(0, 0, 0);
}
}
if( fRadiusTable[ 1 ] != 0 ) {
// gdy podany promień
segsize =
std::clamp(
std::abs( fRadiusTable[ 1 ] ) * ( 0.02 / Global.SplineFidelity ),
2.0 / Global.SplineFidelity,
10.0 / Global.SplineFidelity );
}
else {
// HACK: crude check whether claimed straight is an actual straight piece
if (cp3 == glm::dvec3() && cp4 == glm::dvec3())
{
segsize = 10.0; // for straights, 10m per segment works good enough
}
else {
// HACK: divide roughly in 10 segments.
segsize =
std::clamp(
glm::length( p3 - p4 ) * 0.1,
2.0 / Global.SplineFidelity,
10.0 / Global.SplineFidelity );
}
}
if (cp3 == glm::dvec3() && cp4 == glm::dvec3())
{
// Ra: hm, czasem dla prostego są podane...
// gdy prosty, kontrolne wyliczane przy zmiennej przechyłce
SwitchExtension->Segments[ 1 ]->Init( p3, p4, segsize, r3, r4 );
}
else {
if( eType != tt_Cross ) {
SwitchExtension->Segments[ 1 ]->Init( p3, p3 + cp3, p4 + cp4, p4, segsize, r3, r4 );
}
else {
// dla skrzyżowania dróg dać odwrotnie końce, żeby brzegi generować lewym
SwitchExtension->Segments[ 1 ]->Init( p4, p4 + cp4, p3 + cp3, p3, segsize, r4, r3 ); // odwrócony
}
}
if (eType == tt_Cross)
{ // Ra 2014-07: dla skrzyżowań będą dodatkowe segmenty
SwitchExtension->Segments[2]->Init(p2, cp2 + p2, cp4 + p4, p4, segsize, r2, r4); // z punktu 2 do 4
auto p1p3 = p3 - p1;
if (glm::length2(p1p3) < 0.01) // gdy mniej niż 10cm, to mamy skrzyżowanie trzech dróg
SwitchExtension->iRoads = 3;
else // dla 4 dróg będą dodatkowe 3 segmenty
{
SwitchExtension->Segments[3]->Init(p4, p4 + cp4, p1 + cp1, p1, segsize, r4, r1); // z punktu 4 do 1
SwitchExtension->Segments[4]->Init(p1, p1 + cp1, p3 + cp3, p3, segsize, r1, r3); // z punktu 1 do 3
SwitchExtension->Segments[5]->Init(p3, p3 + cp3, p2 + cp2, p2, segsize, r3, r2); // z punktu 3 do 2
}
}
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
if( eType == tt_Switch )
// Ra: zamienić później na iloczyn wektorowy
{
glm::dvec3 v1, v2;
double a1, a2;
v1 = SwitchExtension->Segments[0]->FastGetPoint_1()
- SwitchExtension->Segments[0]->FastGetPoint_0();
v2 = SwitchExtension->Segments[1]->FastGetPoint_1()
- SwitchExtension->Segments[1]->FastGetPoint_0();
a1 = atan2(v1.x, v1.z);
a2 = atan2(v2.x, v2.z);
a2 = a2 - a1;
while (a2 > M_PI)
a2 = a2 - 2 * M_PI;
while (a2 < -M_PI)
a2 = a2 + 2 * M_PI;
SwitchExtension->RightSwitch = a2 < 0; // lustrzany układ OXY...
}
break;
}
}
}
bool TTrack::AssignEvents() {
bool lookupfail { false };
std::vector< std::pair< std::string, event_sequence * > > const eventsequences {
{ "event0", &m_events0 }, { "eventall0", &m_events0all },
{ "event1", &m_events1 }, { "eventall1", &m_events1all },
{ "event2", &m_events2 }, { "eventall2", &m_events2all } };
for( auto &eventsequence : eventsequences ) {
for( auto &event : *eventsequence.second ) {
event.second = simulation::Events.FindEvent( event.first );
if( event.second != nullptr ) {
m_events = true;
}
else {
ErrorLog( "Bad track: " + ( m_name.empty() ? "unnamed track" : "\"" + m_name + "\"" ) + " can't find assigned event \"" + event.first + "\"" );
lookupfail = true;
}
}
}
auto const trackname { name() };
if (Global.iHiddenEvents & 1
&& false == trackname.empty() ) {
// jeśli podana jest nazwa torów, można szukać eventów skojarzonych przez nazwę
for( auto &eventsequence : eventsequences ) {
auto *event = simulation::Events.FindEvent( trackname + ':' + eventsequence.first );
if( event != nullptr ) {
// HACK: auto-associated events come with empty lookup string, to avoid including them in the text format export
eventsequence.second->emplace_back( "", event );
m_events = true;
}
}
}
return lookupfail == false;
}
bool TTrack::AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus)
{ // ustawienie eventów sygnalizacji rozprucia
if (SwitchExtension)
{
if (NewEventPlus)
SwitchExtension->evPlus = NewEventPlus;
if (NewEventMinus)
SwitchExtension->evMinus = NewEventMinus;
return true;
}
return false;
};
void TTrack::QueueEvents( event_sequence const &Events, TDynamicObject const *Owner ) {
for( auto const &event : Events ) {
if( event.second != nullptr ) {
simulation::Events.AddToQuery( event.second, Owner );
}
}
}
void TTrack::QueueEvents( event_sequence const &Events, TDynamicObject const *Owner, double const Delaylimit ) {
for( auto const &event : Events ) {
if( event.second != nullptr
&& event.second->m_delay <= Delaylimit ) {
simulation::Events.AddToQuery( event.second, Owner );
}
}
}
std::string TTrack::RemoteIsolatedName()
{ // podaje nazwę odcinka izolowanego, jesli nie ma on jeszcze przypisanych zdarzeń
for (const TIsolated *iso : Isolated)
if (!iso->evBusy && !iso->evFree)
return iso->asName;
return "";
};
// ABu: przeniesione z Path.h i poprawione!!!
bool TTrack::AddDynamicObject(TDynamicObject *Dynamic)
{ // dodanie pojazdu do trajektorii
// Ra: tymczasowo wysyłanie informacji o zajętości konkretnego toru
// Ra: usunąć po upowszechnieniu się odcinków izolowanych
if (iCategoryFlag & 0x100) // jeśli usuwaczek
{
Dynamic->MyTrack = nullptr; // trzeba by to uzależnić od kierunku ruchu...
return true;
}
if( Global.iMultiplayer ) {
// jeśli multiplayer
if( true == Dynamics.empty() ) {
// pierwszy zajmujący
if( m_name != "none" ) {
// przekazanie informacji o zajętości toru
multiplayer::WyslijString( m_name, 8 );
}
}
}
Dynamics.emplace_back( Dynamic );
Dynamic->MyTrack = this; // ABu: na ktorym torze jesteśmy
if( Dynamic->iOverheadMask ) {
// jeśli ma pantografy
Dynamic->OverheadTrack( fOverhead ); // przekazanie informacji o jeździe bezprądowej na tym odcinku toru
}
return true;
};
const int numPts = 4;
bool TTrack::CheckDynamicObject(TDynamicObject *Dynamic)
{ // sprawdzenie, czy pojazd jest przypisany do toru
for( auto dynamic : Dynamics ) {
if( dynamic == Dynamic ) {
return true;
}
}
return false;
};
bool TTrack::RemoveDynamicObject(TDynamicObject *Dynamic)
{ // usunięcie pojazdu z listy przypisanych do toru
bool result = false;
if( *Dynamics.begin() == Dynamic ) {
// most likely the object getting removed is at the front...
Dynamics.pop_front();
result = true;
}
else if( *--Dynamics.end() == Dynamic ) {
// ...or the back of the queue...
Dynamics.pop_back();
result = true;
}
else {
// ... if these fail, check all objects one by one
for( auto idx = Dynamics.begin(); idx != Dynamics.end(); /*iterator advancement is inside the loop*/ ) {
if( *idx == Dynamic ) {
idx = Dynamics.erase( idx );
result = true;
break; // object are unique, so we can bail out here.
}
else {
++idx;
}
}
}
if( Global.iMultiplayer ) {
// jeśli multiplayer
if( true == Dynamics.empty() ) {
// jeśli już nie ma żadnego
if( m_name != "none" ) {
// przekazanie informacji o zwolnieniu toru
multiplayer::WyslijString( m_name, 9 );
}
}
}
return result;
}
bool TTrack::InMovement()
{ // tory animowane (zwrotnica, obrotnica) mają SwitchExtension
if (SwitchExtension)
{
if (eType == tt_Switch)
return SwitchExtension->bMovement; // ze zwrotnicą łatwiej
if (eType == tt_Table)
if (SwitchExtension->pModel)
{
if (!SwitchExtension->CurrentIndex)
return false; // 0=zablokowana się nie animuje
// trzeba każdorazowo porównywać z kątem modelu
auto ac = SwitchExtension->pModel ? SwitchExtension->pModel->GetContainer() : nullptr;
return ac ?
ac->AngleGet() != SwitchExtension->fOffset ||
!(ac->TransGet() == SwitchExtension->vTrans) :
false;
// return true; //jeśli jest taki obiekt
}
}
return false;
};
void TTrack::RaAssign( TAnimModel *am, basic_event *done, basic_event *joined )
{ // Ra: wiązanie toru z modelem obrotnicy
if (eType == tt_Table)
{
SwitchExtension->pModel = am;
SwitchExtension->evMinus = done; // event zakończenia animacji (zadanie nowej przedłuża)
SwitchExtension->evPlus = joined; // event potwierdzenia połączenia (gdy nie znajdzie, to się nie połączy)
if( am != nullptr && am->GetContainer() ) {// może nie być?
am->GetContainer()->EventAssign( done ); // zdarzenie zakończenia animacji
}
}
};
void TTrack::create_map_geometry(std::vector<gfx::basic_vertex> &Bank, const gfx::geometrybank_handle Extra)
{
if (iCategoryFlag != 1)
return; // only tracks for now
gfx::userdata_array empty_userdata{};
switch (eType)
{
case tt_Normal: {
std::vector<gfx::basic_vertex> vertices;
Segment->render_lines(vertices, 0.5f);
std::copy(vertices.begin(), vertices.end(), std::back_inserter(Bank));
extra_map_geometry = GfxRenderer->Insert(vertices, empty_userdata, Extra, GL_LINES);
break;
}
case tt_Switch: {
std::vector<gfx::basic_vertex> vertices;
SwitchExtension->Segments[0]->render_lines(vertices, 0.5f);
std::copy(vertices.begin(), vertices.end(), std::back_inserter(Bank));
SwitchExtension->map_geometry[0] = GfxRenderer->Insert(vertices, empty_userdata, Extra, GL_LINES);
vertices.clear();
SwitchExtension->Segments[1]->render_lines(vertices, 0.5f);
std::copy(vertices.begin(), vertices.end(), std::back_inserter(Bank));
SwitchExtension->map_geometry[1] = GfxRenderer->Insert(vertices, empty_userdata, Extra, GL_LINES);
break;
}
default:
break;
}
}
TTrack *TTrack::Next(TTrack *visitor) {
if (eType == tt_Normal) {
if (trNext != visitor)
return trNext;
else
return trPrev;
} else if (eType == tt_Switch) {
int state = GetSwitchState();
if (SwitchExtension->pPrevs[0] == visitor
|| SwitchExtension->pPrevs[1] == visitor)
{
// we came from 'previous' side
return SwitchExtension->pNexts[state];
}
if (SwitchExtension->pNexts[0] == visitor
|| SwitchExtension->pNexts[1] == visitor)
{
// we came from 'next' side
return SwitchExtension->pPrevs[state];
}
}
return nullptr;
}
double TTrack::ActiveLength() {
if (eType == tt_Normal) {
return Segment->GetLength();
} else if (eType == tt_Switch) {
if (GetSwitchState() == 0)
return SwitchExtension->Segments[0]->GetLength();
else
return SwitchExtension->Segments[1]->GetLength();
}
return 1.0f;
}
void TTrack::get_map_active_paths(map_colored_paths &handles)
{
if (iCategoryFlag != 1)
return;
if (eType == tt_Switch) {
if (GetSwitchState() == 0)
handles.switches.push_back(SwitchExtension->map_geometry[0]);
else
handles.switches.push_back(SwitchExtension->map_geometry[1]);
}
}
void TTrack::get_map_paths_for_state(map_colored_paths &handles, int state)
{
if (iCategoryFlag != 1 || eType != tt_Switch)
return;
handles.highlighted.push_back(SwitchExtension->map_geometry[state]);
}
void TTrack::get_map_future_paths(map_colored_paths &handles) {
if (iCategoryFlag != 1)
return;
if (!Dynamics.empty()) {
if (eType == tt_Switch) {
if (GetSwitchState() == 0)
handles.occupied.push_back(SwitchExtension->map_geometry[0]);
else
handles.occupied.push_back(SwitchExtension->map_geometry[1]);
} else if (eType == tt_Normal) {
handles.occupied.push_back(extra_map_geometry);
}
static int stamp = 0;
stamp++;
float distance = Global.map_highlight_distance;
TTrack *track = trPrev;
TTrack *visitor = this;
while (distance > 0.0f && track && track->iterate_stamp != stamp) {
handles.future.push_back(track->extra_map_geometry);
track->iterate_stamp = stamp;
distance -= track->ActiveLength();
TTrack *tmp = track;
track = track->Next(visitor);
visitor = tmp;
}
distance = Global.map_highlight_distance;
stamp++;
track = trNext;
visitor = this;
while (distance > 0.0f && track && track->iterate_stamp != stamp) {
handles.future.push_back(track->extra_map_geometry);
track->iterate_stamp = stamp;
distance -= track->ActiveLength();
TTrack *tmp = track;
track = track->Next(visitor);
visitor = tmp;
}
}
}
glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const
{
if (eType == tt_Normal) {
return Segment->get_nearest_point(point);
}
else if (eType == tt_Switch) {
glm::vec3 nearest;
float min = std::numeric_limits<float>::max();
for (size_t i = 0; i < 2; i++) {
glm::dvec3 p = SwitchExtension->Segments[i]->get_nearest_point(point);
float dist2 = glm::distance2(p, point);
if (dist2 < min) {
nearest = p;
min = dist2;
}
}
return nearest;
}
return glm::vec3(NAN);
}
// wypełnianie tablic VBO
void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
track_rails::forget();
Geometry1.clear();
Geometry2.clear();
GeometryCut1.clear();
GeometryCut2.clear();
GeometryTreads.clear();
if( SwitchExtension ) {
SwitchExtension->Geometry3 = gfx::geometry_handle{};
}
m_sleeper_local_transforms.clear();
// the sleepers of this track and of the tracks around depend on where it runs
m_sleepers3d.dirty = true;
track_sleepers::unsettle_around( *this );
if( Bank == null_handle ) { return; }
create_geometry( Bank );
}
void TTrack::share_joints() {
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 { 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 < 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;
}
for( int theirend = 0; theirend < 2; ++theirend ) {
glm::dvec3 theirpoint;
glm::dvec3 theiracross;
theirs->plan_frame( theirend ? theirs->GetLength() : 0.0, theirpoint, theiracross );
if( theirend == end ) {
theiracross = -theiracross;
}
auto const gap { glm::length( theirpoint - point ) };
if( gap >= closest || glm::dot( across, theiracross ) < 0.9 ) {
continue;
}
closest = gap;
joint.emplace( 0.5 * ( point + theirpoint ), glm::normalize( across + theiracross ) );
}
}
}
segment->set_joint( end, joint );
}
}
}
void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
gfx::userdata_array empty_userdata;
// the rails of a frog with a movable nose are laid again as it moves over, in the same chunks
auto const movable { eType == tt_Switch && SwitchExtension && SwitchExtension->m_movablenose };
track_rails::reused_chunks const reuse { movable ? &SwitchExtension->m_nosechunks : nullptr };
if( eType == tt_Switch && SwitchExtension ) {
SwitchExtension->m_nosethrow = ( Bank != null_handle ? nose_throw() : -1.0 );
}
share_joints();
m_sleepers3d.active = track_sleepers::lays( *this );
// bake per-instance sleeper transforms now that the owning cell has assigned m_origin.
// safe to call here even if the track has no sleepermodel (early-outs internally).
if( m_sleeper_enabled && m_sleeper_local_transforms.empty() ) {
build_sleeper_transforms();
}
switch (iCategoryFlag & 15)
{
case 1: // tor
{
// zwykla szyna: //Ra: czemu główki są asymetryczne na wysokości 0.140?
gfx::vertex_array rpts1, rpts2;
create_track_rail_profile( rpts1, rpts2 );
auto const drawing { make_rail_drawing( Bank ) };
auto const cut_faces = [&]( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, geometryhandle_sequence &Geometry ) {
track_rails::cut_faces( Strip, Ring, Start, End, Bank, Geometry ); };
m_materialcut1 = track_rails::cut_material( m_material1 );
auto const layout { plan_rails( Bank != 0 ) };
if( Bank != 0 ) {
m_crossings = layout.crossed;
}
auto const lay_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
return this->lay_rail( layout, drawing, Path, Profile, Side, Texturelength, From, Openstart, Openend, Geometry, Cuts ); };
switch (eType) // dalej zależnie od typu
{
case tt_Table: // obrotnica jak zwykły tor, tylko animacja dochodzi
case tt_Normal:
if (m_material2 && ( Global.Sleepers3DBed || false == m_sleepers3d.active ) )
{ // podsypka z podkładami jest tylko dla zwykłego toru
gfx::vertex_array bpts1;
create_track_bed_profile( bpts1, trPrev, trNext );
// optional vertical shift of the auto-generated ballast (sleepermodel ballastZ).
// positive value raises the trackbed, negative pushes it down so a custom
// sleeper model placed on top can sit flush with the ballast surface.
if( m_sleeper_enabled && m_sleeper_ballast_z != 0.f ) {
for( auto &v : bpts1 ) {
v.position.y += m_sleeper_ballast_z;
}
}
auto const texturelength { texture_length( m_material2 ) };
gfx::vertex_array vertices;
Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, texturelength);
if( Bank != 0 && true == Geometry2.empty() ) {
Geometry2.emplace_back(track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
}
if( Bank == 0 && false == Geometry2.empty() ) {
// special variant, replace existing data for a turntable track
GfxRenderer->Replace(vertices, empty_userdata, Geometry2[0], GL_TRIANGLE_STRIP);
}
}
if (m_material1)
{ // szyny - generujemy dwie, najwyżej rysować się będzie jedną
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
auto const texturelength { texture_length( m_material1 ) };
gfx::vertex_array vertices;
auto const open { eType == tt_Normal && ( trPrev == nullptr || trNext == nullptr ) };
if( Bank != 0 && true == Geometry1.empty() ) {
for( auto const side : { 1, -1 } ) {
auto const &profile { side > 0 ? rpts1 : rpts2 };
if( eType == tt_Normal && iTrapezoid == 0 && lay_rail( 0, profile, side, texturelength, track_rails::blade_runon( *this, side ), trPrev == nullptr, trNext == nullptr, Geometry1, GeometryCut1 ) ) {
continue;
}
vertices.clear(); // reuse the scratchpad
Segment->RenderLoft( vertices, m_origin, profile, iTrapezoid > 0, texturelength );
if( open ) {
cut_faces( vertices, nnumPts, trPrev == nullptr, trNext == nullptr, GeometryCut1 );
}
Geometry1.emplace_back(track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
}
}
if( Bank == 0 && false == Geometry1.empty() ) {
// special variant, replace existing data for a turntable track
Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength );
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[0], GL_TRIANGLE_STRIP);
vertices.clear(); // reuse the scratchpad
Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength );
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[1], GL_TRIANGLE_STRIP);
}
}
break;
case tt_Switch: // dla zwrotnicy dwa razy szyny
if( m_material1 || m_material2 ) {
// iglice liczone tylko dla zwrotnic
gfx::vertex_array rpts3, rpts4;
create_track_blade_profile( rpts3, rpts4 );
// TODO, TBD: change all track geometry to triangles, to allow packing data in less, larger buffers
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
auto const dense { std::max( blade( 0 ).valid ? blade( 0 ).heel : 0.0, blade( 1 ).valid ? blade( 1 ).heel : 0.0 ) };
SwitchExtension->Segments[ 0 ]->set_dense( dense );
SwitchExtension->Segments[ 1 ]->set_dense( dense );
// positive jointlength: the switch is typically used along the main track, negative: along the diverging track
// TODO: determine this from names of textures assigned to the tracks
// auto const jointlength { static_cast<int>( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.15 ) ) };
auto const jointlength { 0 }; // temporary until implementation of the above
m_materialcut2 = track_rails::cut_material( m_material2 );
// rail behind the blade
auto const fixed_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
if( false == lay_rail( Path, Profile, Side, Texturelength, track_rails::blade_length( *this, Path ), false, false, Geometry, Cuts ) ) {
gfx::vertex_array vertices;
if( auto const &shape { blade( Path ) }; shape.valid ) {
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 ) );
}
Geometry.emplace_back( track_rails::insert_chunk( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
}
};
// rail along the whole path
auto const whole_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) {
if( false == lay_rail( Path, Profile, Side, Texturelength, 0.0, false, false, Geometry, Cuts ) ) {
gfx::vertex_array vertices;
SwitchExtension->Segments[ Path ]->RenderLoft( vertices, m_origin, Profile, false, Texturelength, 1.0, jointlength );
Geometry.emplace_back( track_rails::insert_chunk( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
}
};
gfx::vertex_array vertices;
for( int path = 0; path < 2; ++path ) {
auto const material { path ? m_material2 : m_material1 };
if( material == 0 ) {
continue;
}
auto const side { blade_side( path ) };
loft_blade( path, side > 0 ? rpts1 : rpts2, side > 0 ? rpts3 : rpts4, texture_length( material ), vertices );
cut_faces( vertices, nnumPts, true, false, path ? GeometryCut2 : GeometryCut1 );
( path ? Geometry2 : Geometry1 ).emplace_back( track_rails::insert_chunk( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
vertices.clear();
}
for( int path = 0; path < 2; ++path ) {
auto const material { path ? m_material2 : m_material1 };
if( material == 0 ) {
continue;
}
auto &geometry { path ? Geometry2 : Geometry1 };
auto &cuts { path ? GeometryCut2 : GeometryCut1 };
auto const texturelength { texture_length( material ) };
auto const side { blade_side( path ) };
auto const &rail { side > 0 ? rpts1 : rpts2 };
// fixed parts
// szyna za iglicą
fixed_rail( path, rail, side, texturelength, geometry, cuts );
if( path == 0 && jointlength > 0 ) {
// part of the diverging rail touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rail, false, texturelength, 1.0, 0, jointlength );
geometry.emplace_back( track_rails::insert_chunk( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) );
vertices.clear();
}
// other rail, full length; on the diverging path potentially minus part touched by wheels of vehicle going straight
whole_rail( path, side > 0 ? rpts2 : rpts1, -side, texturelength, geometry, cuts );
}
}
// auto-generated switch trackbed
if( true == Global.CreateSwitchTrackbeds ) {
gfx::vertex_array vertices;
create_switch_trackbed( vertices );
SwitchExtension->Geometry3 = track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP);
vertices.clear();
}
break;
}
} // koniec obsługi torów
break;
case 2: // McZapkie-260302 - droga - rendering
switch (eType) // dalej zależnie od typu
{
case tt_Normal: // drogi proste, bo skrzyżowania osobno
{
gfx::vertex_array bpts1; // punkty głównej płaszczyzny przydają się do robienia boków
if (m_material1 || m_material2) {
// punkty się przydadzą, nawet jeśli nawierzchni nie ma
create_road_profile( bpts1 );
}
if (m_material1) // jeśli podana była tekstura, generujemy trójkąty
{ // tworzenie trójkątów nawierzchni szosy
auto const texturelength { texture_length( m_material1 ) };
gfx::vertex_array vertices;
Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, texturelength);
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
}
if (m_material2)
{ // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?)
auto const side{ std::abs( fTexWidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza
auto const slop{ std::abs( fTexSlope ) }; // brzeg zewnętrzny
auto const texturelength { texture_length( m_material2 ) };
gfx::vertex_array rpts1, rpts2; // współrzędne przekroju i mapowania dla prawej i lewej strony
create_road_side_profile( rpts1, rpts2, bpts1 );
gfx::vertex_array vertices;
if( fTexHeight1 >= 0.0 || slop != 0.0 ) {
// tylko jeśli jest z prawej
Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
if( fTexHeight1 >= 0.0 || side != 0.0 ) {
// tylko jeśli jest z lewej
Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
}
break;
}
case tt_Cross: // skrzyżowanie dróg rysujemy inaczej
{ // ustalenie współrzędnych środka - przecięcie Point1-Point2 z CV4-Point4
double a[4]; // kąty osi ulic wchodzących
glm::dvec3 p[4]; // punkty się przydadzą do obliczeń
// na razie połowa odległości pomiędzy Point1 i Point2, potem się dopracuje
a[0] = a[1] = 0.5; // parametr do poszukiwania przecięcia łuków
// modyfikować a[0] i a[1] tak, aby trafić na przecięcie odcinka 34
p[0] = SwitchExtension->Segments[0]->FastGetPoint(a[0]); // współrzędne środka pierwszego odcinka
p[1] = SwitchExtension->Segments[1]->FastGetPoint(a[1]); //-//- drugiego
// p[2]=p[1]-p[0]; //jeśli różne od zera, przeliczyć a[0] i a[1] i wyznaczyć nowe punkty
glm::dvec3 oxz = p[0]; // punkt mapowania środka tekstury skrzyżowania
p[0] = SwitchExtension->Segments[0]->GetDirection1(); // Point1 - pobranie wektorów kontrolnych
p[1] = SwitchExtension->Segments[1]->GetDirection2(); // Point3 (bo zamienione)
p[2] = SwitchExtension->Segments[0]->GetDirection2(); // Point2
p[3] = SwitchExtension->Segments[1]->GetDirection1(); // Point4 (bo zamienione)
a[0] = atan2(-p[0].x, p[0].z); // kąty stycznych osi dróg
a[1] = atan2(-p[1].x, p[1].z);
a[2] = atan2(-p[2].x, p[2].z);
a[3] = atan2(-p[3].x, p[3].z);
p[0] = SwitchExtension->Segments[0]->FastGetPoint_0(); // Point1 - pobranie współrzędnych końców
p[1] = SwitchExtension->Segments[1]->FastGetPoint_1(); // Point3
p[2] = SwitchExtension->Segments[0]->FastGetPoint_1(); // Point2
p[3] = SwitchExtension->Segments[1]->FastGetPoint_0(); // Point4 - przy trzech drogach pokrywa się z Point1
// 2014-07: na początek rysować brzegi jak dla łuków
// punkty brzegu nawierzchni uzyskujemy podczas renderowania boków (bez sensu, ale najszybciej było zrobić)
int pointcount;
if( SwitchExtension->iRoads == 3 ) {
// mogą być tylko 3 drogi zamiast 4
pointcount =
SwitchExtension->Segments[ 0 ]->RaSegCount()
+ SwitchExtension->Segments[ 1 ]->RaSegCount()
+ SwitchExtension->Segments[ 2 ]->RaSegCount();
}
else {
pointcount =
SwitchExtension->Segments[ 2 ]->RaSegCount()
+ SwitchExtension->Segments[ 3 ]->RaSegCount()
+ SwitchExtension->Segments[ 4 ]->RaSegCount()
+ SwitchExtension->Segments[ 5 ]->RaSegCount();
}
if (!SwitchExtension->vPoints)
{ // jeśli tablica punktów nie jest jeszcze utworzona, zliczamy punkty i tworzymy ją
// we'll need to add couple extra points for the complete fan we'll build
SwitchExtension->vPoints = new glm::vec3[pointcount + SwitchExtension->iRoads];
}
glm::vec3 *b =
SwitchExtension->bPoints ?
nullptr :
SwitchExtension->vPoints; // zmienna robocza, NULL gdy tablica punktów już jest wypełniona
gfx::vertex_array bpts1; // punkty głównej płaszczyzny przydają się do robienia boków
if (m_material1 || m_material2) { // punkty się przydadzą, nawet jeśli nawierzchni nie ma
create_road_profile( bpts1, true );
}
// najpierw renderowanie poboczy i zapamiętywanie punktów
// problem ze skrzyżowaniami jest taki, że teren chce się pogrupować wg tekstur, ale zaczyna od nawierzchni
// sama nawierzchnia nie wypełni tablicy punktów, bo potrzebne są pobocza
// ale pobocza renderują się później, więc nawierzchnia nie załapuje się na renderowanie w swoim czasie
if( m_material2 )
{ // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?)
gfx::vertex_array rpts1, rpts2; // współrzędne przekroju i mapowania dla prawej i lewej strony
create_road_side_profile( rpts1, rpts2, bpts1, true );
// Ra 2014-07: trzeba to przerobić na pętlę i pobierać profile (przynajmniej 2..4) z sąsiednich dróg
bool render = m_material2 != 0; // renderować nie trzeba, ale trzeba wyznaczyć punkty brzegowe nawierzchni
auto const side{ std::abs( fTexWidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza
auto const texturelength{ texture_length( m_material2 ) };
gfx::vertex_array vertices;
if (SwitchExtension->iRoads == 4)
{ // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka
if( fTexHeight1 >= 0.0 || side != 0.0 ) {
SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render );
if( true == render ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
SwitchExtension->Segments[ 3 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render );
if( true == render ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
SwitchExtension->Segments[ 4 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render );
if( true == render ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
SwitchExtension->Segments[ 5 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render );
if( true == render ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
}
}
else {
// punkt 3 pokrywa się z punktem 1, jak w zwrotnicy; połączenie 1->2 nie musi być prostoliniowe
if( fTexHeight1 >= 0.0 || side != 0.0 ) {
SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); // z P2 do P4
if( true == render ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); // z P4 do P3=P1 (odwrócony)
if( true == render ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); // z P1 do P2
if( true == render ) {
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
}
}
}
// renderowanie nawierzchni na końcu
double sina0 = sin(a[0]), cosa0 = cos(a[0]);
double u, v;
if( false == SwitchExtension->bPoints // jeśli tablica nie wypełniona
&& b != nullptr ) {
SwitchExtension->bPoints = true; // tablica punktów została wypełniona
}
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
gfx::vertex_array vertices;
// jeśli podana tekstura nawierzchni
// we start with a vertex in the middle...
vertices.emplace_back(
glm::vec3{
oxz.x - m_origin.x,
oxz.y - m_origin.y,
oxz.z - m_origin.z },
glm::vec3{ 0.0f, 1.0f, 0.0f },
glm::vec2{ 0.5f, 0.5f } );
// ...and add one extra vertex to close the fan...
u = ( SwitchExtension->vPoints[ 0 ].x - oxz.x + m_origin.x ) / texturelength;
v = ( SwitchExtension->vPoints[ 0 ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * texturelength );
vertices.emplace_back(
glm::vec3 {
SwitchExtension->vPoints[ 0 ].x,
SwitchExtension->vPoints[ 0 ].y,
SwitchExtension->vPoints[ 0 ].z },
glm::vec3{ 0.0f, 1.0f, 0.0f },
// mapowanie we współrzędnych scenerii
glm::vec2{
cosa0 * u + sina0 * v + 0.5,
-sina0 * u + cosa0 * v + 0.5 } );
// ...then draw the precalculated rest
for (int i = pointcount + SwitchExtension->iRoads - 1; i >= 0; --i) {
// mapowanie we współrzędnych scenerii
u = ( SwitchExtension->vPoints[ i ].x - oxz.x + m_origin.x ) / texturelength;
v = ( SwitchExtension->vPoints[ i ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * texturelength );
vertices.emplace_back(
glm::vec3 {
SwitchExtension->vPoints[ i ].x,
SwitchExtension->vPoints[ i ].y,
SwitchExtension->vPoints[ i ].z },
glm::vec3{ 0.0f, 1.0f, 0.0f },
// mapowanie we współrzędnych scenerii
glm::vec2{
cosa0 * u + sina0 * v + 0.5,
-sina0 * u + cosa0 * v + 0.5 } );
}
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_FAN));
}
break;
} // tt_cross
} // road
break;
case 4: // Ra: rzeki na razie jak drogi, przechyłki na pewno nie mają
switch (eType) // dalej zależnie od typu
{
case tt_Normal: // drogi proste, bo skrzyżowania osobno
{
gfx::vertex_array bpts1; // punkty głównej płaszczyzny przydają się do robienia boków
if (m_material1 || m_material2) { // punkty się przydadzą, nawet jeśli nawierzchni nie ma
create_road_profile( bpts1 );
}
if (m_material1) // jeśli podana była tekstura, generujemy trójkąty
{ // tworzenie trójkątów nawierzchni szosy
gfx::vertex_array vertices;
Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, fTexLength);
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
}
if (m_material2)
{ // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?)
gfx::vertex_array rpts1, rpts2; // współrzędne przekroju i mapowania dla prawej i lewej strony
create_road_side_profile( rpts1, rpts2, bpts1 );
gfx::vertex_array vertices;
Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, fTexLength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, fTexLength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
}
}
break;
}
return;
};
void TTrack::RenderDynSounds()
{ // odtwarzanie dźwięków pojazdów jest niezależne od ich wyświetlania
for( auto dynamic : Dynamics ) {
dynamic->RenderSounds();
}
};
//---------------------------------------------------------------------------
bool TTrack::SetConnections(int i)
{ // przepisanie aktualnych połączeń toru do odpowiedniego segmentu
if (SwitchExtension)
{
SwitchExtension->pNexts[i] = trNext;
SwitchExtension->pPrevs[i] = trPrev;
SwitchExtension->iNextDirection[i] = iNextDirection;
SwitchExtension->iPrevDirection[i] = iPrevDirection;
if (eType == tt_Switch)
{ // zwrotnica jest wyłącznie w punkcie 1, więc tor od strony Prev jest zawsze ten sam
SwitchExtension->pPrevs[i ^ 1] = trPrev;
SwitchExtension->iPrevDirection[i ^ 1] = iPrevDirection;
}
else if (eType == tt_Cross)
if (SwitchExtension->iRoads == 3)
{
}
if (i)
Switch(0); // po przypisaniu w punkcie 4 włączyć stan zasadniczy
return true;
}
Error("Cannot set connections");
return false;
}
bool TTrack::Switch(int i, float const t, float const d)
{ // przełączenie torów z uruchomieniem animacji
if (SwitchExtension) // tory przełączalne mają doklejkę
if (eType == tt_Switch)
{ // przekładanie zwrotnicy jak zwykle
if (t > 0.f) // prędkość liniowa ruchu iglic
SwitchExtension->fOffsetSpeed = t; // prędkość łatwiej zgrać z animacją modelu
if (d >= 0.f) // dodatkowy ruch drugiej iglicy (zamknięcie nastawnicze)
SwitchExtension->fOffsetDelay = d;
i &= 1; // ograniczenie błędów !!!!
SwitchExtension->fDesiredOffset =
i ? fMaxOffset + SwitchExtension->fOffsetDelay : -SwitchExtension->fOffsetDelay;
SwitchExtension->CurrentIndex = i;
Segment = SwitchExtension->Segments[i]; // wybranie aktywnej drogi - potrzebne to?
trNext = SwitchExtension->pNexts[i]; // przełączenie końców
trPrev = SwitchExtension->pPrevs[i];
iNextDirection = SwitchExtension->iNextDirection[i];
iPrevDirection = SwitchExtension->iPrevDirection[i];
fRadius = fRadiusTable[i]; // McZapkie: wybor promienia toru
if( SwitchExtension->fVelocity <= -2 ) {
//-1 oznacza maksymalną prędkość, a dalsze ujemne to ograniczenie na bok
fVelocity = i ? -SwitchExtension->fVelocity : -1;
}
else {
fVelocity = SwitchExtension->fVelocity;
}
if (SwitchExtension->pOwner ? SwitchExtension->pOwner->RaTrackAnimAdd(this) :
true) // jeśli nie dodane do animacji
{ // nie ma się co bawić
SwitchExtension->fOffset = SwitchExtension->fDesiredOffset;
// przeliczenie położenia iglic; czy zadziała na niewyświetlanym sektorze w VBO?
RaAnimate();
}
return true;
}
else if (eType == tt_Table)
{ // blokowanie (0, szukanie torów) lub odblokowanie (1, rozłączenie) obrotnicy
if (i) // NOTE: this condition seems opposite to intention/comment? TODO: investigate this
{ // 0: rozłączenie sąsiednich torów od obrotnicy
if (trPrev) // jeśli jest tor od Point1 obrotnicy
if (iPrevDirection) // 0:dołączony Point1, 1:dołączony Point2
trPrev->trNext = nullptr; // rozłączamy od Point2
else
trPrev->trPrev = nullptr; // rozłączamy od Point1
if (trNext) // jeśli jest tor od Point2 obrotnicy
if (iNextDirection) // 0:dołączony Point1, 1:dołączony Point2
trNext->trNext = nullptr; // rozłączamy od Point2
else
trNext->trPrev = nullptr; // rozłączamy od Point1
trNext = trPrev = nullptr; // na końcu rozłączamy obrotnicę (wkaźniki do sąsiadów już niepotrzebne)
fVelocity = 0.0; // AI, nie ruszaj się!
if (SwitchExtension->pOwner)
SwitchExtension->pOwner->RaTrackAnimAdd(this); // dodanie do listy animacyjnej
// TODO: unregister path ends in the owner cell
}
else
{ // 1: ustalenie finalnego położenia (gdy nie było animacji)
RaAnimate(); // ostatni etap animowania
// zablokowanie pozycji i połączenie do sąsiednich torów
// TODO: register new position of the path endpoints with the region
simulation::Region->TrackJoin( this );
if (trNext || trPrev)
{
fVelocity = 6.0; // jazda dozwolona
if( trPrev
&& trPrev->fVelocity == 0.0 ) {
// ustawienie 0 da możliwość zatrzymania AI na obrotnicy
trPrev->VelocitySet( 6.0 ); // odblokowanie dołączonego toru do jazdy
}
if( trNext
&& trNext->fVelocity == 0.0 ) {
trNext->VelocitySet( 6.0 );
}
if( SwitchExtension->evPlus ) { // w starych sceneriach może nie być
// potwierdzenie wykonania (np. odpala WZ)
simulation::Events.AddToQuery( SwitchExtension->evPlus, nullptr );
}
}
}
SwitchExtension->CurrentIndex = i; // zapamiętanie stanu zablokowania
return true;
}
else if (eType == tt_Cross)
{ // to jest przydatne tylko do łączenia odcinków
i &= 1;
SwitchExtension->CurrentIndex = i;
Segment = SwitchExtension->Segments[i]; // wybranie aktywnej drogi - potrzebne to?
trNext = SwitchExtension->pNexts[i]; // przełączenie końców
trPrev = SwitchExtension->pPrevs[i];
iNextDirection = SwitchExtension->iNextDirection[i];
iPrevDirection = SwitchExtension->iPrevDirection[i];
return true;
}
if (iCategoryFlag == 1)
iDamageFlag = (iDamageFlag & 127) + 128 * (i & 1); // przełączanie wykolejenia
else
Error("Cannot switch normal track");
return false;
};
int TTrack::nose_set() const {
return ( SwitchExtension->fOffset > 0.5f * fMaxOffset ? 1 : 0 );
}
double TTrack::nose_throw() const {
return ( fMaxOffset > 0.f ? std::clamp( static_cast<double>( SwitchExtension->fOffset / fMaxOffset ), 0.0, 1.0 ) : nose_set() );
}
double TTrack::trailing_point( int const Path ) const {
if( SwitchExtension->m_movablenose ) {
// the nose set the other way stands in the way of the wheels before they get to the blades
if( auto const frog { this->frog() }; frog.valid ) {
return frog.at[ Path ];
}
}
return track_rails::blade_length( *this, Path );
}
bool TTrack::SwitchForced(int i, TDynamicObject *o)
{ // rozprucie rozjazdu
if (SwitchExtension)
if (eType == tt_Switch)
{ //
if (i != SwitchExtension->CurrentIndex)
{
switch (i)
{
case 0:
if (SwitchExtension->evPlus)
simulation::Events.AddToQuery(SwitchExtension->evPlus, o); // dodanie do kolejki
break;
case 1:
if (SwitchExtension->evMinus)
simulation::Events.AddToQuery(SwitchExtension->evMinus, o); // dodanie do kolejki
break;
}
Switch(i); // jeśli się tu nie przełączy, to każdy pojazd powtórzy event rozrprucia
if( SwitchExtension->m_movablenose && o != nullptr ) {
// the wheels can't push the nose of the frog over, the vehicle runs off the rails at it
o->MoverParameters->Derail( TRAILED_SWITCH );
}
}
}
else if (eType == tt_Cross)
{ // ustawienie wskaźnika na wskazany segment
Segment = SwitchExtension->Segments[i];
}
return true;
};
// picks one of the routes through a crossroads for a driver heading for it
int TTrack::RouteDraw() const {
auto const count { std::min( RouteCount(), static_cast<int>( m_routeweights.size() ) ) };
auto total { 0.0 };
for( int route = 0; route < count; ++route ) {
total += std::max( 0.f, m_routeweights[ route ] );
}
if( total <= 0.0 ) {
// even odds, the way it's always been for the crossroads of the scenery
return 1 + static_cast<int>( std::floor( Random( static_cast<double>( RouteCount() ) - 0.001 ) ) );
}
auto draw { Random( total ) };
auto last { 1 };
for( int route = 0; route < count; ++route ) {
if( m_routeweights[ route ] <= 0.f ) { continue; }
last = route + 1;
draw -= m_routeweights[ route ];
if( draw < 0.0 ) { break; }
}
return last;
}
int TTrack::CrossSegment(int from, int into)
{ // ustawienie wskaźnika na segement w pożądanym kierunku (into) od strony (from)
// zwraca kod segmentu, z kierunkiem jazdy jako znakiem ±
int i = 0;
switch (into)
{
case 0: // stop
// WriteLog( "Stopping in P" + to_string( from + 1 ) + " on " + name() );
break;
case 1: // left
// WriteLog( "Turning left from P" + to_string( from + 1 ) + " on " + name() );
i = SwitchExtension->iRoads == 4 ? iLewo4[from] : iLewo3[from];
break;
case 2: // right
// WriteLog( "Turning right from P" + to_string( from + 1 ) + " on " + name() );
i = SwitchExtension->iRoads == 4 ? iPrawo4[from] : iPrawo3[from];
break;
case 3: // stright
// WriteLog( "Going straight from P" + to_string( from + 1 ) + " on " + name() );
i = SwitchExtension->iRoads == 4 ? iProsto4[from] : iProsto3[from];
break;
}
if (i)
{
Segment = SwitchExtension->Segments[abs(i) - 1];
// WriteLog("Selected segment: "+AnsiString(abs(i)-1));
}
return i;
};
void TTrack::RaAnimListAdd(TTrack *t)
{ // dodanie toru do listy animacyjnej
if (t != nullptr && SwitchExtension != nullptr)
{
if (t == this)
return; // siebie nie dodajemy drugi raz do listy
if (!t->SwitchExtension)
return; // nie podlega animacji
if (SwitchExtension->pNextAnim)
{
if (SwitchExtension->pNextAnim == t)
return; // gdy już taki jest
else
SwitchExtension->pNextAnim->RaAnimListAdd(t);
}
else
{
SwitchExtension->pNextAnim = t;
t->SwitchExtension->pNextAnim = nullptr; // nowo dodawany nie może mieć ogona
}
}
};
int TTrack::blade_side( int const Path ) const {
return ( SwitchExtension->RightSwitch == ( Path == 0 ) ? 1 : -1 );
}
void TTrack::loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::vertex_array const &Blade, float const Texturelength, gfx::vertex_array &Vertices ) const {
auto const opening { Path == 0 ? SwitchExtension->fOffset2 : fMaxOffset - SwitchExtension->fOffset1 };
auto const offset { blade_side( Path ) * opening };
if( auto const &shape { blade( Path ) }; shape.valid ) {
auto const thrown { blades_throw() * std::clamp( static_cast<double>( opening ) / ( fMaxOffset + SwitchExtension->fOffsetDelay ), 0.0, 1.0 ) };
std::vector<glm::dvec2> offsets;
for( auto const &bend : shape.bends ) {
offsets.emplace_back( bend.x, -thrown * bend.y );
}
auto const size { rail_dimensions() };
auto const length { SwitchExtension->Segments[ Path ]->GetLength() };
SwitchExtension->Segments[ Path ]->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = shape.tip, .to = std::min( shape.heel, length ), .offsets = offsets, .extents = shape.extents, .walls = true, .drops = shape.drops, .head = { size.neck, size.crest } } );
// the blade running on past the end of the path goes on along the plain track joined there, whose rail starts behind the heel
if( auto const *next { track_rails::runon_track( *this, Path ) }; next != nullptr && shape.heel > length + 1e-3 ) {
auto const along = [&]( std::vector<glm::dvec2> const &Knots ) {
std::vector<glm::dvec2> result;
for( auto const &knot : Knots ) {
result.emplace_back( knot.x - length, knot.y );
}
return result; };
next->Segment->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = 0.0, .to = shape.heel - length, .offsets = along( offsets ), .extents = along( shape.extents ), .walls = true, .drops = along( shape.drops ), .head = { size.neck, size.crest } } );
}
return;
}
auto const bladelength { track_rails::blade_steps( *this, Path ) };
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ Path ]->RenderLoft( Vertices, m_origin, Blade, true, Texturelength, 1.0, 0, bladelength / 2, { offset, offset / 2 } );
SwitchExtension->Segments[ Path ]->RenderLoft( Vertices, m_origin, Rail, false, Texturelength, 1.0, bladelength / 2, bladelength, { offset / 2, 0.f } );
}
TTrack * TTrack::RaAnimate()
{ // wykonanie rekurencyjne animacji, wywoływane przed wyświetleniem sektora
// zwraca wskaźnik toru wymagającego dalszej animacji
gfx::userdata_array empty_userdata;
if( SwitchExtension->pNextAnim )
SwitchExtension->pNextAnim = SwitchExtension->pNextAnim->RaAnimate();
bool m = true; // animacja trwa
if (eType == tt_Switch) // dla zwrotnicy tylko szyny
{
auto const v = SwitchExtension->fDesiredOffset - SwitchExtension->fOffset; // kierunek
SwitchExtension->fOffset += sign(v) * Timer::GetDeltaTime() * SwitchExtension->fOffsetSpeed;
// Ra: trzeba dać to do klasy...
SwitchExtension->fOffset1 = SwitchExtension->fOffset;
SwitchExtension->fOffset2 = SwitchExtension->fOffset;
if (SwitchExtension->fOffset1 > fMaxOffset)
SwitchExtension->fOffset1 = fMaxOffset; // ograniczenie animacji zewnętrznej iglicy
if (SwitchExtension->fOffset2 < 0.f)
SwitchExtension->fOffset2 = 0.f; // ograniczenie animacji wewnętrznej iglicy
if (v < 0.f)
{ // jak na pierwszy z torów
if (SwitchExtension->fOffset <= SwitchExtension->fDesiredOffset)
{
SwitchExtension->fOffset = SwitchExtension->fDesiredOffset;
m = false; // koniec animacji
}
}
else
{ // jak na drugi z torów
if (SwitchExtension->fOffset >= SwitchExtension->fDesiredOffset)
{
SwitchExtension->fOffset = SwitchExtension->fDesiredOffset;
m = false; // koniec animacji
}
}
auto const nosethrow { SwitchExtension->m_movablenose ? nose_throw() : 0.0 };
if( SwitchExtension->m_movablenose
&& SwitchExtension->m_nosethrow >= 0.0
&& ( std::abs( nosethrow - SwitchExtension->m_nosethrow ) >= 0.05
|| ( false == m && nosethrow != SwitchExtension->m_nosethrow ) )
&& false == Geometry1.empty() ) {
// the nose of the frog goes over to the wing rail of the other track along with the blades, laid again in steps
gfx::geometrybank_handle const bank { Geometry1.front().bank, 0 };
Geometry1.clear();
Geometry2.clear();
GeometryCut1.clear();
GeometryCut2.clear();
GeometryTreads.clear();
SwitchExtension->Geometry3 = gfx::geometry_handle{};
create_geometry( bank );
}
// skip the geometry update if no geometry for this track was generated yet
if( ( m_material1 != 0
|| m_material2 != 0 )
&& ( false == Geometry1.empty()
|| false == Geometry2.empty() ) ) {
// iglice liczone tylko dla zwrotnic
gfx::vertex_array rpts1, rpts2;
create_track_rail_profile( rpts1, rpts2 );
gfx::vertex_array rpts3, rpts4;
create_track_blade_profile( rpts3, rpts4 );
gfx::vertex_array vertices;
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
auto const replace_blade = [&]( geometryhandle_sequence const &Geometry, geometryhandle_sequence const &Cuts ) {
if( false == Cuts.empty() ) {
gfx::vertex_array triangles;
track_rails::cap_loft( vertices, nnumPts, true, false, triangles );
auto userdata { track_rails::worn_userdata( triangles.size() ) };
GfxRenderer->Replace( triangles, userdata, Cuts[ 0 ], GL_TRIANGLES );
}
GfxRenderer->Replace( vertices, empty_userdata, Geometry[ 0 ], GL_TRIANGLE_STRIP );
vertices.clear(); };
for( int path = 0; path < 2; ++path ) {
auto const material { path ? m_material2 : m_material1 };
if( material == 0 ) {
continue;
}
auto const side { blade_side( path ) };
loft_blade( path, side > 0 ? rpts1 : rpts2, side > 0 ? rpts3 : rpts4, texture_length( material ), vertices );
replace_blade( path ? Geometry2 : Geometry1, path ? GeometryCut2 : GeometryCut1 );
}
}
}
else if (eType == tt_Table) {
// dla obrotnicy - szyny i podsypka
if (SwitchExtension->pModel &&
SwitchExtension->CurrentIndex) // 0=zablokowana się nie animuje
{ // trzeba każdorazowo porównywać z kątem modelu
// //pobranie kąta z modelu
auto ac = SwitchExtension->pModel ? SwitchExtension->pModel->GetContainer() : // pobranie głównego submodelu
nullptr;
if( ac ) {
if( ac->AngleGet() != SwitchExtension->fOffset
|| !( ac->TransGet() == SwitchExtension->vTrans ) ) { // czy przemieściło się od ostatniego sprawdzania
double hlen = 0.5 * SwitchExtension->Segments[ 0 ]->GetLength(); // połowa długości
SwitchExtension->fOffset =
SwitchExtension->pModel->Angles().y // take into account orientation of the model
+ ac->AngleGet(); // pobranie kąta z submodelu
double
sina = -hlen * std::sin( glm::radians( SwitchExtension->fOffset ) ),
cosa = -hlen * std::cos( glm::radians( SwitchExtension->fOffset ) );
SwitchExtension->vTrans = ac->TransGet();
auto middle = location() + SwitchExtension->vTrans; // SwitchExtension->Segments[0]->FastGetPoint(0.5);
Segment->Init(middle + glm::dvec3(sina, 0.0, cosa), middle - glm::dvec3(sina, 0.0, cosa),
10.0 ); // nowy odcinek
for( auto dynamic : Dynamics ) {
// minimalny ruch, aby przeliczyć pozycję
dynamic->Move( 0.000001 );
}
// NOTE: passing empty handle is a bit of a hack here. could be refactored into something more elegant
create_geometry( {} );
} // animacja trwa nadal
}
}
else
m = false; // koniec animacji albo w ogóle nie połączone z modelem
}
return m ? this : SwitchExtension->pNextAnim; // zwraca obiekt do dalszej animacji
};
//---------------------------------------------------------------------------
void TTrack::RadioStop()
{ // przekazanie pojazdom rozkazu zatrzymania
for( auto dynamic : Dynamics ) {
dynamic->RadioStop();
}
};
double TTrack::WidthTotal()
{ // szerokość z poboczem
if (iCategoryFlag & 2) // jesli droga
if (fTexHeight1 >= 0.0) // i ma boki zagięte w dół (chodnik jest w górę)
return 2.0 * fabs(fTexWidth) +
0.5 * fabs(fTrackWidth + fTrackWidth2); // dodajemy pobocze
return 0.5 * fabs(fTrackWidth + fTrackWidth2); // a tak tylko zwykła średnia szerokość
};
bool TTrack::IsGroupable()
{ // czy wyświetlanie toru może być zgrupwane z innymi
if (eType == tt_Switch || eType == tt_Table)
return false; // tory ruchome nie są grupowane
if (eEnvironment == e_canyon || eEnvironment == e_tunnel)
return false; // tory ze zmianą światła
return true;
};
bool Equal(const glm::dvec3 v1, const glm::dvec3 *v2)
{ // sprawdzenie odległości punktów
// Ra: powinno być do 100cm wzdłuż toru i ze 2cm w poprzek (na prostej może nie być długiego
// kawałka)
// Ra: z automatycznie dodawanym stukiem, jeśli dziura jest większa niż 2mm.
if (fabs(v1.x - v2->x) > 0.02)
return false; // sześcian zamiast kuli
if (fabs(v1.z - v2->z) > 0.02)
return false;
if (fabs(v1.y - v2->y) > 0.02)
return false;
return true;
// return (SquareMagnitude(v1-*v2)<0.00012); //0.011^2=0.00012
};
int TTrack::TestPoint(const glm::dvec3 *Point)
{ // sprawdzanie, czy tory można połączyć
switch (eType)
{
case tt_Normal: // zwykły odcinek
if (trPrev == nullptr)
if (Equal(Segment->FastGetPoint_0(), Point))
return 0;
if (trNext == nullptr)
if (Equal(Segment->FastGetPoint_1(), Point))
return 1;
break;
case tt_Switch: // zwrotnica
{
int state = GetSwitchState(); // po co?
// Ra: TODO: jak się zmieni na bezpośrednie odwołania do segmentow zwrotnicy,
// to się wykoleja, ponieważ trNext zależy od przełożenia
Switch(0);
if (trPrev == nullptr)
// if (Equal(SwitchExtension->Segments[0]->FastGetPoint_0(),Point))
if (Equal(Segment->FastGetPoint_0(), Point))
{
Switch(state);
return 2;
}
if (trNext == nullptr)
// if (Equal(SwitchExtension->Segments[0]->FastGetPoint_1(),Point))
if (Equal(Segment->FastGetPoint_1(), Point))
{
Switch(state);
return 3;
}
Switch(1); // można by się pozbyć tego przełączania
if (trPrev == nullptr) // Ra: z tym chyba nie potrzeba łączyć
// if (Equal(SwitchExtension->Segments[1]->FastGetPoint_0(),Point))
if (Equal(Segment->FastGetPoint_0(), Point))
{
Switch(state); // Switch(0);
return 4;
}
if (trNext == nullptr) // TODO: to zależy od przełożenia zwrotnicy
// if (Equal(SwitchExtension->Segments[1]->FastGetPoint_1(),Point))
if (Equal(Segment->FastGetPoint_1(), Point))
{
Switch(state); // Switch(0);
return 5;
}
Switch(state);
}
break;
case tt_Cross: // skrzyżowanie dróg
// if (trPrev==NULL)
if (Equal(SwitchExtension->Segments[0]->FastGetPoint_0(), Point))
return 2;
// if (trNext==NULL)
if (Equal(SwitchExtension->Segments[0]->FastGetPoint_1(), Point))
return 3;
// if (trPrev==NULL)
if (Equal(SwitchExtension->Segments[1]->FastGetPoint_0(), Point))
return 4;
// if (trNext==NULL)
if (Equal(SwitchExtension->Segments[1]->FastGetPoint_1(), Point))
return 5;
break;
}
return -1;
};
// retrieves list of the track's end points
std::vector<glm::dvec3>
TTrack::endpoints() const {
switch( eType ) {
case tt_Normal:
case tt_Table: {
return {
glm::dvec3{ Segment->FastGetPoint_0() },
glm::dvec3{ Segment->FastGetPoint_1() } };
}
case tt_Switch:
case tt_Cross: {
return {
glm::dvec3{ SwitchExtension->Segments[ 0 ]->FastGetPoint_0() },
glm::dvec3{ SwitchExtension->Segments[ 0 ]->FastGetPoint_1() },
glm::dvec3{ SwitchExtension->Segments[ 1 ]->FastGetPoint_0() },
glm::dvec3{ SwitchExtension->Segments[ 1 ]->FastGetPoint_1() } };
}
default: {
return{};
}
}
}
// radius() subclass details, calculates node's bounding radius
float
TTrack::radius_() {
auto const points { endpoints() };
auto radius { 0.f };
for( auto &point : points ) {
radius = std::max(
radius,
static_cast<float>( glm::length( m_area.center - point ) ) ); // extra margin to prevent driven vehicle from flicking
}
return radius;
}
// serialize() subclass details, sends content of the subclass to provided stream
void
TTrack::serialize_( std::ostream &Output ) const {
// TODO: implement
}
// deserialize() subclass details, restores content of the subclass from provided stream
void
TTrack::deserialize_( std::istream &Input ) {
// TODO: implement
}
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void
TTrack::export_as_text_( std::ostream &Output ) const {
// header
Output << "track ";
// type
Output << (
eType == tt_Normal ? (
( iCategoryFlag & 15 ) == 1 ? "normal" :
( iCategoryFlag & 15 ) == 2 ? "road" :
( iCategoryFlag & 15 ) == 4 ? "river" :
"none" ) :
eType == tt_Switch ? "switch" :
eType == tt_Cross ? "cross" :
eType == tt_Table ? "turn" :
eType == tt_Tributary ? "tributary" :
"none" )
<< ' ';
// basic attributes
Output
<< Length() << ' '
<< fTrackWidth << ' '
<< fFriction << ' '
<< fSoundDistance << ' '
<< iQualityFlag << ' '
<< iDamageFlag << ' ';
// environment
Output << (
eEnvironment == e_flat ? "flat" :
eEnvironment == e_bridge ? "bridge" :
eEnvironment == e_tunnel ? "tunnel" :
eEnvironment == e_bank ? "bank" :
eEnvironment == e_canyon ? "canyon" :
eEnvironment == e_mountains ? "mountains" :
"none" )
<< ' ';
// visibility
// NOTE: 'invis' would be less wrong than 'unvis', but potentially incompatible with old 3rd party tools
Output << ( m_visible ? "vis" : "unvis" ) << ' ';
if( m_visible ) {
// texture parameters are supplied only if the path is set as visible
auto texturefile { (
m_material1 != null_handle ?
GfxRenderer->Material( m_material1 )->GetName() :
"none" ) };
if( texturefile.find( paths::textures ) == 0 ) {
// don't include 'textures/' in the path
texturefile.erase( 0, std::string{ paths::textures }.size() );
}
Output
<< texturefile << ' '
<< fTexLength << ' ';
texturefile = m_material2 != null_handle ? GfxRenderer->Material(m_material2)->GetName() : "none";
if( texturefile.find( paths::textures ) == 0 ) {
// don't include 'textures/' in the path
texturefile.erase( 0, std::string{ paths::textures }.size() );
}
Output << texturefile << ' ';
Output
<< (fTexHeight1 - fTexHeightOffset ) * ( iCategoryFlag & 4 ? -1 : 1 ) << ' '
<< fTexWidth << ' '
<< fTexSlope << ' ';
}
// path data
auto const precision { Output.precision( std::numeric_limits<double>::digits10 ) };
for( auto const &path : m_paths ) {
Output
<< path.points[ segment_data::point::start ].x << ' '
<< path.points[ segment_data::point::start ].y << ' '
<< path.points[ segment_data::point::start ].z << ' '
<< path.rolls[ 0 ] << ' '
<< path.points[ segment_data::point::control1 ].x << ' '
<< path.points[ segment_data::point::control1 ].y << ' '
<< path.points[ segment_data::point::control1 ].z << ' '
<< path.points[ segment_data::point::control2 ].x << ' '
<< path.points[ segment_data::point::control2 ].y << ' '
<< path.points[ segment_data::point::control2 ].z << ' '
<< path.points[ segment_data::point::end ].x << ' '
<< path.points[ segment_data::point::end ].y << ' '
<< path.points[ segment_data::point::end ].z << ' '
<< path.rolls[ 1 ] << ' '
<< path.radius << ' ';
}
Output.precision( precision );
// optional attributes
std::vector< std::pair< std::string, event_sequence const * > > const eventsequences {
{ "event0", &m_events0 }, { "eventall0", &m_events0all },
{ "event1", &m_events1 }, { "eventall1", &m_events1all },
{ "event2", &m_events2 }, { "eventall2", &m_events2all } };
for( auto &eventsequence : eventsequences ) {
for( auto &event : *eventsequence.second ) {
// NOTE: actual event name can be potentially different from its cached name, if it was renamed in the editor
// therefore on export we pull the name from the event itself, if the binding isn't broken
Output
<< eventsequence.first << ' '
<< ( event.second != nullptr ?
event.second->m_name :
event.first )
<< ' ';
}
}
if( SwitchExtension
&& SwitchExtension->fVelocity != -1.0 ) {
Output << "velocity " << SwitchExtension->fVelocity << ' ';
}
else {
if( fVelocity != -1.0 ) {
Output << "velocity " << fVelocity << ' ';
}
}
for( const TIsolated *iso : Isolated ) {
Output << "isolated " << iso->asName << ' ';
}
if( fOverhead != -1.0 ) {
Output << "overhead " << fOverhead << ' ';
}
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() };
if( texturefile.find( paths::textures ) == 0 ) {
// don't include 'textures/' in the path
texturefile.erase( 0, std::string{ paths::textures }.size() );
}
Output << "trackbed " << texturefile << ' ';
}
if( eType == tt_Switch
&& SwitchExtension->m_frogs ) {
Output << "frogs ";
}
if( eType == tt_Switch
&& SwitchExtension->m_movablenose ) {
Output << "movablenose ";
}
if( eType == tt_Switch
&& ( SwitchExtension->m_bladeheel > 0.0 || SwitchExtension->m_bladethrow > 0.0 ) ) {
Output << "blades " << SwitchExtension->m_bladeheel << ' ' << SwitchExtension->m_bladethrow << ' ';
}
if( iCategoryFlag == 1
&& false == m_profile1.first.empty() ) {
Output << "railprofile " << m_profile1.first << ' ';
}
if( false == m_friction.first.empty() ) {
Output << "friction " << m_friction.first << ' ';
}
if( m_sleeper_enabled && false == m_sleeper_model_name.empty() ) {
Output
<< "sleepermodel "
<< m_sleeper_frequency << ' '
<< m_sleeper_model_name << ' '
<< ( m_sleeper_skin_name.empty() ? std::string{ "none" } : m_sleeper_skin_name ) << ' '
<< m_sleeper_offset.x << ' '
<< m_sleeper_offset.y << ' '
<< m_sleeper_offset.z << ' '
<< m_sleeper_ballast_z << ' ';
}
if( false == m_timber_model_name.empty() ) {
Output
<< "timbermodel "
<< m_timber_model_name << ' '
<< ( m_timber_skin_name.empty() ? std::string{ "none" } : m_timber_skin_name ) << ' ';
}
// footer
Output
<< "endtrack"
<< "\n";
}
// Returns the tooltip of this Track, which may contain a list of isolations if the track belongs to any.
std::string TTrack::tooltip() const
{
std::string tooltip = this->name();
if (!Isolated.empty())
{
tooltip += "\nIsolated:";
for (const auto isolation : Isolated)
tooltip += " " + isolation->asName;
}
return tooltip;
}
// locates specified profile in the profile database, potentially loading it from a file
// returns: pair <profile name, profile handle>
void
TTrack::rail_profile( std::string const &Name ) {
m_profile1 = ( Name.empty() ? std::pair<std::string, int>{} : fetch_track_rail_profile( Name ) );
}
std::pair<std::string, int>
TTrack::fetch_track_rail_profile( std::string const &Profile ) {
auto const railprofilepath { std::string( paths::models ) + "tory/railprofile_" };
auto const railkeyprefix { std::string( "rail_" ) };
if( m_profiles.empty() ) {
// ensure the default profile is always first in the database
fetch_default_profiles();
}
// try to locate specified rail profile...
auto const lookup { m_profilesmap.find( railkeyprefix + Profile ) };
if( lookup != m_profilesmap.end() ) {
// ...if it works, we're done...
return { Profile, lookup->second };
}
// ... and if it fails try to add the profile to the database from a data file
auto profilehandle { 0 }; // fallback link to default profile if loading it fails
auto profiledata { deserialize_profile( railprofilepath + Profile ) };
if( false == profiledata.empty() ) {
// if we get the profile data add it to the database and calculate a link to it
profilehandle = static_cast<int>( m_profiles.size() );
m_profiles.emplace_back( profiledata );
}
m_profilesmap.emplace( railkeyprefix + Profile, profilehandle );
return { Profile, profilehandle };
}
void
TTrack::fetch_default_profiles() {
if( false == m_profiles.empty() ) { return; }
auto const railprofilepath { std::string( paths::models ) + "tory/railprofile_" };
auto const railkeyprefix { std::string( "rail_" ) };
m_profiles.emplace_back( deserialize_profile( railprofilepath + "default" ) );
if( m_profiles.back().empty() ) {
// fallback to prevent utter start failure, supply legacy track profile
m_profiles.back() = {
// szyna - vextor6(x,y,mapowanie tekstury,xn,yn,zn)
// tę wersję opracował Tolein (bez pochylenia)
{{ 0.111f, -0.180f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.00f, 0.f}},
{{ 0.046f, -0.150f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.15f, 0.f}},
{{ 0.044f, -0.050f, 0.f}, { 0.707f, -0.707f, 0.f}, {0.25f, 0.f}},
{{ 0.073f, -0.038f, 0.f}, { 0.707f, -0.707f, 0.f}, {0.35f, 0.f}},
{{ 0.072f, -0.010f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.40f, 0.f}},
{{ 0.052f, -0.000f, 0.f}, { 0.000f, 1.000f, 0.f}, {0.45f, 0.f}},
{{ 0.020f, -0.000f, 0.f}, { 0.000f, 1.000f, 0.f}, {0.55f, 0.f}},
{{ 0.000f, -0.010f, 0.f}, {-0.707f, 0.707f, 0.f}, {0.60f, 0.f}},
{{-0.001f, -0.038f, 0.f}, {-0.707f, -0.707f, 0.f}, {0.65f, 0.f}},
{{ 0.028f, -0.050f, 0.f}, {-0.707f, -0.707f, 0.f}, {0.75f, 0.f}},
{{ 0.026f, -0.150f, 0.f}, {-0.707f, 0.707f, 0.f}, {0.85f, 0.f}},
{{-0.039f, -0.180f, 0.f}, {-1.000f, 0.000f, 0.f}, {1.00f, 0.f}},
// iglica - vextor3(x,y,mapowanie tekstury)
// 1 mm więcej, żeby nie nachodziły tekstury?
{{ 0.010f, -0.180f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.00f, 0.f}},
{{ 0.010f, -0.155f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.15f, 0.f}},
{{ 0.010f, -0.070f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.25f, 0.f}},
{{ 0.010f, -0.040f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.35f, 0.f}},
{{ 0.010f, -0.010f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.40f, 0.f}},
{{ 0.010f, -0.000f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.45f, 0.f}},
{{ 0.000f, -0.000f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.55f, 0.f}},
{{ 0.000f, -0.010f, 0.f}, {-1.000f, 0.000f, 0.f}, {0.60f, 0.f}},
{{ 0.000f, -0.040f, 0.f}, {-1.000f, 0.000f, 0.f}, {0.65f, 0.f}},
{{ 0.000f, -0.070f, 0.f}, {-1.000f, 0.000f, 0.f}, {0.75f, 0.f}},
{{ 0.000f, -0.155f, 0.f}, {-0.707f, 0.707f, 0.f}, {0.85f, 0.f}},
{{-0.040f, -0.180f, 0.f}, {-1.000f, 0.000f, 0.f}, {1.00f, 0.f}} };
}
m_profilesmap.emplace( railkeyprefix + "default", 0 );
}
gfx::vertex_array
TTrack::deserialize_profile( std::string const &Profile ) {
gfx::vertex_array profiledata;
cParser input { Profile + ".txt", cParser::buffer_FILE };
while( input.getTokens( 5, true, "\n\r\t ;,{}" ) ) {
gfx::basic_vertex vertex;
input
>> vertex.position.x
>> vertex.position.y
>> vertex.normal.x
>> vertex.normal.y
>> vertex.texture.s;
profiledata.emplace_back( vertex );
}
return profiledata;
}
gfx::vertex_array const &
TTrack::track_rail_profile( int const Profile ) {
return m_profiles[ Profile ];
}
track_rails::rail_size
TTrack::rail_dimensions() const {
if( m_profiles.empty() ) {
return {};
}
return track_rails::measure_rail( track_rail_profile( m_profile1.second ) );
}
float
TTrack::texture_length( material_handle const Material ) {
if( Material == null_handle ) {
return fTexLength;
}
auto const texturelength { GfxRenderer->Material( Material )->GetSize().y };
return texturelength < 0.f ? fTexLength : texturelength;
}
float
TTrack::rail_height() const {
// the same height the ballast is laid at, under the rail profile
return std::abs( track_rail_profile( m_profile1.second ).front().position.y );
}
void TTrack::MovedUp1(float const dh)
{ // poprawienie przechyłki wymaga wydłużenia podsypki
fTexHeight1 += dh;
fTexHeightOffset += dh;
};
// ustawienie prędkości z ograniczeniem do pierwotnej wartości (zapisanej w scenerii)
void TTrack::VelocitySet(float v) {
// TBD, TODO: add a variable to preserve potential speed limit set by the track configuration on basic track pieces
if( SwitchExtension
&& SwitchExtension->fVelocity != -1 ) {
// zwrotnica może mieć odgórne ograniczenie, nieprzeskakiwalne eventem
fVelocity =
min_speed<float>(
v,
SwitchExtension->fVelocity > 0 ?
SwitchExtension->fVelocity : // positive limit applies to both switch tracks
SwitchExtension->CurrentIndex == 0 ?
-1 : // negative limit applies only to the diverging track
-SwitchExtension->fVelocity );
}
else {
fVelocity = v; // nie ma ograniczenia
}
};
double TTrack::VelocityGet()
{ // pobranie dozwolonej prędkości podczas skanowania
return iDamageFlag & 128 ? 0.0 : fVelocity; // tor uszkodzony = prędkość zerowa
};
float TTrack::Friction() const {
if( m_friction.second == nullptr ) {
return fFriction;
}
else {
return std::clamp( fFriction * m_friction.second->Value1() + m_friction.second->Value2(), 0.0, 1.0 );
}
}
void TTrack::ConnectionsLog()
{ // wypisanie informacji o połączeniach
int i;
WriteLog("--> tt_Cross named " + m_name);
if (eType == tt_Cross)
for (i = 0; i < 2; ++i)
{
if (SwitchExtension->pPrevs[i])
WriteLog("Point " + std::to_string(i + i + 1) + " -> track " +
SwitchExtension->pPrevs[i]->m_name + ":" +
std::to_string(int(SwitchExtension->iPrevDirection[i])));
if (SwitchExtension->pNexts[i])
WriteLog("Point " + std::to_string(i + i + 2) + " -> track " +
SwitchExtension->pNexts[i]->m_name + ":" +
std::to_string(int(SwitchExtension->iNextDirection[i])));
}
};
bool
TTrack::DoubleSlip() const {
// crude way to discern part of double slip switch:
// a switch with name ending in _a or _b or _c or _d
return iCategoryFlag == 1 && eType == tt_Switch && m_name.size() > 2 && m_name.back() >= 'a' && m_name.back() <= 'd' &&
(m_name[m_name.size() - 2] == '_' || m_name.rfind('_') != std::string::npos);
}
TTrack * TTrack::Connected(int s, double &d, int const Path) const
{ // zwraca wskaźnik na sąsiedni tor, w kierunku określonym znakiem (s), odwraca (d) w razie
// niezgodności kierunku torów
TTrack *t; // nie zmieniamy kierunku (d), jeśli nie ma toru dalej
if( eType == tt_Switch && Path >= 0 && Path < 2 ) {
// the neighbour of the path given, not of the one the switch is set to
t = ( s > 0 ? SwitchExtension->pNexts[ Path ] : SwitchExtension->pPrevs[ Path ] );
if( t != nullptr && t->eType != tt_Cross && ( s > 0 ? SwitchExtension->iNextDirection[ Path ] : !SwitchExtension->iPrevDirection[ Path ] ) ) {
d = -d;
}
return t;
}
if (eType != tt_Cross)
{ // jeszcze trzeba sprawdzić zgodność
t = s > 0 ? trNext : trPrev;
if (t) // o ile jest na co przejść, zmieniamy znak kierunku na nowym torze
if (t->eType == tt_Cross)
{ // jeśli wjazd na skrzyżowanie, trzeba ustalić segment, bo od tego zależy zmiana
// kierunku (d)
// if (r) //gdy nie podano (r), to nie zmieniać (d)
// if (s*t->CrossSegment(((s>0)?iNextDirection:iPrevDirection),r)<0)
// d=-d;
}
else
{
if (s > 0 ? iNextDirection : !iPrevDirection)
d = -d; // następuje zmiana kierunku wózka albo kierunku skanowania
// s=((s>0)?iNextDirection:iPrevDirection)?-1:1; //kierunek toru po zmianie
}
return t; // zwrotnica ma odpowiednio ustawione (trNext)
}
switch (SwitchExtension->iRoads == 4 ? iEnds4[s + 6] :
iEnds3[s + 6]) // numer końca 0..3, -1 to błąd
{ // zjazd ze skrzyżowania
case 0:
if (SwitchExtension->pPrevs[0])
if (s > 0 == SwitchExtension->iPrevDirection[0])
d = -d;
return SwitchExtension->pPrevs[0];
case 1:
if (SwitchExtension->pNexts[0])
if (s > 0 == SwitchExtension->iNextDirection[0])
d = -d;
return SwitchExtension->pNexts[0];
case 2:
if (SwitchExtension->pPrevs[1])
if (s > 0 == SwitchExtension->iPrevDirection[1])
d = -d;
return SwitchExtension->pPrevs[1];
case 3:
if (SwitchExtension->pNexts[1])
if (s > 0 == SwitchExtension->iNextDirection[1])
d = -d;
return SwitchExtension->pNexts[1];
}
return nullptr;
};
// creates rail profile data for current track
void
TTrack::create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const *Shape ) {
auto const fHTW { 0.5f * std::abs( fTrackWidth ) };
float const fHTW2{((iTrapezoid & 2) != 0 ? // ten bit oznacza, że istnieje odpowiednie pNext
0.5f * std::fabs(trNext->fTrackWidth) : // połowa rozstawu/nawierzchni
fHTW)};
float
roll1{ 0.f },
roll2{ 0.f };
profile_rolls( roll1, roll2 );
float const
sin1 { std::sin( roll1 ) },
cos1 { std::cos( roll1 ) },
sin2 { std::sin( roll2 ) },
cos2 { std::cos( roll2 ) };
auto const &railprofile { Shape != nullptr ? *Shape : track_rail_profile( m_profile1.second ) };
// NOTE: rail profile defines both regular rail and switch blade profiles, so we halve total point count
auto const pointcount { Shape != nullptr ? railprofile.size() : railprofile.size() / 2 };
Right.resize( pointcount * ( iTrapezoid == 0 ? 1 : 2 ) );
Left.resize( pointcount * ( iTrapezoid == 0 ? 1 : 2 ) );
auto const *szyna { railprofile.data() };
for( int i = 0; i < pointcount; ++i ) {
Right[ i ] = {
// position
{( fHTW + szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1,
-( fHTW + szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1,
szyna[ i ].position.z},
// normal
{ szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1,
-szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1,
szyna[ i ].normal.z },
// texture
{ szyna[ i ].texture.s,
szyna[ i ].texture.t } };
Left[ pointcount - 1 - i ] = {
// position
{(-fHTW - szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1,
-(-fHTW - szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1,
szyna[ i ].position.z},
// normal
{-szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1,
szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1,
szyna[ i ].normal.z },
// texture
{ szyna[ i ].texture.s,
szyna[ i ].texture.t } };
if( iTrapezoid == 0 ) { continue; }
// trapez albo przechyłki, to oddzielne punkty na końcu
Right[ pointcount + i ] = {
// position
{( fHTW2 + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2,
-( fHTW2 + szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2,
szyna[ i ].position.z},
// normal
{ szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2,
-szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2,
szyna[ i ].normal.z },
// texture
{ szyna[ i ].texture.s,
szyna[ i ].texture.t } };
Left[ pointcount * 2 - 1 - i ] = {
// position
{(-fHTW2 - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2,
-(-fHTW2 - szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2,
szyna[ i ].position.z},
// normal
{-szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2,
szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2,
szyna[ i ].normal.z },
// texture
{ szyna[ i ].texture.s,
szyna[ i ].texture.t } };
}
}
void
TTrack::profile_rolls( float &Roll1, float &Roll2 ) const {
Roll1 = Roll2 = 0.f;
if( eType == tt_Switch && SwitchExtension ) {
auto sum { 0.f };
for( int path = 0; path < 2; ++path ) {
float roll1, roll2;
SwitchExtension->Segments[ path ]->GetRolls( roll1, roll2 );
sum += roll1 + roll2;
}
Roll1 = Roll2 = 0.25f * sum;
return;
}
if( Segment ) {
Segment->GetRolls( Roll1, Roll2 );
}
}
// creates switch blades profile data for current track
void
TTrack::create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ) {
auto const fHTW { 0.5f * std::abs( fTrackWidth ) };
float const fHTW2 { (
( iTrapezoid & 2 ) != 0 ? // ten bit oznacza, że istnieje odpowiednie pNext
0.5f * std::fabs( trNext->fTrackWidth ) : // połowa rozstawu/nawierzchni
fHTW ) };
float
roll1 { 0.f },
roll2 { 0.f };
profile_rolls( roll1, roll2 );
float const
sin1 { std::sin( roll1 ) },
cos1 { std::cos( roll1 ) },
sin2 { std::sin( roll2 ) },
cos2 { std::cos( roll2 ) };
auto const &railprofile { track_rail_profile( m_profile1.second ) };
// NOTE: rail profile defines both regular rail and switch blade profiles, so we halve total point count
auto const pointcount { railprofile.size() / 2 };
Right.resize( pointcount * 2 );
Left.resize( pointcount * 2 );
auto const *szyna { railprofile.data() };
auto const *iglica { szyna + pointcount };
glm::vec3 const flipxvalue { -1, 1, 1 };
for( int i = 0; i < pointcount; ++i ) {
Right[ i ] = {
{+( fHTW + iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1,
-( fHTW + iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1,
0.f},
{iglica[ i ].normal},
{iglica[ i ].texture.s, 0.f} };
Right[ i + pointcount ] = {
{+( fHTW2 + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2,
-( fHTW2 + szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2,
0.f},
{szyna[ i ].normal},
{szyna[ i ].texture.s, 0.f} };
Left[ pointcount - 1 - i ] = {
{ ( -fHTW - iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1,
-( -fHTW - iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1,
0.f},
{iglica[ i ].normal * flipxvalue},
{iglica[ i ].texture.s, 0.f} };
Left[ pointcount * 2 - 1 - i ] = {
{ ( -fHTW2 - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2,
-( -fHTW2 - szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2,
0.f},
{szyna[ i ].normal * flipxvalue},
{szyna[ i ].texture.s, 0.f} };
}
}
// creates trackbed profile data for current track
void
TTrack::create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next ) {
// geometry parameters
auto * profilesource = eType != tt_Switch ? this : Previous && Previous->eType != tt_Switch ? Previous : Next && Next->eType != tt_Switch ? Next : this;
auto const texheight1 { profilesource->fTexHeight1 };
auto const texwidth { profilesource->fTexWidth };
auto const texslope { profilesource->fTexSlope };
auto const fHTW { 0.5f * std::abs( fTrackWidth ) };
auto const side { std::abs( texwidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza
auto const slop { std::abs( texslope ) }; // brzeg zewnętrzny
auto const rozp { fHTW + side + slop }; // brzeg zewnętrzny
auto hypot1 { std::hypot( slop, texheight1 ) }; // rozmiar pochylenia do liczenia normalnych
if( hypot1 == 0.f )
hypot1 = 1.f;
glm::vec3 normal1 { texheight1 / hypot1, texslope / hypot1, 0.f }; // wektor normalny
if( glm::length( normal1 ) == 0.f ) {
// fix normal for vertical surfaces
normal1 = glm::vec3 { 1.f, 0.f, 0.f };
}
glm::vec3 normal2;
float fHTW2, side2, slop2, rozp2, fTexHeight2, hypot2;
if( Next != nullptr
&& Next->eType != tt_Switch
&& ( iTrapezoid & 2 // ten bit oznacza, że istnieje odpowiednie pNext
|| eType == tt_Switch ) ) {
fHTW2 = 0.5f * std::abs(Next->fTrackWidth); // połowa rozstawu/nawierzchni
side2 = std::abs(Next->fTexWidth);
slop2 = std::abs(Next->fTexSlope); // nie jest używane później
rozp2 = fHTW2 + side2 + slop2;
fTexHeight2 = Next->fTexHeight1;
hypot2 = std::hypot(slop2, fTexHeight2);
if( hypot2 == 0.f )
hypot2 = 1.f;
normal2 = { fTexHeight2 / hypot2, Next->fTexSlope / hypot2, 0.f };
if( glm::length( normal2 ) == 0.f ) {
// fix normal for vertical surfaces
normal2 = glm::vec3 { 1.f, 0.f, 0.f };
}
}
else {
// gdy nie ma następnego albo jest nieodpowiednim końcem podpięty
fHTW2 = fHTW;
side2 = side;
slop2 = slop;
rozp2 = rozp;
fTexHeight2 = texheight1;
hypot2 = hypot1;
normal2 = normal1;
}
float
roll1{ 0.f },
roll2{ 0.f };
profile_rolls( roll1, roll2 );
float const
sin1 { std::sin( roll1 ) },
cos1 { std::cos( roll1 ) },
sin2 { std::sin( roll2 ) },
cos2 { std::cos( roll2 ) };
// profile
auto const transition { iTrapezoid != 0 || eType == tt_Switch };
auto const pointcount { transition ? 10 : 5 };
Output.resize( pointcount );
// potentially retrieve texture length override from the assigned material
auto const texturelength { texture_length( copy_adjacent_trackbed_material() ) };
auto const railheight { std::abs( track_rail_profile( m_profile1.second ).front().position.y ) };
if( texturelength == 4.f ) {
// stare mapowanie z różną gęstością pikseli i oddzielnymi teksturami na każdy profil
auto const normalx = std::cos( glm::radians( 75.f ) );
auto const normaly = std::sin( glm::radians( 75.f ) );
if( transition ) {
// trapez albo przechyłki
// ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671 ((0.3464,0.6536)
// bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu
Output[ 0 ] = {
{rozp, -texheight1 - railheight, 0.f},
{normalx, normaly, 0.f},
{0.00f, 0.f} }; // lewy brzeg
Output[ 1 ] = {
{( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - railheight, 0.f},
{normalx, normaly, 0.f},
{0.33f, 0.f} }; // krawędź załamania
Output[ 2 ] = {
{0.f, -railheight + 0.01f, 0.f},
{0.f, 1.f, 0.f},
{0.5f, 0.f} }; // middle
Output[ 3 ] = {
{-Output[ 1 ].position.x, +( fHTW + side ) * sin1 - railheight, 0.f},
{-normalx, normaly, 0.f},
{0.67f, 0.f} }; // prawy brzeg początku symetrycznie
Output[ 4 ] = {
{-rozp, -texheight1 - railheight, 0.f},
{-normalx, normaly, 0.f},
{1.f, 0.f} }; // prawy skos
// końcowy przekrój
Output[ 5 ] = {
{rozp2, -fTexHeight2 - railheight, 0.f},
{normalx, normaly, 0.f},
{0.00f, 0.f} }; // lewy brzeg
Output[ 6 ] = {
{( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - railheight, 0.f},
{normalx, normaly, 0.f},
{0.33f, 0.f} }; // krawędź załamania
Output[ 7 ] = {
{0.f, -railheight + 0.01f, 0.f},
{0.f, 1.f, 0.f},
{0.5f, 0.f} }; // middle
Output[ 8 ] = {
{-Output[ 6 ].position.x, +( fHTW2 + side2 ) * sin2 - railheight, 0.f},
{-normalx, normaly, 0.f},
{0.67f, 0.f} }; // prawy brzeg początku symetrycznie
Output[ 9 ] = {
{-rozp2, -fTexHeight2 - railheight, 0.f},
{-normalx, normaly, 0.f},
{1.00f, 0.f} }; // prawy skos
}
else {
Output[ 0 ] = {
{rozp, -texheight1 - railheight, 0.f},
{normalx, normaly, 0.f},
{0.00f, 0.f} }; // lewy brzeg
Output[ 1 ] = {
{fHTW + side, -railheight, 0.f},
{normalx, normaly, 0.f},
{0.33f, 0.f} }; // krawędź załamania
Output[ 2 ] = {
{0.f, -railheight + 0.01f, 0.f},
{0.f, 1.f, 0.f},
{0.5f, 0.f} }; // middle
Output[ 3 ] = {
{-fHTW - side, -railheight, 0.f},
{-normalx, normaly, 0.f},
{0.67f, 0.f} }; // druga
Output[ 4 ] = {
{-rozp, -texheight1 - railheight, 0.f},
{-normalx, normaly, 0.f},
{1.00f, 0.f} }; // prawy skos
}
}
else {
// mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa fTexLength
auto const max = fTexRatio2 * texturelength; // szerokość proporcjonalna do długości
auto const map11 = max > 0.f ? (fHTW + side) / max : 0.25f; // załamanie od strony 1
auto const map12 = max > 0.f ? (fHTW + side + hypot1) / max : 0.5f; // brzeg od strony 1
if (transition) {
// trapez albo przechyłki
auto const map21 = max > 0.f ? (fHTW2 + side2) / max : 0.25f; // załamanie od strony 2
auto const map22 = max > 0.f ? (fHTW2 + side2 + hypot2) / max : 0.5f; // brzeg od strony 2
// ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671
// ((0.3464,0.6536)
// bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu
Output[ 0 ] = {
{rozp, -texheight1 - railheight, 0.f},
{normal1.x, normal1.y, 0.f},
{0.5f - map12, 0.f} }; // lewy brzeg
Output[ 1 ] = {
{( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - railheight, 0.f},
{normal1.x, normal1.y, 0.f},
{0.5f - map11 , 0.f} }; // krawędź załamania
Output[ 2 ] = {
{0.f, -railheight + 0.01f, 0.f},
{0.f, 1.f, 0.f},
{0.5f, 0.f} }; // middle
Output[ 3 ] = {
{-Output[ 1 ].position.x, +( fHTW + side ) * sin1 - railheight, 0.f},
{-normal1.x, normal1.y, 0.f},
{0.5 + map11, 0.f} }; // prawy brzeg początku symetrycznie
Output[ 4 ] = {
{-rozp, -texheight1 - railheight, 0.f},
{-normal1.x, normal1.y, 0.f},
{0.5f + map12, 0.f} }; // prawy skos
// przekrój końcowy
Output[ 5 ] = {
{rozp2, -fTexHeight2 - railheight, 0.f},
{normal2.x, normal2.y, 0.f},
{0.5f - map22, 0.f} }; // lewy brzeg
Output[ 6 ] = {
{( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - railheight, 0.f},
{normal2.x, normal2.y, 0.f},
{0.5f - map21 , 0.f} }; // krawędź załamania
Output[ 7 ] = {
{0.f, -railheight + 0.01f, 0.f},
{0.f, 1.f, 0.f},
{0.5f, 0.f} }; // middle
Output[ 8 ] = {
{-Output[ 6 ].position.x, +( fHTW2 + side2 ) * sin2 - railheight, 0.f},
{-normal2.x, normal2.y, 0.f},
{0.5f + map21, 0.f} }; // prawy brzeg początku symetrycznie
Output[ 9 ] = {
{-rozp2, -fTexHeight2 - railheight, 0.f},
{-normal2.x, normal2.y, 0.f},
{0.5f + map22, 0.f} }; // prawy skos
}
else
{
Output[ 0 ] = {
{rozp, -texheight1 - railheight, 0.f},
{+normal1.x, normal1.y, 0.f},
{0.5f - map12, 0.f} }; // lewy brzeg
Output[ 1 ] = {
{fHTW + side, - railheight, 0.f},
{+normal1.x, normal1.y, 0.f},
{0.5f - map11, 0.f} }; // krawędź załamania
Output[ 2 ] = {
{0.f, -railheight + 0.01f, 0.f},
{0.f, 1.f, 0.f},
{0.5f, 0.f} }; // middle
Output[ 3 ] = {
{-fHTW - side, - railheight, 0.f},
{-normal1.x, normal1.y, 0.f},
{0.5f + map11, 0.f} }; // druga
Output[ 4 ] = {
{-rozp, -texheight1 - railheight, 0.f},
{-normal1.x, normal1.y, 0.f},
{0.5f + map12, 0.f} }; // prawy skos
}
}
if( m_sleepers3d.active ) {
// the ballast lowered under the sleepers laid on it, so they stick out of it
for( auto &point : Output ) {
point.position.y -= static_cast<float>( track_sleepers::bed_drop );
}
}
}
// creates road profile data for current path
void
TTrack::create_road_profile( gfx::vertex_array &Output, bool const Forcetransition ) {
auto const fHTW { 0.5f * std::abs( fTrackWidth ) };
float const fHTW2 { (
( iTrapezoid & 2 ) != 0 ? // ten bit oznacza, że istnieje odpowiednie pNext
0.5f * std::fabs( trNext->fTrackWidth ) : // połowa rozstawu/nawierzchni
fHTW ) };
glm::vec3 const normalup { 0.f, 1.f, 0.f };
auto const texturelength { texture_length( m_material1 ) };
auto const max = fTexRatio1 * texturelength; // test: szerokość proporcjonalna do długości
auto const map1 = max > 0.f ? fHTW / max : 0.5f; // obcięcie tekstury od strony 1
auto const map2 = max > 0.f ? fHTW2 / max : 0.5f; // obcięcie tekstury od strony 2
auto const transition { true == Forcetransition || iTrapezoid != 0 };
auto const pointcount{ transition ? 4 : 2 };
Output.resize( pointcount );
if( transition ) {
// trapez albo przechyłki
float
roll1 { 0.f },
roll2 { 0.f };
if( Segment ) {
Segment->GetRolls( roll1, roll2 );
}
Output[ 0 ] = {
{fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f},
normalup,
{0.5f - map1, 0.f} }; // lewy brzeg początku
Output[ 1 ] = {
{-Output[ 0 ].position.x, -Output[ 0 ].position.y, 0.f},
normalup,
{0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie
Output[ 2 ] = {
{fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f},
normalup,
{0.5f - map2, 0.f} }; // lewy brzeg końca
Output[ 3 ] = {
{-Output[ 2 ].position.x, -Output[ 2 ].position.y, 0.f},
normalup,
{0.5f + map2, 0.f} }; // prawy brzeg początku symetrycznie
}
else {
Output[ 0 ] = {
{fHTW, 0.f, 0.f},
normalup,
{0.5f - map1, 0.f} };
Output[ 1 ] = {
{-fHTW, 0.f, 0.f},
normalup,
{0.5f + map1, 0.f} };
}
}
void
TTrack::create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition ) {
auto const fHTW{ 0.5f * std::abs( fTrackWidth ) };
auto const side{ std::abs( fTexWidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza
auto const slop{ std::abs( fTexSlope ) }; // brzeg zewnętrzny
auto const rozp{ fHTW + side + slop }; // brzeg zewnętrzny
float fHTW2, side2, slop2, rozp2, fTexHeight2;
if( iTrapezoid & 2 ) {
// ten bit oznacza, że istnieje odpowiednie pNext
// Ra: jest OK
fHTW2 = 0.5f * std::fabs( trNext->fTrackWidth ); // połowa rozstawu/nawierzchni
side2 = std::fabs( trNext->fTexWidth );
slop2 = std::fabs( trNext->fTexSlope ); // nie jest używane później
rozp2 = fHTW2 + side2 + slop2;
fTexHeight2 = trNext->fTexHeight1;
}
else {
// gdy nie ma następnego albo jest nieodpowiednim końcem podpięty
fHTW2 = fHTW;
side2 = side;
slop2 = slop;
rozp2 = rozp;
fTexHeight2 = fTexHeight1;
}
glm::vec3 const normalup{ 0.f, 1.f, 0.f };
auto const texturelength{ texture_length( m_material2 ) };
auto const transition { true == Forcetransition || iTrapezoid != 0 };
auto const pointcount{ transition ? 6 : 3 };
Right.resize( pointcount );
Left.resize( pointcount );
if( fTexHeight1 >= 0.f ) { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo
Right[ 0 ] = {
{rozp, -fTexHeight1, 0.f},
{ 1.f, 0.f, 0.f },
{0.f, 0.f} }; // lewy brzeg podstawy
Right[ 1 ] = {
{Road[ 0 ].position.x + side, Road[ 0 ].position.y, 0.f},
normalup,
{0.5, 0.f} }; // lewa krawędź załamania
Right[ 2 ] = {
{Road[ 0 ].position.x, Road[ 0 ].position.y, 0.f},
normalup,
{1.f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne
Left[ 0 ] = {
{Road[ 1 ].position.x, Road[ 1 ].position.y, 0.f},
normalup,
{1.f, 0.f} }; // prawy brzeg pobocza
Left[ 1 ] = {
{Road[ 1 ].position.x - side, Road[ 1 ].position.y, 0.f},
normalup,
{0.5f, 0.f} }; // prawa krawędź załamania
Left[ 2 ] = {
{-rozp, -fTexHeight1, 0.f},
{ -1.f, 0.f, 0.f },
{0.f, 0.f} }; // prawy brzeg podstawy
if( transition ) {
// pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka
Right[ 3 ] = {
{rozp2, -fTexHeight2, 0.f},
{ 1.f, 0.f, 0.f },
{0.f, 0.f} }; // lewy brzeg lewego pobocza
Right[ 4 ] = {
{Road[ 2 ].position.x + side2, Road[ 2 ].position.y, 0.f},
normalup,
{0.5f, 0.f} }; // krawędź załamania
Right[ 5 ] = {
{Road[ 2 ].position.x, Road[ 2 ].position.y, 0.f},
normalup,
{1.f, 0.f} }; // brzeg pobocza
Left[ 3 ] = {
{Road[ 3 ].position.x, Road[ 3 ].position.y, 0.f},
normalup,
{1.f, 0.f} };
Left[ 4 ] = {
{Road[ 3 ].position.x - side2, Road[ 3 ].position.y, 0.f},
normalup,
{0.5f, 0.f} };
Left[ 5 ] = {
{-rozp2, -fTexHeight2, 0.f},
{ -1.f, 0.f, 0.f },
{0.f, 0.f} }; // prawy brzeg prawego pobocza
}
}
else { // wersja dla chodnika: skos 1:3.75, każdy chodnik innej szerokości
// mapowanie propocjonalne do szerokości chodnika
// krawężnik jest mapowany od 31/64 do 32/64 lewy i od 32/64 do 33/64 prawy
auto const d = -fTexHeight1 / 3.75f; // krawężnik o wysokości 150mm jest pochylony 40mm
auto const max = fTexRatio2 * texturelength; // test: szerokość proporcjonalna do długości
auto const map1l = max > 0.f ? side / max : 0.484375f; // obcięcie tekstury od lewej strony punktu 1
auto const map1r = max > 0.f ? slop / max : 0.484375f; // obcięcie tekstury od prawej strony punktu 1
auto const h1r = slop > d ? -fTexHeight1 : 0.f;
auto const h1l = side > d ? -fTexHeight1 : 0.f;
Right[ 0 ] = {
{Road[ 0 ].position.x + slop, Road[ 0 ].position.y + h1r, 0.f},
normalup,
{0.515625f + map1r, 0.f} }; // prawy brzeg prawego chodnika
Right[ 1 ] = {
{Road[ 0 ].position.x + d, Road[ 0 ].position.y + h1r, 0.f},
normalup,
{0.515625f, 0.f} }; // prawy krawężnik u góry
Right[ 2 ] = {
{Road[ 0 ].position.x, Road[ 0 ].position.y, 0.f},
{ -1.f, 0.f, 0.f },
{0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu
Left[ 0 ] = {
{Road[ 1 ].position.x, Road[ 1 ].position.y, 0.f},
{ 1.f, 0.f, 0.f },
{0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu
Left[ 1 ] = {
{Road[ 1 ].position.x - d, Road[ 1 ].position.y + h1l, 0.f},
normalup,
{0.484375f, 0.f} }; // lewy krawężnik u góry
Left[ 2 ] = {
{Road[ 1 ].position.x - side, Road[ 1 ].position.y + h1l, 0.f},
normalup,
{0.484375f - map1l, 0.f} }; // lewy brzeg lewego chodnika
if( transition ) {
// pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka
slop2 = std::fabs(iTrapezoid & 2 ? slop2 : slop); // szerokość chodnika po prawej
auto const map2l = max > 0.f ? side2 / max : 0.484375f; // obcięcie tekstury od lewej strony punktu 2
auto const map2r = max > 0.f ? slop2 / max : 0.484375f; // obcięcie tekstury od prawej strony punktu 2
auto const h2r = slop2 > d ? -fTexHeight2 : 0.f;
auto const h2l = side2 > d ? -fTexHeight2 : 0.f;
Right[ 3 ] = {
{Road[ 2 ].position.x + slop2, Road[ 2 ].position.y + h2r, 0.f},
normalup,
{0.515625f + map2r, 0.f} }; // prawy brzeg prawego chodnika
Right[ 4 ] = {
{Road[ 2 ].position.x + d, Road[ 2 ].position.y + h2r, 0.f},
normalup,
{0.515625f, 0.f} }; // prawy krawężnik u góry
Right[ 5 ] = {
{Road[ 2 ].position.x, Road[ 2 ].position.y, 0.f},
{ -1.f, 0.f, 0.f },
{0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu
Left[ 3 ] = {
{Road[ 3 ].position.x, Road[ 3 ].position.y, 0.f},
{ 1.f, 0.f, 0.f },
{0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu
Left[ 4 ] = {
{Road[ 3 ].position.x - d, Road[ 3 ].position.y + h2l, 0.f},
normalup,
{0.484375f, 0.f} }; // lewy krawężnik u góry
Left[ 5 ] = {
{Road[ 3 ].position.x - side2, Road[ 3 ].position.y + h2l, 0.f},
normalup,
{0.484375f - map2l, 0.f} }; // lewy brzeg lewego chodnika
}
}
}
void
TTrack::create_switch_trackbed( gfx::vertex_array &Output ) {
// try to get trackbed material from a regular track connected to the primary path
if( SwitchExtension->m_material3 == null_handle && trPrev != nullptr
&& trPrev->eType == tt_Normal ) {
SwitchExtension->m_material3 = trPrev->m_material2;
}
if( SwitchExtension->m_material3 == null_handle && trNext != nullptr
&& trNext->eType == tt_Normal ) {
SwitchExtension->m_material3 = trNext->m_material2;
}
// without material don't bother, nor when the ballast isn't laid under the 3D sleepers
if( SwitchExtension->m_material3 == null_handle || ( m_sleepers3d.active && false == Global.Sleepers3DBed ) ) { return; }
// generate trackbed for each path of the switch...
auto const texturelength { texture_length( SwitchExtension->m_material3 ) };
gfx::vertex_array trackbedprofile;
gfx::vertex_array trackbedvertices1, trackbedvertices2;
// main trackbed
create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 0 ], SwitchExtension->pNexts[ 0 ] );
SwitchExtension->Segments[ 0 ]->RenderLoft( trackbedvertices1, m_origin, trackbedprofile, true, texturelength );
// side trackbed
create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 1 ], SwitchExtension->pNexts[ 1 ] );
SwitchExtension->Segments[ 1 ]->RenderLoft( trackbedvertices2, m_origin, trackbedprofile, true, texturelength );
if( trackbedvertices1.size() != trackbedvertices2.size() ) {
// a path made of several curves has more pieces than the other one, and the two are combined piece by piece,
// so both are lofted again in the same number of equal pieces, else the trackbed would end where the shorter count does
auto const steps { std::max( SwitchExtension->Segments[ 0 ]->RaSegCount(), SwitchExtension->Segments[ 1 ]->RaSegCount() ) };
trackbedvertices1.clear();
trackbedvertices2.clear();
create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 0 ], SwitchExtension->pNexts[ 0 ] );
SwitchExtension->Segments[ 0 ]->RenderLoftSteps( trackbedvertices1, m_origin, trackbedprofile, texturelength, steps );
create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 1 ], SwitchExtension->pNexts[ 1 ] );
SwitchExtension->Segments[ 1 ]->RenderLoftSteps( trackbedvertices2, m_origin, trackbedprofile, texturelength, steps );
}
// ...then combine them into a single geometry sequence
auto const segmentsize { 10 };
auto const segmentcount { std::min( trackbedvertices1.size(), trackbedvertices2.size() ) / segmentsize };
auto *sampler1 { trackbedvertices1.data() };
auto *sampler2 { trackbedvertices2.data() };
auto const isright { SwitchExtension->RightSwitch };
auto const isleft { false == isright };
auto const samplersoffset { isright ? 2 : 0 };
auto const geometryoffset { 0.025f };
for( int segment = 0; segment < segmentcount; ++segment ) {
// main trackbed
// lower outer edge to avoid z-fighting
if( isright ) {
( sampler1 + samplersoffset + 0 )->position.y -= geometryoffset;
( sampler1 + samplersoffset + 1 )->position.y -= geometryoffset;
}
if( isleft ) {
( sampler1 + samplersoffset + 6 )->position.y -= geometryoffset;
( sampler1 + samplersoffset + 7 )->position.y -= geometryoffset;
}
// copy the data
for( auto pointidx = 0; pointidx < segmentsize; ++pointidx ) {
Output.emplace_back( *( sampler1 + pointidx ) );
}
// side trackbed
// lower outer edge to avoid z-fighting
if( isleft ) {
( sampler2 - samplersoffset + 2 )->position.y -= geometryoffset;
( sampler2 - samplersoffset + 3 )->position.y -= geometryoffset;
}
if( isright ) {
( sampler2 - samplersoffset + 8 )->position.y -= geometryoffset;
( sampler2 - samplersoffset + 9 )->position.y -= geometryoffset;
}
// copy the data
for( auto pointidx = 0; pointidx < segmentsize; ++pointidx ) {
Output.emplace_back( *( sampler2 + pointidx ) );
}
// switch to next segment data
sampler1 += segmentsize;
sampler2 += segmentsize;
}
}
material_handle
TTrack::copy_adjacent_trackbed_material( TTrack const *Exclude ) {
if( iCategoryFlag != 1 ) { return null_handle; } // tracks only
auto &material { eType == tt_Switch ? SwitchExtension->m_material3 : m_material2 };
if( material != null_handle ) { return material; } // already has material
std::vector<TTrack *> adjacents;
switch( eType ) {
case tt_Normal: {
// for regular tracks don't set the trackbed texture if we aren't sitting next to a part of a double slip
/*
auto const hasadjacentdoubleslip {
( trPrev ? trPrev->DoubleSlip() : false )
|| ( trNext ? trNext->DoubleSlip() : false ) };
if( true == hasadjacentdoubleslip ) {
adjacents.emplace_back( trPrev );
adjacents.emplace_back( trNext );
}
*/
auto const hasadjacentswitch {
( trPrev && trPrev->eType == tt_Switch )
|| ( trNext && trNext->eType == tt_Switch ) };
// if( true == hasadjacentdoubleslip ) {
if( true == hasadjacentswitch ) {
adjacents.emplace_back( trPrev );
adjacents.emplace_back( trNext );
}
break;
}
case tt_Switch: {
// only check the neighbours of the main track
adjacents.emplace_back( SwitchExtension->pPrevs[ 0 ] );
adjacents.emplace_back( SwitchExtension->pNexts[ 0 ] );
break;
}
default: {
break;
}
}
for( auto *adjacent : adjacents ) {
if( adjacent != nullptr && adjacent != Exclude ) {
material = adjacent->copy_adjacent_trackbed_material( this );
if( material != null_handle ) { break; } // got what we wanted
}
}
return material;
}
path_table::~path_table() {
TIsolated::DeleteAll();
}
// legacy method, initializes tracks after deserialization from scenario file
void
path_table::InitTracks() {
int connection { -1 };
TTrack *matchingtrack { nullptr };
#ifdef EU07_IGNORE_LEGACYPROCESSINGORDER
for( auto *track : m_items ) {
#else
// NOTE: legacy code peformed item operations last-to-first due to way the items were added to the list
// this had impact in situations like two possible connection candidates, where only the first one would be used
for( auto first = std::rbegin(m_items); first != std::rend(m_items); ++first ) {
auto *track = *first;
#endif
track->AssignEvents();
auto const trackname { track->name() };
switch (track->eType) {
case tt_Table: {
// obrotnicę też łączymy na starcie z innymi torami
// szukamy modelu o tej samej nazwie
auto *instance = simulation::Instances.find( trackname );
// wiązanie toru z modelem obrotnicy
track->RaAssign(
instance,
simulation::Events.FindEvent( trackname + ":done" ),
simulation::Events.FindEvent( trackname + ":joined" ) );
if( instance == nullptr ) {
// jak nie ma modelu to pewnie jest wykolejnica, a ta jest domyślnie zamknięta i wykoleja
break;
}
// no break on purpose:
// "jak coś pójdzie źle, to robimy z tego normalny tor"
}
case tt_Normal: {
// tylko proste są podłączane do rozjazdów, stąd dwa rozjazdy się nie połączą ze sobą
if( track->CurrentPrev() == nullptr ) {
// tylko jeśli jeszcze nie podłączony
std::tie( matchingtrack, connection ) = simulation::Region->find_path( track->CurrentSegment()->FastGetPoint_0(), track );
switch( connection ) {
case -1: // Ra: pierwsza koncepcja zawijania samochodów i statków
// if ((Track->iCategoryFlag&1)==0) //jeśli nie jest torem szynowym
// Track->ConnectPrevPrev(Track,0); //łączenie końca odcinka do samego siebie
break;
case 0:
track->ConnectPrevPrev( matchingtrack, 0 );
break;
case 1:
track->ConnectPrevNext( matchingtrack, 1 );
break;
case 2:
track->ConnectPrevPrev( matchingtrack, 0 ); // do Point1 pierwszego
matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie
break;
case 3:
track->ConnectPrevNext( matchingtrack, 1 ); // do Point2 pierwszego
matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie
break;
case 4:
matchingtrack->Switch( 1 );
track->ConnectPrevPrev( matchingtrack, 2 ); // do Point1 drugiego
matchingtrack->SetConnections( 1 ); // robi też Switch(0)
matchingtrack->Switch( 0 );
break;
case 5:
matchingtrack->Switch( 1 );
track->ConnectPrevNext( matchingtrack, 3 ); // do Point2 drugiego
matchingtrack->SetConnections( 1 ); // robi też Switch(0)
matchingtrack->Switch( 0 );
break;
default:
break;
}
}
if( track->CurrentNext() == nullptr ) {
// tylko jeśli jeszcze nie podłączony
std::tie( matchingtrack, connection ) = simulation::Region->find_path( track->CurrentSegment()->FastGetPoint_1(), track );
switch( connection ) {
case -1: // Ra: pierwsza koncepcja zawijania samochodów i statków
// if ((Track->iCategoryFlag&1)==0) //jeśli nie jest torem szynowym
// Track->ConnectNextNext(Track,1); //łączenie końca odcinka do samego siebie
break;
case 0:
track->ConnectNextPrev( matchingtrack, 0 );
break;
case 1:
track->ConnectNextNext( matchingtrack, 1 );
break;
case 2:
track->ConnectNextPrev( matchingtrack, 0 );
matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie
break;
case 3:
track->ConnectNextNext( matchingtrack, 1 );
matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie
break;
case 4:
matchingtrack->Switch( 1 );
track->ConnectNextPrev( matchingtrack, 2 );
matchingtrack->SetConnections( 1 ); // robi też Switch(0)
// tmp->Switch(0);
break;
case 5:
matchingtrack->Switch( 1 );
track->ConnectNextNext( matchingtrack, 3 );
matchingtrack->SetConnections( 1 ); // robi też Switch(0)
// tmp->Switch(0);
break;
default:
break;
}
}
break;
}
case tt_Switch: {
// dla rozjazdów szukamy eventów sygnalizacji rozprucia
track->AssignForcedEvents(
simulation::Events.FindEvent( trackname + ":forced+" ),
simulation::Events.FindEvent( trackname + ":forced-" ) );
break;
}
default: {
break;
}
} // switch
if( trackname[0] == '*'
&& !track->CurrentPrev() && track->CurrentNext() ) {
// możliwy portal, jeśli nie podłączony od strony 1
// ustawienie flagi portalu
track->iCategoryFlag |= 0x100;
}
{
auto const inputcellname { track->m_friction.first };
if( false == inputcellname.empty() ) {
track->m_friction.second = simulation::Memory.find( inputcellname );
if( track->m_friction.second == nullptr ) {
ErrorLog( "Bad track: " + ( trackname.empty() ? "unnamed track" : "\"" + trackname + "\"" ) + " can't find assigned memory cell \"" + inputcellname + "\"" );
}
}
}
}
if( Global.CreateSwitchTrackbeds ) {
// do this after all connections are established, otherwise missing switch connections
// may prevent us from obtaining texture data for basic track from 'across' a switch
for( auto *track : m_items ) {
// try to assign missing trackbed materials for switches and tracks neighbouring switches
switch( track->eType ) {
case tt_Normal:
case tt_Switch: {
track->copy_adjacent_trackbed_material();
break;
}
default: {
break;
}
} // switch
}
}
auto *isolated = TIsolated::Root();
while( isolated ) {
isolated->AssignEvents();
auto *memorycell = simulation::Memory.find( isolated->asName ); // czy jest komóka o odpowiedniej nazwie
if( memorycell == nullptr ) {
// utworzenie automatycznej komórki
// TODO: determine suitable location for this one, create and add world reference node
scene::node_data nodedata;
nodedata.name = isolated->asName;
memorycell = new TMemCell( nodedata ); // to nie musi mieć nazwy, nazwa w wyszukiwarce wystarczy
// NOTE: autogenerated cells aren't exported; they'll be autogenerated anew when exported file is loaded
memorycell->is_exportable = false;
simulation::Memory.insert( memorycell );
}
// przypisanie powiązanej komórki
isolated->pMemCell = memorycell;
isolated = isolated->Next();
}
TTrack::fetch_default_profiles();
}
// legacy method, sends list of occupied paths over network
void
path_table::TrackBusyList() const {
// wysłanie informacji o wszystkich zajętych odcinkach
for( auto const *path : m_items ) {
if( false == path->name().empty() // musi być nazwa
&& false == path->Dynamics.empty() ) {
// zajęty
multiplayer::WyslijString( path->name(), 8 );
}
}
}
// legacy method, sends list of occupied path sections over network
void
path_table::IsolatedBusyList() const {
// wysłanie informacji o wszystkich odcinkach izolowanych
TIsolated *Current;
for( Current = TIsolated::Root(); Current; Current = Current->Next() ) {
if( Current->Busy() ) { multiplayer::WyslijString( Current->asName, 11 ); }
else { multiplayer::WyslijString( Current->asName, 10 ); }
}
multiplayer::WyslijString( "none", 10 ); // informacja o końcu listy
}
// legacy method, sends state of specified path section over network
void
path_table::IsolatedBusy( std::string const &Name ) const {
// wysłanie informacji o odcinku izolowanym (t)
// Ra 2014-06: do wyszukania użyć drzewka nazw
TIsolated *Current;
for( Current = TIsolated::Root(); Current; Current = Current->Next() ) {
if( Current->asName == Name ) {
if( Current->Busy() ) { multiplayer::WyslijString( Current->asName, 11 ); }
else { multiplayer::WyslijString( Current->asName, 10 ); }
// nie sprawdzaj dalszych
return;
}
}
multiplayer::WyslijString( Name, 10 ); // wolny (technically not found but, eh)
}
namespace {
// Returns the list of segments to walk when laying out sleepers for a given track.
// For plain tracks/turntables we just use the active Segment. For switches, crossings
// and tributaries we use every initialised sub-path so the user gets sleepers covering
// the full footprint of the junction (not just the currently-selected route).
std::vector<TSegment *> sleeper_segments_for( TTrack const &Track )
{
std::vector<TSegment *> out;
switch( Track.eType ) {
case tt_Switch:
case tt_Tributary: {
// both main + diverging branches
if( Track.SwitchExtension ) {
for( int i = 0; i < 2; ++i ) {
auto *seg = Track.SwitchExtension->Segments[ i ].get();
if( seg != nullptr ) { out.push_back( seg ); }
}
}
break;
}
case tt_Cross: {
// a road crossing potentially holds up to 6 connection segments; iterate them all,
// skipping zero-length / null entries.
if( Track.SwitchExtension ) {
for( int i = 0; i < 6; ++i ) {
auto *seg = Track.SwitchExtension->Segments[ i ].get();
if( seg == nullptr ) { continue; }
if( seg->GetLength() <= 0.0 ) { continue; }
out.push_back( seg );
}
}
break;
}
case tt_Normal:
case tt_Table:
default: {
if( Track.Segment ) { out.push_back( Track.Segment.get() ); }
break;
}
}
return out;
}
// Local-space transform (relative to Origin) of the sleeper at distance S along the Segment,
// or none at a degenerate spot (e.g. zero-length sub-segment) where no tangent can be taken.
// Eps is the half-span of the central difference used to extract the tangent.
std::optional<glm::mat4> sleeper_transform( TSegment const &Segment, double const S, double const Eps, glm::dvec3 const &Origin, glm::vec3 const &Offset )
{
glm::dvec3 pos;
glm::vec3 angles;
Segment.RaPositionGet( S, pos, angles );
// tangent direction via central difference (clamped to the segment endpoints)
glm::dvec3 p_back;
glm::dvec3 p_fwd;
glm::vec3 dummy;
Segment.RaPositionGet( std::max( 0.0, S - Eps ), p_back, dummy );
Segment.RaPositionGet( std::min( Segment.GetLength(), S + Eps ), p_fwd, dummy );
auto tangent = glm::vec3( p_fwd - p_back );
if( glm::length2( tangent ) < 1e-8f ) {
// skip this position rather than emit a junk transform with NaN normals.
return std::nullopt;
}
tangent = glm::normalize( tangent );
// build an orthonormal basis around the tangent. world up is the reference; if the
// track is almost vertical we fall back to world X so cross() doesn't collapse.
glm::vec3 up_ref { 0.f, 1.f, 0.f };
if( std::abs( glm::dot( tangent, up_ref ) ) > 0.999f ) { up_ref = glm::vec3( 1.f, 0.f, 0.f ); }
glm::vec3 side = glm::normalize( glm::cross( up_ref, tangent ) );
glm::vec3 up = glm::cross( tangent, side );
// apply track roll (banking) around the tangent / forward axis.
if( float const roll = angles.x; roll != 0.f ) {
auto const roll_mat = glm::rotate( glm::mat4( 1.f ), roll, tangent );
side = glm::vec3( roll_mat * glm::vec4( side, 0.f ) );
up = glm::vec3( roll_mat * glm::vec4( up, 0.f ) );
}
// assemble local transform: columns are (right, up, forward, translation).
// a sleeper modelled with X = sideways, Y = up, Z = along-track now ends up
// correctly oriented along the path tangent regardless of curve direction.
auto const localpos = glm::vec3( pos - Origin );
glm::mat4 m { 1.f };
m[ 0 ] = glm::vec4( side, 0.f );
m[ 1 ] = glm::vec4( up, 0.f );
m[ 2 ] = glm::vec4( tangent, 0.f );
m[ 3 ] = glm::vec4( localpos, 1.f );
if( Offset != glm::vec3( 0.f ) ) {
m = glm::translate( m, Offset );
}
return m;
}
} // anonymous namespace
// Resolves the sleeper model + (optional) replacable skin via the global model/material
// managers, then walks every active sub-segment at the configured spacing and bakes a
// local-space transform matrix (relative to m_origin) for every instance. The renderer
// turns these into final camera-space modelview matrices at draw time.
//
// Per-instance orientation is built from an explicit tangent-based basis (right, up, forward)
// rather than RPY-decomposed angles, so curves and switches stay aligned with the path
// regardless of where they live in the parameter space.
void TTrack::build_sleeper_transforms()
{
m_sleeper_local_transforms.clear();
m_sleeper_model = nullptr;
m_sleeper_skin = 0;
if( false == m_sleeper_enabled ) { return; }
if( m_sleeper_model_name.empty() ) { return; }
if( m_sleeper_frequency <= 0.01f ) { return; }
auto const segments = sleeper_segments_for( *this );
if( segments.empty() ) { return; }
// resolve model
m_sleeper_model = TModelsManager::GetModel( m_sleeper_model_name, false );
if( m_sleeper_model == nullptr ) {
ErrorLog( "Bad track: sleepermodel model \"" + m_sleeper_model_name + "\" failed to load for track \"" + m_name + "\"" );
m_sleeper_enabled = false;
return;
}
// resolve replacable skin (optional)
if( false == m_sleeper_skin_name.empty() && m_sleeper_skin_name != "none" ) {
auto skinpath { m_sleeper_skin_name };
replace_slashes( skinpath );
m_sleeper_skin = GfxRenderer->Fetch_Material( skinpath );
}
auto const spacing = static_cast<double>( m_sleeper_frequency );
// small finite-difference epsilon used to extract the tangent from RaPositionGet.
// RaPositionGet's reported angles are correct in principle but for curves they're
// derived from the polynomial first derivative, which is sensitive to numerical noise
// at the segment endpoints. Sampling positions directly is robust for both straight
// segments and bezier curves, and it costs us two extra evaluations per sleeper.
double const eps = std::min( 0.1, spacing * 0.25 );
// user offset is (left/right, forward/back, up/down) in the local frame established by
// the basis below (x=right, y=up, z=forward). swap y<->z to match the documented axes.
glm::vec3 const local_offset { m_sleeper_offset.x, m_sleeper_offset.z, m_sleeper_offset.y };
for( auto const *segment : segments ) {
auto const length = segment->GetLength();
if( length <= 0.0 ) { continue; }
// start half a frequency in so the first sleeper doesn't sit on the joint.
auto const start = std::min( spacing * 0.5, length * 0.5 );
auto const expected = static_cast<std::size_t>( std::max( 0.0, ( length - start ) / spacing ) ) + 1u;
m_sleeper_local_transforms.reserve( m_sleeper_local_transforms.size() + expected );
for( std::size_t sleeperidx = 0;; ++sleeperidx ) {
auto const s = start + static_cast<double>( sleeperidx ) * spacing;
if( s >= length ) { break; }
if( auto const transform = sleeper_transform( *segment, s, eps, m_origin, local_offset ) ) {
m_sleeper_local_transforms.emplace_back( *transform );
}
}
}
}