17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 06:19:19 +02:00
Files
maszyna/world/Track.cpp
maj00r c15e200e37 Merge the track layout editor into cpp20-modules, ported to modules
The editor branch was written against headers and the PCH, both of which this
branch has done away with, so its own files were ported rather than merged: the
five headers it added are now interface units and their .cpp files module
implementation units.

- editor/{puwg1992,polandmap,turnouts,orthophoto} become eu07.editor.* modules,
  application/planpanel becomes eu07.application.planpanel. The plan tool keeps
  to its own module rather than joining the eu07.simcore partitions: it stands on
  the editor's libraries and eu07.application.uilayer, not on the simulation core,
  so :editoruilayer can export import it without a cycle.
- glm arrives by import eu07.glm, never by include, as the rest of the tree does.
  maj0sted is header-only and glm-free, so its headers stay includes in the global
  module fragment - every unit that names those types includes them itself, since
  a global fragment is not visible across units.
- the one textual conflict was :editoruilayer's include block against this
  branch's imports; resolved this branch's way plus the planpanel import.

Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF) and starts into the editor on
empty ground with -editor alone.
2026-09-13 12:36:21 +02:00

3766 lines
160 KiB
C++

module;
#include <array>
#include <chrono>
#include <mutex>
#include <sstream>
#include <type_traits>
#include <deque>
#include <iterator>
#include <unordered_map>
#include <memory>
#include "glad/glad.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <istream>
#include <limits>
#include <ostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "global_include/interfaces/ITexture_macros.h"
#include "utilities/Globals_macros.h"
#include "vehicle/DynObj_macros.h"
module eu07.simcore;
import eu07.simulation.simulation;
import eu07.rendering.renderer;
import eu07.input.messaging;
import eu07.model.mdlmngr;
import eu07.utilities.utilities;
import eu07.utilities.globals;
import eu07.utilities.timer;
/*
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
*/
// 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;
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 ) {}
TTrack::~TTrack()
{ // likwidacja odcinka
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
glm::dvec3 pt, vec, p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań
double a1, a2, r1, r2, r3, r4;
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
double segsize = 5.0; // długość odcinka segmentowania
// 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;
}
}
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
}
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( 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( 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
}
}
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;
}
}
// 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 == "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
ErrorLog("Bad track: unknown property: \"" + str + "\" defined for track \"" + m_name + "\"");
parser->getTokens();
*parser >> token;
str = token;
}
// 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
}
// calculate path location
location( (
CurrentSegment()->FastGetPoint_0()
+ CurrentSegment()->FastGetPoint( 0.5 )
+ CurrentSegment()->FastGetPoint_1() )
/ 3.0 );
// 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).
}
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::create_geometry( gfx::geometrybank_handle const &Bank ) {
gfx::userdata_array empty_userdata;
// 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 );
switch (eType) // dalej zależnie od typu
{
case tt_Table: // obrotnica jak zwykły tor, tylko animacja dochodzi
case tt_Normal:
if (m_material2)
{ // 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(GfxRenderer->Insert(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;
if( Bank != 0 && true == Geometry1.empty() ) {
Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear(); // reuse the scratchpad
Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(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 bladelength { static_cast<int>( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) ) };
// 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
if (SwitchExtension->RightSwitch)
{ // nowa wersja z SPKS, ale odwrotnie lewa/prawa
gfx::vertex_array vertices;
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
if( jointlength > 0 ) {
// part of the diverging rail touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, 0, jointlength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
// other rail, full length
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// diverging rail, potentially minus part touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, jointlength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
}
else
{ // lewa działa lepiej niż prawa
gfx::vertex_array vertices;
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
// prawa szyna za iglicą
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
if( jointlength > 0 ) {
// part of the diverging rail touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, 0, jointlength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
// other rail, full length
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength );
Geometry1.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// fixed parts
// lewa szyna za iglicą
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
// diverging rail, potentially minus part touched by wheels of vehicle going straight
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, jointlength );
Geometry2.emplace_back(GfxRenderer->Insert(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
vertices.clear();
}
}
}
// auto-generated switch trackbed
if( true == Global.CreateSwitchTrackbeds ) {
gfx::vertex_array vertices;
create_switch_trackbed( vertices );
SwitchExtension->Geometry3 = GfxRenderer->Insert(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;
};
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
}
}
else if (eType == tt_Cross)
{ // ustawienie wskaźnika na wskazany segment
Segment = SwitchExtension->Segments[i];
}
return true;
};
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
}
}
};
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
}
}
// 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 bladelength { static_cast<int>( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) ) };
auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 };
if (SwitchExtension->RightSwitch)
{ // nowa wersja z SPKS, ale odwrotnie lewa/prawa
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry2[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
}
else { // lewa działa lepiej niż prawa
if( m_material1 ) {
auto const texturelength { texture_length( m_material1 ) };
// right blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } );
SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry1[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
if( m_material2 ) {
auto const texturelength { texture_length( m_material2 ) };
// left blade
// composed from two parts: transition from blade to regular rail, and regular rail
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } );
SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } );
GfxRenderer->Replace(vertices, empty_userdata, Geometry2[0], GL_TRIANGLE_STRIP);
vertices.clear();
}
}
}
}
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 == 1 ? "normal" :
iCategoryFlag == 2 ? "road" :
iCategoryFlag == 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
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 << ' ';
}
// 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 << ' ';
}
if( eType == tt_Switch
&& SwitchExtension->m_material3 != null_handle) {
auto texturefile { GfxRenderer->Material( m_material2 )->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( 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 << ' ';
}
// 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>
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 ];
}
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;
}
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) 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_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 ) {
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 };
if( Segment ) {
Segment->GetRolls( 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 * ( 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 } };
}
}
// 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 };
if( Segment ) {
Segment->GetRolls( 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 };
if( Segment ) {
Segment->GetRolls( 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
}
}
}
// 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
if( SwitchExtension->m_material3 == null_handle ) { 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 );
// ...then combine them into a single geometry sequence
auto const segmentsize { 10 };
auto const segmentcount { trackbedvertices1.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;
}
} // 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 );
glm::vec3 const world_up { 0.f, 1.f, 0.f };
// 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 *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( double s = start; s < length; s += spacing ) {
glm::dvec3 pos;
glm::vec3 angles;
segment->RaPositionGet( s, pos, angles );
// tangent direction via central difference (clamped to the segment endpoints)
auto const s_back = std::max( 0.0, s - eps );
auto const s_fwd = std::min( length, s + eps );
glm::dvec3 p_back, p_fwd;
glm::vec3 dummy;
segment->RaPositionGet( s_back, p_back, dummy );
segment->RaPositionGet( s_fwd, p_fwd, dummy );
auto tangent = glm::vec3( p_fwd - p_back );
if( glm::length2( tangent ) < 1e-8f ) {
// degenerate sample (e.g. zero-length sub-segment); skip this position rather
// than emit a junk transform with NaN normals.
continue;
}
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 = world_up;
if( std::abs( glm::dot( tangent, up_ref ) ) > 0.999f ) { up_ref = glm::vec3( 1.f, 0.f, 0.f ); }
glm::vec3 right = glm::normalize( glm::cross( up_ref, tangent ) );
glm::vec3 up = glm::cross( tangent, right );
// apply track roll (banking) around the tangent / forward axis.
float const roll = angles.x;
if( roll != 0.f ) {
auto const roll_mat = glm::rotate( glm::mat4( 1.f ), roll, tangent );
right = glm::vec3( roll_mat * glm::vec4( right, 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 - m_origin );
glm::mat4 m { 1.f };
m[ 0 ] = glm::vec4( right, 0.f );
m[ 1 ] = glm::vec4( up, 0.f );
m[ 2 ] = glm::vec4( tangent, 0.f );
m[ 3 ] = glm::vec4( localpos, 1.f );
if( local_offset != glm::vec3( 0.f ) ) {
m = glm::translate( m, local_offset );
}
m_sleeper_local_transforms.emplace_back( m );
}
}
}