/*
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

*/

// nagłówki identyczne w każdym pliku...
#pragma hdrstop

#include "Track.h"
#include "Usefull.h"
#include "Texture.h"
#include "Timer.h"
#include "Globals.h"
#include "Ground.h"
#include "parser.h"
#include "Mover.h"
#include "DynObj.h"
#include "AnimModel.h"
#include "MemCell.h"
#include "Event.h"

#pragma package(smart_init)

// 101206 Ra: trapezoidalne drogi i tory
// 110720 Ra: rozprucie zwrotnicy i odcinki izolowane

static const double fMaxOffset = 0.1; // 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 = NULL;

TSwitchExtension::TSwitchExtension(TTrack *owner, int what)
{ // na początku wszystko puste
    CurrentIndex = 0;
    pNexts[0] = NULL; // wskaźniki do kolejnych odcinków ruchu
    pNexts[1] = NULL;
    pPrevs[0] = NULL;
    pPrevs[1] = NULL;
    fOffsetSpeed = 0.1; // prędkość liniowa iglic
    fOffsetDelay = 0.05; // dodatkowy ruch drugiej iglicy po zablokowaniu pierwszej na opornicy
    fOffset1 = fOffset = fDesiredOffset = -fOffsetDelay; // położenie zasadnicze
    fOffset2 = 0.0; // w zasadniczym wewnętrzna iglica dolega
    pOwner = NULL;
    pNextAnim = NULL;
    bMovement = false; // nie potrzeba przeliczać fOffset1
    Segments[0] = new TSegment(owner); // z punktu 1 do 2
    Segments[1] = new TSegment(
        owner); // z punktu 3 do 4 (1=3 dla zwrotnic; odwrócony dla skrzyżowań, ewentualnie 1=4)
    Segments[2] = (what >= 3) ?
                      new TSegment(owner) :
                      NULL; // z punktu 2 do 4       skrzyżowanie od góry:      wersja "-1":
    Segments[3] =
        (what >= 4) ? new TSegment(owner) : NULL; // z punktu 4 do 1              1       1=4 0 0=3
    Segments[4] =
        (what >= 5) ? new TSegment(owner) : NULL; // z punktu 1 do 3            4 x 3   3 3 x 2   2
    Segments[5] = (what >= 6) ? new TSegment(owner) :
                                NULL; // z punktu 3 do 2              2       2            1       1
    evPlus = evMinus = NULL;
    fVelocity = -1.0; // maksymalne ograniczenie prędkości (ustawianej eventem)
    vTrans = vector3(0, 0, 0); // docelowa translacja przesuwnicy
}
TSwitchExtension::~TSwitchExtension()
{ // nie ma nic do usuwania
    // delete Segments[0];
    // delete Segments[1];
    delete Segments[2];
    delete Segments[3];
    delete Segments[4];
    delete Segments[5];
}

TIsolated::TIsolated()
{ // utworznie pustego
    TIsolated("none", NULL);
};

TIsolated::TIsolated(const AnsiString &n, TIsolated *i)
{ // utworznie obwodu izolowanego
    asName = n;
    pNext = i;
    iAxles = 0;
    evBusy = evFree = NULL;
    pMemCell = NULL; // podpiąć istniejącą albo utworzyć pustą
};

TIsolated::~TIsolated(){
    // usuwanie
    /*
     TIsolated *p=pRoot;
     while (pRoot)
     {
      p=pRoot;
      p->pNext=NULL;
      delete p;
     }
    */
};

TIsolated * TIsolated::Find(const AnsiString &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);
    return pRoot;
};

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)
                Global::AddToQuery(evFree, o); // dodanie zwolnienia do kolejki
            if (Global::iMultiplayer) // jeśli multiplayer
                Global::pGround->WyslijString(asName, 10); // wysłanie pakietu o zwolnieniu
            if (pMemCell) // w powiązanej komórce
                pMemCell->UpdateValues(NULL, 0, int(pMemCell->Value2()) & ~0xFF,
                                       update_memval2); //"zerujemy" ostatnią wartość
        }
    }
    else
    { // grupa była wolna
        iAxles += i;
        if (iAxles)
        {
            if (evBusy)
                Global::AddToQuery(evBusy, o); // dodanie zajętości do kolejki
            if (Global::iMultiplayer) // jeśli multiplayer
                Global::pGround->WyslijString(asName, 11); // wysłanie pakietu o zajęciu
            if (pMemCell) // w powiązanej komórce
                pMemCell->UpdateValues(NULL, 0, int(pMemCell->Value2()) | 1,
                                       update_memval2); // zmieniamy ostatnią wartość na nieparzystą
        }
    }
};

TTrack::TTrack(TGroundNode *g)
{ // tworzenie nowego odcinka ruchu
    trNext = trPrev = NULL; // sąsiednie
    Segment = NULL; // dane odcinka
    SwitchExtension = NULL; // dodatkowe parametry zwrotnicy i obrotnicy
    TextureID1 = 0; // tekstura szyny
    fTexLength = 4.0; // powtarzanie tekstury
    TextureID2 = 0; // tekstura podsypki albo drugiego toru zwrotnicy
    fTexHeight1 = 0.6; // nowy profil podsypki ;)
    fTexWidth = 0.9;
    fTexSlope = 0.9;
    eType = tt_Normal; // domyślnie zwykły
    iCategoryFlag = 1; // 1-tor, 2-droga, 4-rzeka, 8-samolot?
    fTrackWidth = 1.435; // rozstaw toru, szerokość nawierzchni
    fFriction = 0.15; // współczynnik tarcia
    fSoundDistance = -1;
    iQualityFlag = 20;
    iDamageFlag = 0;
    eEnvironment = e_flat;
    bVisible = true;
    iEvents = 0; // Ra: flaga informująca o obecności eventów
    evEvent0 = NULL;
    evEvent1 = NULL;
    evEvent2 = NULL;
    evEventall0 = NULL;
    evEventall1 = NULL;
    evEventall2 = NULL;
    fVelocity = -1; // ograniczenie prędkości
    fTrackLength = 100.0;
    fRadius = 0; // promień wybranego toru zwrotnicy
    fRadiusTable[0] = 0; // dwa promienie nawet dla prostego
    fRadiusTable[1] = 0;
    iNumDynamics = 0;
    ScannedFlag = false;
    DisplayListID = 0;
    iTrapezoid = 0; // parametry kształtu: 0-standard, 1-przechyłka, 2-trapez, 3-oba
    hvOverhead = NULL; // drut zasilający, najbliższy Point1 toru
    fTexRatio1 =
        1.0; // proporcja boków tekstury nawierzchni (żeby zaoszczędzić na rozmiarach tekstur...)
    fTexRatio2 =
        1.0; // proporcja boków tekstury chodnika (żeby zaoszczędzić na rozmiarach tekstur...)
    iPrevDirection = 0; // domyślnie wirtualne odcinki dołączamy stroną od Point1
    iNextDirection = 0;
    pIsolated = NULL;
    pMyNode = g; // Ra: proteza, żeby tor znał swoją nazwę TODO: odziedziczyć TTrack z TGroundNode
    iAction = 0; // normalnie może być pomijany podczas skanowania
    fOverhead = -1.0; // można normalnie pobierać prąd (0 dla jazdy bezprądowej po danym odcinku
    nFouling[0] = NULL; // ukres albo kozioł od strony Point1
    nFouling[1] = NULL; // ukres albo kozioł od strony Point2
    trColides = NULL; // tor kolizyjny, na którym trzeba sprawdzać pojazdy pod kątem zderzenia
}

TTrack::~TTrack()
{ // likwidacja odcinka
    if (eType == tt_Normal)
        delete Segment; // dla zwrotnic nie usuwać tego (kopiowany)
    else
    { // usuwanie dodatkowych danych dla niezwykłych odcinków
        if (eType == tt_Cross)
            delete SwitchExtension->vPoints; // skrzyżowanie może mieć punkty
        SafeDelete(SwitchExtension);
    }
}

void TTrack::Init()
{ // tworzenie pomocniczych danych
    switch (eType)
    {
    case tt_Switch:
        SwitchExtension = new TSwitchExtension(this, 2); // na wprost i na bok
        break;
    case tt_Cross: // tylko dla skrzyżowania dróg
        SwitchExtension = new TSwitchExtension(this, 6); // 6 połączeń
        SwitchExtension->vPoints = NULL; // brak tablicy punktów
        SwitchExtension->iPoints = 0;
        SwitchExtension->bPoints = false; // tablica punktów nie wypełniona
        SwitchExtension->iRoads = 4; // domyślnie 4
        break;
    case tt_Normal:
        Segment = new TSegment(this);
        break;
    case tt_Table: // oba potrzebne
        SwitchExtension = new TSwitchExtension(this, 1); // kopia oryginalnego toru
        Segment = new TSegment(this);
        break;
    }
}

TTrack * TTrack::Create400m(int what, double dx)
{ // tworzenie toru do wstawiania taboru podczas konwersji na E3D
    TGroundNode *tmp = new TGroundNode(TP_TRACK); // node
    TTrack *trk = tmp->pTrack;
    trk->bVisible = 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(vector3(-dx, 0, 0), vector3(-dx, 0, 400), 0, 0, 0); // prosty
    tmp->pCenter = vector3(-dx, 0, 200); //środek, aby się mogło wyświetlić
    TSubRect *r = Global::pGround->GetSubRect(tmp->pCenter.x, tmp->pCenter.z);
    r->NodeAdd(tmp); // dodanie toru do segmentu
    r->Sort(); //żeby wyświetlał tabor z dodanego toru
    r->Release(); // usunięcie skompilowanych zasobów
    return trk;
};

TTrack * TTrack::NullCreate(int dir)
{ // tworzenie toru wykolejającego od strony (dir), albo pętli dla samochodów
    TGroundNode *tmp = new TGroundNode(TP_TRACK), *tmp2 = NULL; // node
    TTrack *trk = tmp->pTrack; // tor; UWAGA! obrotnica może generować duże ilości tego
    // tmp->iType=TP_TRACK;
    // TTrack* trk=new TTrack(tmp); //tor; UWAGA! obrotnica może generować duże ilości tego
    // tmp->pTrack=trk;
    trk->bVisible = false; // nie potrzeba pokazywać, zresztą i tak nie ma tekstur
    // trk->iTrapezoid=1; //są przechyłki do uwzględniania w rysowaniu
    trk->iCategoryFlag = (iCategoryFlag & 15) | 0x80; // taki sam typ plus informacja, że dodatkowy
    double r1, r2;
    Segment->GetRolls(r1, r2); // pobranie przechyłek na początku toru
    vector3 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 * Normalize(Segment->GetDirection1());
            trk->Segment->Init(p1, p2, 5, -RadToDeg(r1),
                               70.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce
            ConnectPrevPrev(trk, 0);
            break;
        case 1:
            p1 = Segment->FastGetPoint_1();
            p2 = p1 - 450.0 * Normalize(Segment->GetDirection2());
            trk->Segment->Init(p1, p2, 5, RadToDeg(r2),
                               70.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce
            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 *
                     Normalize(
                         SwitchExtension->Segments[1]->GetDirection2()); // przedłużenie na wprost
            trk->Segment->Init(p1, p2, 5, RadToDeg(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
        tmp2 = new TGroundNode(TP_TRACK); // drugi odcinek do zapętlenia
        TTrack *trk2 = tmp2->pTrack;
        trk2->iCategoryFlag =
            (iCategoryFlag & 15) | 0x80; // taki sam typ plus informacja, że dodatkowy
        trk2->bVisible = 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
        switch (dir)
        { //łączenie z nowym torem
        case 0:
            p1 = Segment->FastGetPoint_0();
            cv1 = -20.0 * Normalize(Segment->GetDirection1()); // pierwszy wektor kontrolny
            p2 = p1 + cv1 + cv1; // 40m
            trk->Segment->Init(p1, p1 + cv1, p2 + vector3(-cv1.z, cv1.y, cv1.x), p2, 2,
                               -RadToDeg(r1),
                               0.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce
            ConnectPrevPrev(trk, 0);
            trk2->Segment->Init(p1, p1 + cv1, p2 + vector3(cv1.z, cv1.y, -cv1.x), p2, 2,
                                -RadToDeg(r1),
                                0.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce
            trk2->iPrevDirection = 0; // zwrotnie do tego samego odcinka
            break;
        case 1:
            p1 = Segment->FastGetPoint_1();
            cv1 = -20.0 * Normalize(Segment->GetDirection2()); // pierwszy wektor kontrolny
            p2 = p1 + cv1 + cv1;
            trk->Segment->Init(p1, p1 + cv1, p2 + vector3(-cv1.z, cv1.y, cv1.x), p2, 2,
                               RadToDeg(r2),
                               0.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce
            ConnectNextPrev(trk, 0);
            trk2->Segment->Init(p1, p1 + cv1, p2 + vector3(cv1.z, cv1.y, -cv1.x), p2, 2,
                                RadToDeg(r2),
                                0.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce
            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
        tmp2->pCenter = (0.5 * (p1 + p2)); //środek, aby się mogło wyświetlić
    }
    // trzeba jeszcze dodać do odpowiedniego segmentu, aby się renderowały z niego pojazdy
    tmp->pCenter = (0.5 * (p1 + p2)); //środek, aby się mogło wyświetlić
    if (tmp2)
        tmp2->pCenter = tmp->pCenter; // ten sam środek jest
    // Ra: to poniżej to porażka, ale na razie się nie da inaczej
    TSubRect *r = Global::pGround->GetSubRect(tmp->pCenter.x, tmp->pCenter.z);
    r->NodeAdd(tmp); // dodanie toru do segmentu
    if (tmp2)
        r->NodeAdd(tmp2); // drugiego też
    r->Sort(); //żeby wyświetlał tabor z dodanego toru
    r->Release(); // usunięcie skompilowanych zasobów
    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 (bVisible)
            if (pTrack->bVisible)
                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 (bVisible)
            if (pTrack->bVisible)
                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;
    }
}

vector3 MakeCPoint(vector3 p, double d, double a1, double a2)
{
    vector3 cp = vector3(0, 0, 1);
    cp.RotateX(DegToRad(a2));
    cp.RotateY(DegToRad(a1));
    cp = cp * d + p;
    return cp;
}

vector3 LoadPoint(cParser *parser)
{ // pobranie współrzędnych punktu
    vector3 p;
    std::string token;
    parser->getTokens(3);
    *parser >> p.x >> p.y >> p.z;
    return p;
}

void TTrack::Load(cParser *parser, vector3 pOrigin, AnsiString name)
{ // pobranie obiektu trajektorii ruchu
    vector3 pt, vec, p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań
    double a1, a2, r1, r2, r3, r4, d1, d2, a;
    AnsiString str;
    bool bCurve;
    int i; //,state; //Ra: teraz już nie ma początkowego stanu zwrotnicy we wpisie
    std::string token;

    parser->getTokens();
    *parser >> token;
    str = AnsiString(token.c_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.c_str());
    parser->getTokens(4);
    *parser >> fTrackLength >> fTrackWidth >> fFriction >> fSoundDistance;
    //    fTrackLength=Parser->GetNextSymbol().ToDouble();                       //track length
    //    100502
    //    fTrackWidth=Parser->GetNextSymbol().ToDouble();                        //track width
    //    fFriction=Parser->GetNextSymbol().ToDouble();                          //friction coeff.
    //    fSoundDistance=Parser->GetNextSymbol().ToDouble();   //snd
    fTrackWidth2 = fTrackWidth; // rozstaw/szerokość w punkcie 2, na razie taka sama
    parser->getTokens(2);
    *parser >> iQualityFlag >> iDamageFlag;
    //    iQualityFlag=Parser->GetNextSymbol().ToInt();   //McZapkie: qualityflag
    //    iDamageFlag=Parser->GetNextSymbol().ToInt();   //damage
    if (iDamageFlag & 128)
        iAction |= 0x80; // flaga wykolejania z powodu uszkodzenia
    parser->getTokens();
    *parser >> token;
    str = AnsiString(token.c_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;
    bVisible = (token.compare("vis") == 0); // visible
    if (bVisible)
    {
        parser->getTokens();
        *parser >> token;
        str = AnsiString(token.c_str()); // railtex
        TextureID1 = (str == "none" ? 0 : TTexturesManager::GetTextureID(
                                              szTexturePath, szSceneryPath, str.c_str(),
                                              (iCategoryFlag & 1) ? Global::iRailProFiltering :
                                                                    Global::iBallastFiltering));
        parser->getTokens();
        *parser >> fTexLength; // tex tile length
        if (fTexLength < 0.01)
            fTexLength = 4; // Ra: zabezpiecznie przed zawieszeniem
        parser->getTokens();
        *parser >> token;
        str = AnsiString(token.c_str()); // sub || railtex
        TextureID2 = (str == "none" ? 0 : TTexturesManager::GetTextureID(
                                              szTexturePath, szSceneryPath, str.c_str(),
                                              (eType == tt_Normal) ? Global::iBallastFiltering :
                                                                     Global::iRailProFiltering));
        parser->getTokens(3);
        *parser >> fTexHeight1 >> fTexWidth >> fTexSlope;
        //     fTexHeight=Parser->GetNextSymbol().ToDouble(); //tex sub height
        //     fTexWidth=Parser->GetNextSymbol().ToDouble(); //tex sub width
        //     fTexSlope=Parser->GetNextSymbol().ToDouble(); //tex sub slope width
        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
    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:
        p1 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P1
        parser->getTokens();
        *parser >> r1; // pobranie przechyłki w P1
        cp1 = LoadPoint(parser); // pobranie współrzędnych punktów kontrolnych
        cp2 = LoadPoint(parser);
        p2 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P2
        parser->getTokens(2);
        *parser >> r2 >> fRadius; // pobranie przechyłki w P1 i promienia
        fRadius = fabs(fRadius); // we wpisie może być ujemny
        if (iCategoryFlag & 1)
        { // zero na główce szyny
            p1.y += 0.18;
            p2.y += 0.18;
            // na przechyłce doliczyć jeszcze pół przechyłki
        }
        if (fRadius != 0) // gdy podany promień
            segsize = Min0R(5.0, 0.2 + fabs(fRadius) * 0.02); // do 250m - 5, potem 1 co 50m

        if ((((p1 + p1 + p2) / 3.0 - p1 - cp1).Length() < 0.02) ||
            (((p1 + p2 + p2) / 3.0 - p2 + cp1).Length() < 0.02))
            cp1 = cp2 = vector3(0, 0, 0); //"prostowanie" prostych z kontrolnymi, dokładność 2cm

        if ((cp1 == vector3(0, 0, 0)) &&
            (cp2 == vector3(0, 0, 0))) // Ra: hm, czasem dla prostego są podane...
            Segment->Init(p1, p2, segsize, r1,
                          r2); // gdy prosty, kontrolne wyliczane przy zmiennej przechyłce
        else
            Segment->Init(p1, cp1 + p1, cp2 + p2, p2, segsize, r1,
                          r2); // gdy łuk (ustawia bCurve=true)
        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); // kopia oryginalnego toru
        }
        else if (iCategoryFlag & 2)
            if (TextureID1 && fTexLength)
            { // dla drogi trzeba ustalić proporcje boków nawierzchni
                float w, h;
                glBindTexture(GL_TEXTURE_2D, TextureID1);
                glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
                glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
                if (h != 0.0)
                    fTexRatio1 = w / h; // proporcja boków
                glBindTexture(GL_TEXTURE_2D, TextureID2);
                glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
                glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
                if (h != 0.0)
                    fTexRatio2 = w / h; // 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ę

        p1 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P1
        parser->getTokens();
        *parser >> r1;
        cp1 = LoadPoint(parser);
        cp2 = LoadPoint(parser);
        p2 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P2
        parser->getTokens(2);
        *parser >> r2 >> fRadiusTable[0];
        fRadiusTable[0] = fabs(fRadiusTable[0]); // we wpisie może być ujemny
        if (iCategoryFlag & 1)
        { // zero na główce szyny
            p1.y += 0.18;
            p2.y += 0.18;
            // na przechyłce doliczyć jeszcze pół przechyłki?
        }
        if (fRadiusTable[0] > 0)
            segsize = Min0R(5.0, 0.2 + fRadiusTable[0] * 0.02);
        else if (eType != tt_Cross) // dla skrzyżowań muszą być podane kontrolne
        { // jak promień zerowy, to przeliczamy punkty kontrolne
            cp1 = (p1 + p1 + p2) / 3.0 - p1; // jak jest prosty, to się zoptymalizuje
            cp2 = (p1 + p2 + p2) / 3.0 - p2;
            segsize = 5.0;
        } // ułomny prosty
        if (!(cp1 == vector3(0, 0, 0)) && !(cp2 == vector3(0, 0, 0)))
            SwitchExtension->Segments[0]->Init(p1, p1 + cp1, p2 + cp2, p2, segsize, r1, r2);
        else
            SwitchExtension->Segments[0]->Init(p1, p2, segsize, r1, r2);

        p3 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P3
        parser->getTokens();
        *parser >> r3;
        cp3 = LoadPoint(parser);
        cp4 = LoadPoint(parser);
        p4 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P4
        parser->getTokens(2);
        *parser >> r4 >> fRadiusTable[1];
        fRadiusTable[1] = fabs(fRadiusTable[1]); // we wpisie może być ujemny
        if (iCategoryFlag & 1)
        { // zero na główce szyny
            p3.y += 0.18;
            p4.y += 0.18;
            // na przechyłce doliczyć jeszcze pół przechyłki?
        }

        if (fRadiusTable[1] > 0)
            segsize = Min0R(5.0, 0.2 + fRadiusTable[1] * 0.02);
        else if (eType != tt_Cross) // dla skrzyżowań muszą być podane kontrolne
        { // jak promień zerowy, to przeliczamy punkty kontrolne
            cp3 = (p3 + p3 + p4) / 3.0 - p3; // jak jest prosty, to się zoptymalizuje
            cp4 = (p3 + p4 + p4) / 3.0 - p4;
            segsize = 5.0;
        } // ułomny prosty

        if (!(cp3 == vector3(0, 0, 0)) && !(cp4 == vector3(0, 0, 0)))
        { // dla skrzyżowania dróg dać odwrotnie końce, żeby brzegi generować lewym
            if (eType != tt_Cross)
                SwitchExtension->Segments[1]->Init(p3, p3 + cp3, p4 + cp4, p4, segsize, r3, r4);
            else
                SwitchExtension->Segments[1]->Init(p4, p4 + cp4, p3 + cp3, p3, segsize, r4,
                                                   r3); // odwrócony
        }
        else
            SwitchExtension->Segments[1]->Init(p3, p4, segsize, r3, r4);
        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
            if (LengthSquared3(p3 - p1) <
                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

        // Ra: zamienić później na iloczyn wektorowy
        {
            vector3 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;
    }
    parser->getTokens();
    *parser >> token;
    str = AnsiString(token.c_str());
    while (str != "endtrack")
    {
        if (str == "event0")
        {
            parser->getTokens();
            *parser >> token;
            asEvent0Name = AnsiString(token.c_str());
        }
        else if (str == "event1")
        {
            parser->getTokens();
            *parser >> token;
            asEvent1Name = AnsiString(token.c_str());
        }
        else if (str == "event2")
        {
            parser->getTokens();
            *parser >> token;
            asEvent2Name = AnsiString(token.c_str());
        }
        else if (str == "eventall0")
        {
            parser->getTokens();
            *parser >> token;
            asEventall0Name = AnsiString(token.c_str());
        }
        else if (str == "eventall1")
        {
            parser->getTokens();
            *parser >> token;
            asEventall1Name = AnsiString(token.c_str());
        }
        else if (str == "eventall2")
        {
            parser->getTokens();
            *parser >> token;
            asEventall2Name = AnsiString(token.c_str());
        }
        else if (str == "velocity")
        {
            parser->getTokens();
            *parser >> fVelocity; //*0.28; McZapkie-010602
            if (SwitchExtension) // jeśli tor ruchomy
                if (fabs(fVelocity) >= 1.0) //żeby zero nie ograniczało dożywotnio
                    SwitchExtension->fVelocity = fVelocity; // zapamiętanie głównego ograniczenia; a
            // np. -40 ogranicza tylko na bok
        }
        else if (str == "isolated")
        { // obwód izolowany, do którego tor należy
            parser->getTokens();
            *parser >> token;
            pIsolated = TIsolated::Find(AnsiString(token.c_str()));
        }
        else if (str == "angle1")
        { // kąt ścięcia końca od strony 1
            parser->getTokens();
            *parser >> a1;
            Segment->AngleSet(0, a1);
        }
        else if (str == "angle2")
        { // kąt ścięcia końca od strony 2
            parser->getTokens();
            *parser >> a2;
            Segment->AngleSet(1, a2);
        }
        else if (str == "fouling1")
        { // wskazanie modelu ukresu w kierunku 1
            parser->getTokens();
            *parser >> token;
            // nFouling[0]=
        }
        else if (str == "fouling2")
        { // wskazanie modelu ukresu w kierunku 2
            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 == "colides")
        { // informacja o stanie sieci: 0-jazda bezprądowa, >0-z opuszczonym i ograniczeniem
            // prędkości
            parser->getTokens();
            *parser >> token;
            // trColides=; //tor kolizyjny, na którym trzeba sprawdzać pojazdy pod kątem zderzenia
        }
        else
            ErrorLog("Unknown property: \"" + str + "\" in track \"" + name + "\"");
        parser->getTokens();
        *parser >> token;
        str = AnsiString(token.c_str());
    }
    // alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru
    if (!pIsolated)
        if ((i = name.Pos("@")) > 0)
            if (i < name.Length()) // nie może być puste
            {
                pIsolated = TIsolated::Find(name.SubString(i + 1, name.Length()));
                name = name.SubString(1, i - 1); // usunięcie z nazwy
            }
}

bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2)
{
    bool bError = false;
    if (!evEvent0)
    {
        if (NewEvent0)
        {
            evEvent0 = NewEvent0;
            asEvent0Name = "";
            iEvents |= 1; // sumaryczna informacja o eventach
        }
        else
        {
            if (!asEvent0Name.IsEmpty())
            {
                ErrorLog(AnsiString("Bad track: Event0 \"") + asEvent0Name +
                         AnsiString("\" does not exist"));
                bError = true;
            }
        }
    }
    else
    {
        ErrorLog(
            AnsiString("Bad track: Event0 cannot be assigned to track, track already has one"));
        bError = true;
    }
    if (!evEvent1)
    {
        if (NewEvent1)
        {
            evEvent1 = NewEvent1;
            asEvent1Name = "";
            iEvents |= 2; // sumaryczna informacja o eventach
        }
        else if (!asEvent1Name.IsEmpty())
        { // Ra: tylko w logu informacja
            ErrorLog(AnsiString("Bad track: Event1 \"") + asEvent1Name +
                     AnsiString("\" does not exist").c_str());
            bError = true;
        }
    }
    else
    {
        ErrorLog(
            AnsiString("Bad track: Event1 cannot be assigned to track, track already has one"));
        bError = true;
    }
    if (!evEvent2)
    {
        if (NewEvent2)
        {
            evEvent2 = NewEvent2;
            asEvent2Name = "";
            iEvents |= 4; // sumaryczna informacja o eventach
        }
        else if (!asEvent2Name.IsEmpty())
        { // Ra: tylko w logu informacja
            ErrorLog(AnsiString("Bad track: Event2 \"") + asEvent2Name +
                     AnsiString("\" does not exist"));
            bError = true;
        }
    }
    else
    {
        ErrorLog(
            AnsiString("Bad track: Event2 cannot be assigned to track, track already has one"));
        bError = true;
    }
    return !bError;
}

bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2)
{
    bool bError = false;
    if (!evEventall0)
    {
        if (NewEvent0)
        {
            evEventall0 = NewEvent0;
            asEventall0Name = "";
            iEvents |= 8; // sumaryczna informacja o eventach
        }
        else
        {
            if (!asEvent0Name.IsEmpty())
            {
                Error(AnsiString("Eventall0 \"") + asEventall0Name +
                      AnsiString("\" does not exist"));
                bError = true;
            }
        }
    }
    else
    {
        Error(AnsiString("Eventall0 cannot be assigned to track, track already has one"));
        bError = true;
    }
    if (!evEventall1)
    {
        if (NewEvent1)
        {
            evEventall1 = NewEvent1;
            asEventall1Name = "";
            iEvents |= 16; // sumaryczna informacja o eventach
        }
        else
        {
            if (!asEvent0Name.IsEmpty())
            { // Ra: tylko w logu informacja
                WriteLog(AnsiString("Eventall1 \"") + asEventall1Name +
                         AnsiString("\" does not exist"));
                bError = true;
            }
        }
    }
    else
    {
        Error(AnsiString("Eventall1 cannot be assigned to track, track already has one"));
        bError = true;
    }
    if (!evEventall2)
    {
        if (NewEvent2)
        {
            evEventall2 = NewEvent2;
            asEventall2Name = "";
            iEvents |= 32; // sumaryczna informacja o eventach
        }
        else
        {
            if (!asEvent0Name.IsEmpty())
            { // Ra: tylko w logu informacja
                WriteLog(AnsiString("Eventall2 \"") + asEventall2Name +
                         AnsiString("\" does not exist"));
                bError = true;
            }
        }
    }
    else
    {
        Error(AnsiString("Eventall2 cannot be assigned to track, track already has one"));
        bError = true;
    }
    return !bError;
}

bool TTrack::AssignForcedEvents(TEvent *NewEventPlus, TEvent *NewEventMinus)
{ // ustawienie eventów sygnalizacji rozprucia
    if (SwitchExtension)
    {
        if (NewEventPlus)
            SwitchExtension->evPlus = NewEventPlus;
        if (NewEventMinus)
            SwitchExtension->evMinus = NewEventMinus;
        return true;
    }
    return false;
};

AnsiString TTrack::IsolatedName()
{ // podaje nazwę odcinka izolowanego, jesli nie ma on jeszcze przypisanych zdarzeń
    if (pIsolated)
        if (!pIsolated->evBusy && !pIsolated->evFree)
            return pIsolated->asName;
    return "";
};

bool TTrack::IsolatedEventsAssign(TEvent *busy, TEvent *free)
{ // ustawia zdarzenia dla odcinka izolowanego
    if (pIsolated)
    {
        if (busy)
            pIsolated->evBusy = busy;
        if (free)
            pIsolated->evFree = free;
        return true;
    }
    return false;
};

// ABu: przeniesione z Track.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 = NULL; // trzeba by to uzależnić od kierunku ruchu...
        return true;
    }
    if (Global::iMultiplayer) // jeśli multiplayer
        if (!iNumDynamics) // pierwszy zajmujący
            if (pMyNode->asName != "none")
                Global::pGround->WyslijString(pMyNode->asName,
                                              8); // przekazanie informacji o zajętości toru
    if (iNumDynamics < iMaxNumDynamics)
    { // jeśli jest miejsce, dajemy na koniec
        Dynamics[iNumDynamics++] = 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;
    }
    else
    {
        Error("Too many dynamics on track " + pMyNode->asName);
        return false;
    }
};

void TTrack::MoveMe(vector3 pPosition)
{ // to nie jest używane
    if (SwitchExtension)
    {
        SwitchExtension->Segments[0]->MoveMe(1 * pPosition);
        SwitchExtension->Segments[1]->MoveMe(1 * pPosition);
        SwitchExtension->Segments[2]->MoveMe(3 * pPosition); // Ra: 3 razy?
        SwitchExtension->Segments[3]->MoveMe(4 * pPosition);
    }
    else
    {
        Segment->MoveMe(pPosition);
    };
    ResourceManager::Unregister(this);
};

const int numPts = 4;
const int nnumPts = 12;
/*
const vector6 szyna[nnumPts]= //szyna - vextor6(x,y,mapowanie tekstury,xn,yn,zn)
{pierwotna szyna, opracował youBy, zmiany w celu uzyskania symetrii
 vector6( 0.111,-0.180,0.00, 1.000, 0.000,0.000),
 vector6( 0.045,-0.155,0.15, 0.707, 0.707,0.000),
 vector6( 0.045,-0.070,0.25, 0.707,-0.707,0.000),
 vector6( 0.071,-0.040,0.35, 0.707,-0.707,0.000), //albo tu 0.073
 vector6( 0.072,-0.010,0.40, 0.707, 0.707,0.000),
 vector6( 0.052,-0.000,0.45, 0.000, 1.000,0.000),
 vector6( 0.020,-0.000,0.55, 0.000, 1.000,0.000),
 vector6( 0.000,-0.010,0.60,-0.707, 0.707,0.000),
 vector6( 0.001,-0.040,0.65,-0.707,-0.707,0.000), //albo tu -0.001
 vector6( 0.027,-0.070,0.75,-0.707,-0.707,0.000), //albo zostanie asymetryczna
 vector6( 0.027,-0.155,0.85,-0.707, 0.707,0.000),
 vector6(-0.039,-0.180,1.00,-1.000, 0.000,0.000)
};
*/
const vector6 szyna[nnumPts] = // szyna - vextor6(x,y,mapowanie tekstury,xn,yn,zn)
    { // tę wersję opracował Tolein (bez pochylenia)
     vector6(0.111, -0.180, 0.00, 1.000, 0.000, 0.000),
     vector6(0.046, -0.150, 0.15, 0.707, 0.707, 0.000),
     vector6(0.044, -0.050, 0.25, 0.707, -0.707, 0.000),
     vector6(0.073, -0.038, 0.35, 0.707, -0.707, 0.000),
     vector6(0.072, -0.010, 0.40, 0.707, 0.707, 0.000),
     vector6(0.052, -0.000, 0.45, 0.000, 1.000, 0.000),
     vector6(0.020, -0.000, 0.55, 0.000, 1.000, 0.000),
     vector6(0.000, -0.010, 0.60, -0.707, 0.707, 0.000),
     vector6(-0.001, -0.038, 0.65, -0.707, -0.707, 0.000),
     vector6(0.028, -0.050, 0.75, -0.707, -0.707, 0.000),
     vector6(0.026, -0.150, 0.85, -0.707, 0.707, 0.000),
     vector6(-0.039, -0.180, 1.00, -1.000, 0.000, 0.000)};

const vector6 iglica[nnumPts] = // iglica - vextor3(x,y,mapowanie tekstury)
    {
     vector6(0.010, -0.180, 0.00, 1.000, 0.000, 0.000),
     vector6(0.010, -0.155, 0.15, 1.000, 0.000, 0.000),
     vector6(0.010, -0.070, 0.25, 1.000, 0.000, 0.000),
     vector6(0.010, -0.040, 0.35, 1.000, 0.000, 0.000),
     vector6(0.010, -0.010, 0.40, 1.000, 0.000, 0.000),
     vector6(0.010, -0.000, 0.45, 0.707, 0.707, 0.000),
     vector6(0.000, -0.000, 0.55, 0.707, 0.707, 0.000),
     vector6(0.000, -0.010, 0.60, -1.000, 0.000, 0.000),
     vector6(0.000, -0.040, 0.65, -1.000, 0.000, 0.000),
     vector6(0.000, -0.070, 0.75, -1.000, 0.000, 0.000),
     vector6(0.000, -0.155, 0.85, -0.707, 0.707, 0.000),
     vector6(-0.040, -0.180, 1.00, -1.000, 0.000,
             0.000) // 1mm więcej, żeby nie nachodziły tekstury?
};

void TTrack::Compile(GLuint tex)
{ // generowanie treści dla Display Lists - model proceduralny
    if (!tex)
    { // jeśli nie podana tekstura, to każdy tor ma wlasne DL
        if (DisplayListID)
            Release(); // zwolnienie zasobów w celu ponownego utworzenia
        if (Global::bManageNodes)
        {
            DisplayListID = glGenLists(1); // otwarcie nowej listy
            glNewList(DisplayListID, GL_COMPILE);
        };
    }
    glColor3f(1.0f, 1.0f, 1.0f); // to tutaj potrzebne?
    // Ra: nie zmieniamy oświetlenia przy kompilowaniu, ponieważ ono się zmienia w czasie!
    // trochę podliczonych zmiennych, co się potem przydadzą
    double fHTW = 0.5 * fabs(fTrackWidth); // połowa szerokości
    double side = fabs(fTexWidth); // szerokść podsypki na zewnątrz szyny albo pobocza
    double slop = fabs(fTexSlope); // szerokość pochylenia
    double rozp = fHTW + side + slop; // brzeg zewnętrzny
    double hypot1 = hypot(slop, fTexHeight1); // rozmiar pochylenia do liczenia normalnych
    if (hypot1 == 0.0)
        hypot1 = 1.0;
    vector3 normal1 = vector3(fTexSlope / hypot1, fTexHeight1 / hypot1, 0.0); // wektor normalny
    double fHTW2, side2, slop2, rozp2, fTexHeight2, hypot2;
    vector3 normal2;
    if (iTrapezoid & 2) // ten bit oznacza, że istnieje odpowiednie pNext
    { // Ra: jest OK
        fHTW2 = 0.5 * fabs(trNext->fTrackWidth); // połowa rozstawu/nawierzchni
        side2 = fabs(trNext->fTexWidth);
        slop2 = fabs(trNext->fTexSlope);
        rozp2 = fHTW2 + side2 + slop2; // szerokość podstawy
        fTexHeight2 = trNext->fTexHeight1;
        hypot2 = hypot(slop2, fTexHeight2);
        if (hypot2 == 0.0)
            hypot2 = 1.0;
        normal2 = vector3(trNext->fTexSlope / hypot2, fTexHeight2 / hypot2, 0.0);
    }
    else // gdy nie ma następnego albo jest nieodpowiednim końcem podpięty
    {
        fHTW2 = fHTW;
        side2 = side;
        slop2 = slop;
        rozp2 = rozp;
        fTexHeight2 = fTexHeight1;
        hypot2 = hypot1;
        normal2 = normal1;
    }
    double roll1, roll2;
    switch (iCategoryFlag & 15)
    {
    case 1: // tor
    {
        Segment->GetRolls(roll1, roll2);
        double sin1 = sin(roll1), cos1 = cos(roll1), sin2 = sin(roll2), cos2 = cos(roll2);
        // zwykla szyna: //Ra: czemu główki są asymetryczne na wysokości 0.140?
        vector6 rpts1[24], rpts2[24], rpts3[24], rpts4[24];
        int i;
        for (i = 0; i < 12; ++i)
        {
            rpts1[i] = vector6((fHTW + szyna[i].x) * cos1 + szyna[i].y * sin1,
                               -(fHTW + szyna[i].x) * sin1 + szyna[i].y * cos1, szyna[i].z,
                               +szyna[i].n.x * cos1 + szyna[i].n.y * sin1,
                               -szyna[i].n.x * sin1 + szyna[i].n.y * cos1, 0.0);
            rpts2[11 - i] = vector6((-fHTW - szyna[i].x) * cos1 + szyna[i].y * sin1,
                                    -(-fHTW - szyna[i].x) * sin1 + szyna[i].y * cos1, szyna[i].z,
                                    -szyna[i].n.x * cos1 + szyna[i].n.y * sin1,
                                    +szyna[i].n.x * sin1 + szyna[i].n.y * cos1, 0.0);
        }
        if (iTrapezoid) // jak trapez albo przechyłki, to oddzielne punkty na końcu
            for (i = 0; i < 12; ++i)
            {
                rpts1[12 + i] = vector6((fHTW2 + szyna[i].x) * cos2 + szyna[i].y * sin2,
                                        -(fHTW2 + szyna[i].x) * sin2 + szyna[i].y * cos2,
                                        szyna[i].z, +szyna[i].n.x * cos2 + szyna[i].n.y * sin2,
                                        -szyna[i].n.x * sin2 + szyna[i].n.y * cos2, 0.0);
                rpts2[23 - i] = vector6((-fHTW2 - szyna[i].x) * cos2 + szyna[i].y * sin2,
                                        -(-fHTW2 - szyna[i].x) * sin2 + szyna[i].y * cos2,
                                        szyna[i].z, -szyna[i].n.x * cos2 + szyna[i].n.y * sin2,
                                        +szyna[i].n.x * sin2 + szyna[i].n.y * cos2, 0.0);
            }
        switch (eType) // dalej zależnie od typu
        {
        case tt_Table: // obrotnica jak zwykły tor, animacja wykonywana w RaAnimate(), tutaj tylko
        // regeneracja siatek
        case tt_Normal:
            if (TextureID2)
                if (tex ? TextureID2 == tex : true) // jeśli pasuje do grupy (tex)
                { // podsypka z podkładami jest tylko dla zwykłego toru
                    vector6
                        bpts1[8]; // punkty głównej płaszczyzny nie przydają się do robienia boków
                    if (fTexLength ==
                        4.0) // jeśli stare mapowanie na profil 0.2 0.5 1.1 (również 6-9-9/noil)
                    { // stare mapowanie z różną gęstością pikseli i oddzielnymi teksturami na każdy
                        // profil
                        if (iTrapezoid) // trapez albo przechyłki
                        { // podsypka z podkladami trapezowata
                            // 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
                            bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.00, normal1.x,
                                               -normal1.y, 0.0); // lewy brzeg
                            bpts1[1] = vector6((fHTW + side) * cos1, -(fHTW + side) * sin1 - 0.18,
                                               0.33, 0.0, 1.0, 0.0); // krawędź załamania
                            bpts1[2] =
                                vector6(-bpts1[1].x, +(fHTW + side) * sin1 - 0.18, 0.67, -normal1.x,
                                        -normal1.y, 0.0); // prawy brzeg początku symetrycznie
                            bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 1.00, -normal1.x,
                                               -normal1.y, 0.0); // prawy skos
                            // przekrój końcowy
                            bpts1[4] = vector6(rozp2, -fTexHeight2 - 0.18, 0.00, normal2.x,
                                               -normal2.y, 0.0); // lewy brzeg
                            bpts1[5] =
                                vector6((fHTW2 + side2) * cos2, -(fHTW2 + side2) * sin2 - 0.18,
                                        0.33, 0.0, 1.0, 0.0); // krawędź załamania
                            bpts1[6] = vector6(-bpts1[5].x, +(fHTW2 + side2) * sin2 - 0.18, 0.67,
                                               0.0, 1.0, 0.0); // prawy brzeg początku symetrycznie
                            bpts1[7] = vector6(-rozp2, -fTexHeight2 - 0.18, 1.00, -normal2.x,
                                               -normal2.y, 0.0); // prawy skos
                        }
                        else
                        {
                            bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.0, +normal1.x,
                                               -normal1.y, 0.0); // lewy brzeg
                            bpts1[1] = vector6(fHTW + side, -0.18, 0.33, +normal1.x, -normal1.y,
                                               0.0); // krawędź załamania
                            bpts1[2] = vector6(-fHTW - side, -0.18, 0.67, -normal1.x, -normal1.y,
                                               0.0); // druga
                            bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 1.0, -normal1.x,
                                               -normal1.y, 0.0); // prawy skos
                        }
                    }
                    else
                    { // mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa
                        // fTexLength
                        double max = fTexRatio2 * fTexLength; // szerokość proporcjonalna do
                        // długości
                        double map11 =
                            max > 0.0 ? (fHTW + side) / max : 0.25; // załamanie od strony 1
                        double map12 =
                            max > 0.0 ? (fHTW + side + hypot1) / max : 0.5; // brzeg od strony 1
                        if (iTrapezoid) // trapez albo przechyłki
                        { // podsypka z podkladami trapezowata
                            double map21 =
                                max > 0.0 ? (fHTW2 + side2) / max : 0.25; // załamanie od strony 2
                            double map22 = max > 0.0 ? (fHTW2 + side2 + hypot2) / max :
                                                       0.5; // 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
                            bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.5 - map12, normal1.x,
                                               -normal1.y, 0.0); // lewy brzeg
                            bpts1[1] = vector6((fHTW + side) * cos1, -(fHTW + side) * sin1 - 0.18,
                                               0.5 - map11, 0.0, 1.0, 0.0); // krawędź załamania
                            bpts1[2] =
                                vector6(-bpts1[1].x, +(fHTW + side) * sin1 - 0.18, 0.5 + map11, 0.0,
                                        1.0, 0.0); // prawy brzeg początku symetrycznie
                            bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 0.5 + map12, -normal1.x,
                                               -normal1.y, 0.0); // prawy skos
                            // przekrój końcowy
                            bpts1[4] = vector6(rozp2, -fTexHeight2 - 0.18, 0.5 - map22, normal2.x,
                                               -normal2.y, 0.0); // lewy brzeg
                            bpts1[5] =
                                vector6((fHTW2 + side2) * cos2, -(fHTW2 + side2) * sin2 - 0.18,
                                        0.5 - map21, 0.0, 1.0, 0.0); // krawędź załamania
                            bpts1[6] =
                                vector6(-bpts1[5].x, +(fHTW2 + side2) * sin2 - 0.18, 0.5 + map21,
                                        0.0, 1.0, 0.0); // prawy brzeg początku symetrycznie
                            bpts1[7] = vector6(-rozp2, -fTexHeight2 - 0.18, 0.5 + map22, -normal2.x,
                                               -normal2.y, 0.0); // prawy skos
                        }
                        else
                        {
                            bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.5 - map12, +normal1.x,
                                               -normal1.y, 0.0); // lewy brzeg
                            bpts1[1] = vector6(fHTW + side, -0.18, 0.5 - map11, +normal1.x,
                                               -normal1.y, 0.0); // krawędź załamania
                            bpts1[2] = vector6(-fHTW - side, -0.18, 0.5 + map11, -normal1.x,
                                               -normal1.y, 0.0); // druga
                            bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 0.5 + map12, -normal1.x,
                                               -normal1.y, 0.0); // prawy skos
                        }
                    }
                    if (!tex)
                        glBindTexture(GL_TEXTURE_2D, TextureID2);
                    Segment->RenderLoft(bpts1, iTrapezoid ? -4 : 4, fTexLength);
                }
            if (TextureID1)
                if (tex ? TextureID1 == tex : true) // jeśli pasuje do grupy (tex)
                { // szyny
                    if (!tex)
                        glBindTexture(GL_TEXTURE_2D, TextureID1);
                    Segment->RenderLoft(rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength);
                    Segment->RenderLoft(rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength);
                }
            break;
        case tt_Switch: // dla zwrotnicy dwa razy szyny
            if (TextureID1) // zwrotnice nie są grupowane, aby prościej było je animować
            { // iglice liczone tylko dla zwrotnic
                // Ra: TODO: oddzielna animacja każdej iglicy, opór na docisku
                vector6 rpts3[24], rpts4[24];
                for (i = 0; i < 12; ++i)
                {
                    rpts3[i] = vector6((fHTW + iglica[i].x) * cos1 + iglica[i].y * sin1,
                                       -(fHTW + iglica[i].x) * sin1 + iglica[i].y * cos1,
                                       iglica[i].z, +iglica[i].n.x * cos1 + iglica[i].n.y * sin1,
                                       -iglica[i].n.x * sin1 + iglica[i].n.y * cos1, 0.0);
                    rpts4[11 - i] =
                        vector6((-fHTW - iglica[i].x) * cos1 + iglica[i].y * sin1,
                                -(-fHTW - iglica[i].x) * sin1 + iglica[i].y * cos1, iglica[i].z,
                                -iglica[i].n.x * cos1 + iglica[i].n.y * sin1,
                                +iglica[i].n.x * sin1 + iglica[i].n.y * cos1, 0.0);
                }
                if (iTrapezoid) // trapez albo przechyłki, to oddzielne punkty na końcu
                    for (i = 0; i < 12; ++i)
                    {
                        rpts3[12 + i] =
                            vector6((fHTW2 + iglica[i].x) * cos2 + iglica[i].y * sin2,
                                    -(fHTW2 + iglica[i].x) * sin2 + iglica[i].y * cos2, iglica[i].z,
                                    +iglica[i].n.x * cos2 + iglica[i].n.y * sin2,
                                    -iglica[i].n.x * sin2 + iglica[i].n.y * cos2, 0.0);
                        rpts4[23 - i] =
                            vector6((-fHTW2 - iglica[i].x) * cos2 + iglica[i].y * sin2,
                                    -(-fHTW2 - iglica[i].x) * sin2 + iglica[i].y * cos2,
                                    iglica[i].z, -iglica[i].n.x * cos2 + iglica[i].n.y * sin2,
                                    +iglica[i].n.x * sin2 + iglica[i].n.y * cos2, 0.0);
                    }
                // McZapkie-130302 - poprawione rysowanie szyn
                if (SwitchExtension->RightSwitch)
                { // zwrotnica prawa
                    glBindTexture(GL_TEXTURE_2D, TextureID1);
                    SwitchExtension->Segments[0]->RenderLoft(rpts1, nnumPts, fTexLength,
                                                             2); // prawa szyna za iglicą
                    SwitchExtension->Segments[0]->RenderSwitchRail(
                        rpts1, rpts3, nnumPts, fTexLength, 2,
                        SwitchExtension->fOffset2); // prawa iglica
                    SwitchExtension->Segments[0]->RenderLoft(
                        rpts2, nnumPts, fTexLength); // lewa szyna normalnie cała
                    if (TextureID2 != TextureID1) // nie wiadomo, czy OpenGL to optymalizuje
                        glBindTexture(GL_TEXTURE_2D, TextureID2);
                    SwitchExtension->Segments[1]->RenderLoft(
                        rpts1, nnumPts, fTexLength); // prawa szyna normalna cała
                    SwitchExtension->Segments[1]->RenderLoft(rpts2, nnumPts, fTexLength,
                                                             2); // lewa szyna za iglicą
                    SwitchExtension->Segments[1]->RenderSwitchRail(
                        rpts2, rpts4, nnumPts, fTexLength, 2,
                        -fMaxOffset + SwitchExtension->fOffset1); // lewa iglica
                }
                else
                { // lewa kiedyś działała lepiej niż prawa
                    glBindTexture(GL_TEXTURE_2D, TextureID1);
                    SwitchExtension->Segments[0]->RenderLoft(
                        rpts1, nnumPts, fTexLength); // prawa szyna normalna cała
                    SwitchExtension->Segments[0]->RenderLoft(rpts2, nnumPts, fTexLength,
                                                             2); // lewa szyna za iglicą
                    SwitchExtension->Segments[0]->RenderSwitchRail(
                        rpts2, rpts4, nnumPts, fTexLength, 2,
                        -SwitchExtension->fOffset2); // lewa iglica
                    if (TextureID2 != TextureID1) // nie wiadomo, czy OpenGL to optymalizuje
                        glBindTexture(GL_TEXTURE_2D, TextureID2);
                    SwitchExtension->Segments[1]->RenderLoft(rpts1, nnumPts, fTexLength,
                                                             2); // prawa szyna za iglicą
                    SwitchExtension->Segments[1]->RenderSwitchRail(
                        rpts1, rpts3, nnumPts, fTexLength, 2,
                        fMaxOffset - SwitchExtension->fOffset1); // prawa iglica
                    SwitchExtension->Segments[1]->RenderLoft(
                        rpts2, nnumPts, fTexLength); // lewa szyna normalnie cała
                }
            }
            break;
        }
    } // koniec obsługi torów
    break;
    case 2: // McZapkie-260302 - droga - rendering
        // McZapkie:240702-zmieniony zakres widzialnosci
        switch (eType) // dalej zależnie od typu
        {
        case tt_Normal: // drogi proste, bo skrzyżowania osobno
        {
            vector6 bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków
            if (TextureID1 || TextureID2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma
            { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100%
                double max = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości
                double map1 = max > 0.0 ? fHTW / max : 0.5; // obcięcie tekstury od strony 1
                double map2 = max > 0.0 ? fHTW2 / max : 0.5; // obcięcie tekstury od strony 2
                if (iTrapezoid) // trapez albo przechyłki
                { // nawierzchnia trapezowata
                    Segment->GetRolls(roll1, roll2);
                    bpts1[0] = vector6(fHTW * cos(roll1), -fHTW * sin(roll1), 0.5 - map1,
                                       sin(roll1), cos(roll1), 0.0); // lewy brzeg początku
                    bpts1[1] = vector6(-bpts1[0].x, -bpts1[0].y, 0.5 + map1, -sin(roll1),
                                       cos(roll1), 0.0); // prawy brzeg początku symetrycznie
                    bpts1[2] = vector6(fHTW2 * cos(roll2), -fHTW2 * sin(roll2), 0.5 - map2,
                                       sin(roll2), cos(roll2), 0.0); // lewy brzeg końca
                    bpts1[3] = vector6(-bpts1[2].x, -bpts1[2].y, 0.5 + map2, -sin(roll2),
                                       cos(roll2), 0.0); // prawy brzeg początku symetrycznie
                }
                else
                {
                    bpts1[0] = vector6(fHTW, 0.0, 0.5 - map1, 0.0, 1.0, 0.0);
                    bpts1[1] = vector6(-fHTW, 0.0, 0.5 + map1, 0.0, 1.0, 0.0);
                }
            }
            if (TextureID1) // jeśli podana była tekstura, generujemy trójkąty
                if (tex ? TextureID1 == tex : true) // jeśli pasuje do grupy (tex)
                { // tworzenie trójkątów nawierzchni szosy
                    if (!tex)
                        glBindTexture(GL_TEXTURE_2D, TextureID1);
                    Segment->RenderLoft(bpts1, iTrapezoid ? -2 : 2, fTexLength);
                }
            if (TextureID2)
                if (tex ? TextureID2 == tex : true) // jeśli pasuje do grupy (tex)
                { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak
                    // w Midtown Madness 2?)
                    if (!tex)
                        glBindTexture(GL_TEXTURE_2D, TextureID2);
                    vector6 rpts1[6],
                        rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony
                    if (fTexHeight1 >= 0.0)
                    { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo
                        rpts1[0] = vector6(rozp, -fTexHeight1, 0.0); // lewy brzeg podstawy
                        rpts1[1] =
                            vector6(bpts1[0].x + side, bpts1[0].y, 0.5); // lewa krawędź załamania
                        rpts1[2] = vector6(bpts1[0].x, bpts1[0].y,
                                           1.0); // lewy brzeg pobocza (mapowanie może być inne
                        rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 1.0); // prawy brzeg pobocza
                        rpts2[1] =
                            vector6(bpts1[1].x - side, bpts1[1].y, 0.5); // prawa krawędź załamania
                        rpts2[2] = vector6(-rozp, -fTexHeight1, 0.0); // prawy brzeg podstawy
                        if (iTrapezoid) // trapez albo przechyłki
                        { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony
                            // odcinka
                            rpts1[3] =
                                vector6(rozp2, -fTexHeight2, 0.0); // lewy brzeg lewego pobocza
                            rpts1[4] =
                                vector6(bpts1[2].x + side2, bpts1[2].y, 0.5); // krawędź załamania
                            rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 1.0); // brzeg pobocza
                            rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 1.0);
                            rpts2[4] = vector6(bpts1[3].x - side2, bpts1[3].y, 0.5);
                            rpts2[5] =
                                vector6(-rozp2, -fTexHeight2, 0.0); // 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
                        double d =
                            -fTexHeight1 / 3.75; // krawężnik o wysokości 150mm jest pochylony 40mm
                        double max =
                            fTexRatio2 * fTexLength; // test: szerokość proporcjonalna do długości
                        double map1l = max > 0.0 ?
                                           side / max :
                                           0.484375; // obcięcie tekstury od lewej strony punktu 1
                        double map1r = max > 0.0 ?
                                           slop / max :
                                           0.484375; // obcięcie tekstury od prawej strony punktu 1
                        rpts1[0] = vector6(bpts1[0].x + slop, bpts1[0].y - fTexHeight1,
                                           0.515625 + map1r); // prawy brzeg prawego chodnika
                        rpts1[1] = vector6(bpts1[0].x + d, bpts1[0].y - fTexHeight1,
                                           0.515625); // prawy krawężnik u góry
                        rpts1[2] = vector6(bpts1[0].x, bpts1[0].y,
                                           0.515625 - d / 2.56); // prawy krawężnik u dołu
                        rpts2[0] = vector6(bpts1[1].x, bpts1[1].y,
                                           0.484375 + d / 2.56); // lewy krawężnik u dołu
                        rpts2[1] = vector6(bpts1[1].x - d, bpts1[1].y - fTexHeight1,
                                           0.484375); // lewy krawężnik u góry
                        rpts2[2] = vector6(bpts1[1].x - side, bpts1[1].y - fTexHeight1,
                                           0.484375 - map1l); // lewy brzeg lewego chodnika
                        if (iTrapezoid) // trapez albo przechyłki
                        { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony
                            // odcinka
                            slop2 = fabs((iTrapezoid & 2) ? slop2 :
                                                            slop); // szerokość chodnika po prawej
                            double map2l =
                                max > 0.0 ? side2 / max :
                                            0.484375; // obcięcie tekstury od lewej strony punktu 2
                            double map2r =
                                max > 0.0 ? slop2 / max :
                                            0.484375; // obcięcie tekstury od prawej strony punktu 2
                            rpts1[3] = vector6(bpts1[2].x + slop2, bpts1[2].y - fTexHeight2,
                                               0.515625 + map2r); // prawy brzeg prawego chodnika
                            rpts1[4] = vector6(bpts1[2].x + d, bpts1[2].y - fTexHeight2,
                                               0.515625); // prawy krawężnik u góry
                            rpts1[5] = vector6(bpts1[2].x, bpts1[2].y,
                                               0.515625 - d / 2.56); // prawy krawężnik u dołu
                            rpts2[3] = vector6(bpts1[3].x, bpts1[3].y,
                                               0.484375 + d / 2.56); // lewy krawężnik u dołu
                            rpts2[4] = vector6(bpts1[3].x - d, bpts1[3].y - fTexHeight2,
                                               0.484375); // lewy krawężnik u góry
                            rpts2[5] = vector6(bpts1[3].x - side2, bpts1[3].y - fTexHeight2,
                                               0.484375 - map2l); // lewy brzeg lewego chodnika
                        }
                    }
                    if (iTrapezoid) // trapez albo przechyłki
                    { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony
                        // odcinka
                        if ((fTexHeight1 >= 0.0) ? true : (slop != 0.0))
                            Segment->RenderLoft(rpts1, -3, fTexLength); // tylko jeśli jest z prawej
                        if ((fTexHeight1 >= 0.0) ? true : (side != 0.0))
                            Segment->RenderLoft(rpts2, -3, fTexLength); // tylko jeśli jest z lewej
                    }
                    else
                    { // pobocza zwykłe, brak przechyłki
                        if ((fTexHeight1 >= 0.0) ? true : (slop != 0.0))
                            Segment->RenderLoft(rpts1, 3, fTexLength);
                        if ((fTexHeight1 >= 0.0) ? true : (side != 0.0))
                            Segment->RenderLoft(rpts2, 3, fTexLength);
                    }
                }
            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
            vector3 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
            vector3 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 i, j; // ile punktów (może byc różna ilość punktów między drogami)
            if (!SwitchExtension->vPoints)
            { // jeśli tablica punktów nie jest jeszcze utworzona, zliczamy punkty i tworzymy ją
                if (SwitchExtension->iRoads == 3) // mogą być tylko 3 drogi zamiast 4
                    SwitchExtension->iPoints = 5 + SwitchExtension->Segments[0]->RaSegCount() +
                                               SwitchExtension->Segments[1]->RaSegCount() +
                                               SwitchExtension->Segments[2]->RaSegCount();
                else
                    SwitchExtension->iPoints =
                        5 + SwitchExtension->Segments[2]->RaSegCount() +
                        SwitchExtension->Segments[3]->RaSegCount() +
                        SwitchExtension->Segments[4]->RaSegCount() +
                        SwitchExtension->Segments[5]->RaSegCount(); // mogą być tylko 3 drogi
                SwitchExtension->vPoints =
                    new vector3[SwitchExtension->iPoints]; // tablica utworzona z zapasem, ale nie
                // wypełniona współrzędnymi
            }
            vector3 *b =
                SwitchExtension->bPoints ?
                    NULL :
                    SwitchExtension
                        ->vPoints; // zmienna robocza, NULL gdy tablica punktów już jest wypełniona
            vector6 bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków
            if (TextureID1 || TextureID2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma
            { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100%
                double max = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości
                double map1 = max > 0.0 ? fHTW / max : 0.5; // obcięcie tekstury od strony 1
                double map2 = max > 0.0 ? fHTW2 / max : 0.5; // obcięcie tekstury od strony 2
                // if (iTrapezoid) //trapez albo przechyłki
                { // nawierzchnia trapezowata
                    Segment->GetRolls(roll1, roll2);
                    bpts1[0] = vector6(fHTW * cos(roll1), -fHTW * sin(roll1), 0.5 - map1,
                                       sin(roll1), cos(roll1), 0.0); // lewy brzeg początku
                    bpts1[1] = vector6(-bpts1[0].x, -bpts1[0].y, 0.5 + map1, -sin(roll1),
                                       cos(roll1), 0.0); // prawy brzeg początku symetrycznie
                    bpts1[2] = vector6(fHTW2 * cos(roll2), -fHTW2 * sin(roll2), 0.5 - map2,
                                       sin(roll2), cos(roll2), 0.0); // lewy brzeg końca
                    bpts1[3] = vector6(-bpts1[2].x, -bpts1[2].y, 0.5 + map2, -sin(roll2),
                                       cos(roll2), 0.0); // prawy brzeg początku symetrycznie
                }
            }
            // 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 (TextureID2)
            // if (tex?TextureID2==tex:true) //jeśli pasuje do grupy (tex)
            { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w
                // Midtown Madness 2?)
                if (TextureID2)
                    if (!tex)
                        glBindTexture(GL_TEXTURE_2D, TextureID2);
                vector6 rpts1[6],
                    rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony
                // Ra 2014-07: trzeba to przerobić na pętlę i pobierać profile (przynajmniej 2..4) z
                // sąsiednich dróg
                if (fTexHeight1 >= 0.0)
                { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo
                    rpts1[0] = vector6(rozp, -fTexHeight1, 0.0); // lewy brzeg podstawy
                    rpts1[1] = vector6(bpts1[0].x + side, bpts1[0].y, 0.5); // lewa krawędź
                    // załamania
                    rpts1[2] = vector6(bpts1[0].x, bpts1[0].y,
                                       1.0); // lewy brzeg pobocza (mapowanie może być inne
                    rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 1.0); // prawy brzeg pobocza
                    rpts2[1] =
                        vector6(bpts1[1].x - side, bpts1[1].y, 0.5); // prawa krawędź załamania
                    rpts2[2] = vector6(-rozp, -fTexHeight1, 0.0); // prawy brzeg podstawy
                    // if (iTrapezoid) //trapez albo przechyłki
                    { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony
                        // odcinka
                        rpts1[3] = vector6(rozp2, -fTexHeight2, 0.0); // lewy brzeg lewego pobocza
                        rpts1[4] = vector6(bpts1[2].x + side2, bpts1[2].y, 0.5); // krawędź
                        // załamania
                        rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 1.0); // brzeg pobocza
                        rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 1.0);
                        rpts2[4] = vector6(bpts1[3].x - side2, bpts1[3].y, 0.5);
                        rpts2[5] = vector6(-rozp2, -fTexHeight2, 0.0); // 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
                    double d =
                        -fTexHeight1 / 3.75; // krawężnik o wysokości 150mm jest pochylony 40mm
                    double max =
                        fTexRatio2 * fTexLength; // test: szerokość proporcjonalna do długości
                    double map1l = max > 0.0 ?
                                       side / max :
                                       0.484375; // obcięcie tekstury od lewej strony punktu 1
                    double map1r = max > 0.0 ?
                                       slop / max :
                                       0.484375; // obcięcie tekstury od prawej strony punktu 1
                    rpts1[0] = vector6(bpts1[0].x + slop, bpts1[0].y - fTexHeight1,
                                       0.515625 + map1r); // prawy brzeg prawego chodnika
                    rpts1[1] = vector6(bpts1[0].x + d, bpts1[0].y - fTexHeight1,
                                       0.515625); // prawy krawężnik u góry
                    rpts1[2] = vector6(bpts1[0].x, bpts1[0].y,
                                       0.515625 - d / 2.56); // prawy krawężnik u dołu
                    rpts2[0] = vector6(bpts1[1].x, bpts1[1].y,
                                       0.484375 + d / 2.56); // lewy krawężnik u dołu
                    rpts2[1] = vector6(bpts1[1].x - d, bpts1[1].y - fTexHeight1,
                                       0.484375); // lewy krawężnik u góry
                    rpts2[2] = vector6(bpts1[1].x - side, bpts1[1].y - fTexHeight1,
                                       0.484375 - map1l); // lewy brzeg lewego chodnika
                    // if (iTrapezoid) //trapez albo przechyłki
                    { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony
                        // odcinka
                        slop2 =
                            fabs((iTrapezoid & 2) ? slop2 : slop); // szerokość chodnika po prawej
                        double map2l = max > 0.0 ?
                                           side2 / max :
                                           0.484375; // obcięcie tekstury od lewej strony punktu 2
                        double map2r = max > 0.0 ?
                                           slop2 / max :
                                           0.484375; // obcięcie tekstury od prawej strony punktu 2
                        rpts1[3] = vector6(bpts1[2].x + slop2, bpts1[2].y - fTexHeight2,
                                           0.515625 + map2r); // prawy brzeg prawego chodnika
                        rpts1[4] = vector6(bpts1[2].x + d, bpts1[2].y - fTexHeight2,
                                           0.515625); // prawy krawężnik u góry
                        rpts1[5] = vector6(bpts1[2].x, bpts1[2].y,
                                           0.515625 - d / 2.56); // prawy krawężnik u dołu
                        rpts2[3] = vector6(bpts1[3].x, bpts1[3].y,
                                           0.484375 + d / 2.56); // lewy krawężnik u dołu
                        rpts2[4] = vector6(bpts1[3].x - d, bpts1[3].y - fTexHeight2,
                                           0.484375); // lewy krawężnik u góry
                        rpts2[5] = vector6(bpts1[3].x - side2, bpts1[3].y - fTexHeight2,
                                           0.484375 - map2l); // lewy brzeg lewego chodnika
                    }
                }
                bool render = TextureID2 ? (tex ? TextureID2 == tex : true) :
                                           false; // renderować nie trzeba, ale trzeba wyznaczyć
                // punkty brzegowe nawierzchni
                // if (iTrapezoid) //trapez albo przechyłki
                if (SwitchExtension->iRoads == 4)
                { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka
                    if ((fTexHeight1 >= 0.0) ? true : (side != 0.0))
                        SwitchExtension->Segments[2]->RenderLoft(
                            rpts2, -3, fTexLength, 0, 1, &b, render); // tylko jeśli jest z lewej
                    if ((fTexHeight1 >= 0.0) ? true : (side != 0.0))
                        SwitchExtension->Segments[3]->RenderLoft(
                            rpts2, -3, fTexLength, 0, 1, &b, render); // tylko jeśli jest z lewej
                    if ((fTexHeight1 >= 0.0) ? true : (side != 0.0))
                        SwitchExtension->Segments[4]->RenderLoft(
                            rpts2, -3, fTexLength, 0, 1, &b, render); // tylko jeśli jest z lewej
                    if ((fTexHeight1 >= 0.0) ? true : (side != 0.0))
                        SwitchExtension->Segments[5]->RenderLoft(
                            rpts2, -3, fTexLength, 0, 1, &b, render); // tylko jeśli jest z lewej
                }
                else // to będzie ewentualnie dla prostego na skrzyżowaniu trzech dróg
                { // punkt 3 pokrywa się z punktem 1, jak w zwrotnicy; połączenie 1->2 nie musi być
                    // prostoliniowe
                    if ((fTexHeight1 >= 0.0) ? true : (side != 0.0)) // OK
                        SwitchExtension->Segments[2]->RenderLoft(rpts2, -3, fTexLength, 0, 1, &b,
                                                                 render); // z P2 do P4
                    if ((fTexHeight1 >= 0.0) ? true : (side != 0.0)) // OK
                        SwitchExtension->Segments[1]->RenderLoft(
                            rpts2, -3, fTexLength, 0, 1, &b, render); // z P4 do P3=P1 (odwrócony)
                    if ((fTexHeight1 >= 0.0) ? true : (side != 0.0)) // OK
                        SwitchExtension->Segments[0]->RenderLoft(rpts2, -3, fTexLength, 0, 1, &b,
                                                                 render); // z P1 do P2
                    /*
                    if ((fTexHeight1>=0.0)?true:(slop!=0.0))
                     Segment->RenderLoft(rpts1,3,fTexLength);
                    if ((fTexHeight1>=0.0)?true:(side!=0.0))
                     Segment->RenderLoft(rpts2,3,fTexLength);
                    */
                }
            }
            // renderowanie nawierzchni na końcu
            double sina0 = sin(a[0]), cosa0 = cos(a[0]);
            double u, v;
            if (!SwitchExtension->bPoints) // jeśli tablica nie wypełniona
                if (b) // ale jest wskaźnik do tablicy - może nie być?
                { // coś się gubi w obliczeniach na wskaźnikach
                    i = (int((void *)(b)) - int((void *)(SwitchExtension->vPoints))) /
                        sizeof(vector3); // ustalenie liczby punktów, bo mogło wyjść inaczej niż
                    // policzone z góry
                    if (i > 0)
                    { // jeśli zostało to właśnie utworzone
                        if (SwitchExtension->iPoints > i) // jeśli wyszło mniej niż było miejsca
                            SwitchExtension->iPoints = i; // domknięcie wachlarza
                        else
                            --SwitchExtension->iPoints; // jak tutaj wejdzie, to błąd jest - zrobić
                        // miejsce na powtórzenie pierwszego punktu
                        // na końcu
                        SwitchExtension->vPoints[SwitchExtension->iPoints++] =
                            SwitchExtension->vPoints[0];
                        SwitchExtension->bPoints = true; // tablica punktów została wypełniona
                    }
                }
            if (TextureID1) // jeśli podana tekstura nawierzchni
                if (tex ? TextureID1 == tex : true) // jeśli pasuje do grupy (tex)
                {
                    if (!tex)
                        glBindTexture(GL_TEXTURE_2D, TextureID1);
                    glBegin(GL_TRIANGLE_FAN); // takie kółeczko będzie
                    glNormal3f(0, 1, 0);
                    glTexCoord2f(0.5, 0.5); //środek tekstury na środku skrzyżowania
                    glVertex3f(oxz.x, oxz.y, oxz.z);
                    for (i = SwitchExtension->iPoints - 1; i >= 0; --i)
                    // for (i=0;i<SwitchExtension->iPoints;++i)
                    {
                        glNormal3f(0, 1, 0);
                        u = (SwitchExtension->vPoints[i].x - oxz.x) /
                            fTexLength; // mapowanie we współrzędnych scenerii
                        v = (SwitchExtension->vPoints[i].z - oxz.z) / (fTexRatio1 * fTexLength);
                        glTexCoord2f(cosa0 * u + sina0 * v + 0.5, -sina0 * u + cosa0 * v + 0.5);
                        glVertex3f(SwitchExtension->vPoints[i].x, SwitchExtension->vPoints[i].y,
                                   SwitchExtension->vPoints[i].z);
                    }
                    glEnd();
                }
            break;
        }
        }
        break;
    case 4: // McZapkie-260302 - rzeka- rendering
        // Ra: rzeki na razie bez zmian, przechyłki na pewno nie mają
        // Ra: przemyśleć wyrównanie u góry trawą do czworoboku
        vector6 bpts1[numPts] = {vector6(fHTW, 0.0, 0.0), vector6(fHTW, 0.2, 0.33),
                                 vector6(-fHTW, 0.0, 0.67), vector6(-fHTW, 0.0, 1.0)};
        // Ra: dziwnie ten kształt wygląda
        if (TextureID1)
            if (tex ? TextureID1 == tex : true) // jeśli pasuje do grupy (tex)
            {
                if (!tex)
                    glBindTexture(GL_TEXTURE_2D, TextureID1);
                Segment->RenderLoft(bpts1, numPts, fTexLength);
            }
        if (TextureID2)
            if (tex ? TextureID2 == tex : true) // jeśli pasuje do grupy (tex)
            { // brzegi rzeki prawie jak pobocze derogi, tylko inny znak ma wysokość
                // znak jest zmieniany przy wczytywaniu, więc tu musi byc minus fTexHeight
                vector6 rpts1[3] = {vector6(rozp, -fTexHeight1, 0.0),
                                    vector6(fHTW + side, 0.0, 0.5), vector6(fHTW, 0.0, 1.0)};
                vector6 rpts2[3] = {vector6(-fHTW, 0.0, 1.0), vector6(-fHTW - side, 0.0, 0.5),
                                    vector6(-rozp, -fTexHeight1, 0.0)}; // Ra: po kiego 0.1?
                if (!tex)
                    glBindTexture(GL_TEXTURE_2D, TextureID2); // brzeg rzeki
                Segment->RenderLoft(rpts1, 3, fTexLength);
                Segment->RenderLoft(rpts2, 3, fTexLength);
            }
        break;
    }
    if (!tex)
        if (Global::bManageNodes)
            glEndList();
};

void TTrack::Release()
{
    if (DisplayListID)
        glDeleteLists(DisplayListID, 1);
    DisplayListID = 0;
};

void TTrack::Render()
{
    if (bVisible) // Ra: tory są renderowane sektorami i nie ma sensu każdorazowo liczyć odległości
    {
        if (!DisplayListID)
        {
            Compile();
            if (Global::bManageNodes)
                ResourceManager::Register(this);
        };
        SetLastUsage(Timer::GetSimulationTime());
        EnvironmentSet(); // oświetlenie nie może być skompilowane, bo może się zmieniać z czasem
        glCallList(DisplayListID);
        EnvironmentReset(); // ustawienie oświetlenia na zwykłe
        if (InMovement())
            Release(); // zwrotnica w trakcie animacji do odrysowania
    };
//#ifdef _DEBUG
#if 0
 if (DebugModeFlag && ScannedFlag) //McZapkie-230702
 //if (iNumDynamics) //będzie kreska na zajętym torze
 {
  vector3 pos1,pos2,pos3;
  glDisable(GL_DEPTH_TEST);
      glDisable(GL_LIGHTING);
  glColor3ub(255,0,0);
  glBindTexture(GL_TEXTURE_2D,0);
  glBegin(GL_LINE_STRIP);
      pos1=Segment->FastGetPoint_0();
      pos2=Segment->FastGetPoint(0.5);
      pos3=Segment->FastGetPoint_1();
      glVertex3f(pos1.x,pos1.y,pos1.z);
      glVertex3f(pos2.x,pos2.y+10,pos2.z);
      glVertex3f(pos3.x,pos3.y,pos3.z);
  glEnd();
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  ScannedFlag=false;
 }
#endif
    // glLightfv(GL_LIGHT0,GL_AMBIENT,Global::ambientDayLight);
    // glLightfv(GL_LIGHT0,GL_DIFFUSE,Global::diffuseDayLight);
    // glLightfv(GL_LIGHT0,GL_SPECULAR,Global::specularDayLight);
};

bool TTrack::CheckDynamicObject(TDynamicObject *Dynamic)
{ // sprawdzenie, czy pojazd jest przypisany do toru
    for (int i = 0; i < iNumDynamics; i++)
        if (Dynamic == Dynamics[i])
            return true;
    return false;
};

bool TTrack::RemoveDynamicObject(TDynamicObject *Dynamic)
{ // usunięcie pojazdu z listy przypisanych do toru
    for (int i = 0; i < iNumDynamics; i++)
    { // sprawdzanie wszystkich po kolei
        if (Dynamic == Dynamics[i])
        { // znaleziony, przepisanie następnych, żeby dziur nie było
            --iNumDynamics;
            for (i; i < iNumDynamics; i++)
                Dynamics[i] = Dynamics[i + 1];
            if (Global::iMultiplayer) // jeśli multiplayer
                if (!iNumDynamics) // jeśli już nie ma żadnego
                    if (pMyNode->asName != "none")
                        Global::pGround->WyslijString(
                            pMyNode->asName, 9); // przekazanie informacji o zwolnieniu toru
            return true;
        }
    }
    Error("Cannot remove dynamic from track");
    return false;
}

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
                TAnimContainer *ac =
                    SwitchExtension->pModel ? SwitchExtension->pModel->GetContainer(NULL) : NULL;
                return ac ?
                           (ac->AngleGet() != SwitchExtension->fOffset) ||
                               !(ac->TransGet() == SwitchExtension->vTrans) :
                           false;
                // return true; //jeśli jest taki obiekt
            }
    }
    return false;
};
void TTrack::RaAssign(TGroundNode *gn, TAnimContainer *ac){
    // Ra: wiązanie toru z modelem obrotnicy
    // if (eType==tt_Table) SwitchExtension->pAnim=p;
};
void TTrack::RaAssign(TGroundNode *gn, TAnimModel *am, TEvent *done, TEvent *joined)
{ // Ra: wiązanie toru z modelem obrotnicy
    if (eType == tt_Table)
    {
        SwitchExtension->pModel = am;
        SwitchExtension->pMyNode = gn;
        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)
            if (am->GetContainer(NULL)) // może nie być?
                am->GetContainer(NULL)->EventAssign(done); // zdarzenie zakończenia animacji
    }
};

int TTrack::RaArrayPrepare()
{ // przygotowanie tablic do skopiowania do VBO (zliczanie wierzchołków)
    if (bVisible) // o ile w ogóle widać
        switch (iCategoryFlag & 15)
        {
        case 1: // tor
            if (eType == tt_Switch) // dla zwrotnicy tylko szyny
                return 48 * ((TextureID1 ? SwitchExtension->Segments[0]->RaSegCount() : 0) +
                             (TextureID2 ? SwitchExtension->Segments[1]->RaSegCount() : 0));
            else // dla toru podsypka plus szyny
                return (Segment->RaSegCount()) * ((TextureID1 ? 48 : 0) + (TextureID2 ? 8 : 0));
        case 2: // droga
            if (eType == tt_Cross) // tylko dla skrzyżowania dróg
            { // specjalny sposób obliczania liczby wierzchołków w skrzyżowaniu
                // int n=0; //wierzchołki wewnętrzne do generowania nawierzchni
                // int b=0; //wierzchołki do generowania boków
                if (fTexHeight1 >= 0) // jeśli fTexHeight1<0, to są chodniki i może któregoś nie być
                { // normalne pobocze, na razie się składa z
                    return (Segment->RaSegCount()) * ((TextureID1 ? 4 : 0) + (TextureID2 ? 12 : 0));
                }
                else
                    return (Segment->RaSegCount()) *
                           ((TextureID1 ? 4 : 0) +
                            (TextureID2 ? (fTexWidth != 0.0 ? 6 : 0) + (fTexSlope != 0.0 ? 6 : 0) :
                                          0));
            }
            else // standardowo dla zwykłej drogi
                if (fTexHeight1 >= 0) // jeśli fTexHeight1<0, to są chodniki i może któregoś nie być
                return (Segment->RaSegCount()) *
                       ((TextureID1 ? 4 : 0) + (TextureID2 ? 12 : 0)); // może nie być poziomego!
            else
                return (Segment->RaSegCount()) *
                       ((TextureID1 ? 4 : 0) +
                        (TextureID2 ? (fTexWidth != 0.0 ? 6 : 0) + (fTexSlope != 0.0 ? 6 : 0) : 0));
        case 4: // rzeki do przemyślenia
            return (Segment->RaSegCount()) * ((TextureID1 ? 4 : 0) + (TextureID2 ? 12 : 0));
        }
    return 0;
};

void TTrack::RaArrayFill(CVertNormTex *Vert, const CVertNormTex *Start)
{ // wypełnianie tablic VBO
    // Ra: trzeba rozdzielić szyny od podsypki, aby móc grupować wg tekstur
    double fHTW = 0.5 * fabs(fTrackWidth);
    double side = fabs(fTexWidth); // szerokść podsypki na zewnątrz szyny albo pobocza
    double slop = fabs(fTexSlope); // brzeg zewnętrzny
    double rozp = fHTW + side + slop; // brzeg zewnętrzny
    double hypot1 = hypot(slop, fTexHeight1); // rozmiar pochylenia do liczenia normalnych
    if (hypot1 == 0.0)
        hypot1 = 1.0;
    vector3 normal1 = vector3(fTexSlope / hypot1, fTexHeight1 / hypot1, 0.0); // wektor normalny
    double fHTW2, side2, slop2, rozp2, fTexHeight2, hypot2;
    vector3 normal2;
    if (iTrapezoid & 2) // ten bit oznacza, że istnieje odpowiednie pNext
    { // Ra: jest OK
        fHTW2 = 0.5 * fabs(trNext->fTrackWidth); // połowa rozstawu/nawierzchni
        side2 = fabs(trNext->fTexWidth);
        slop2 = fabs(trNext->fTexSlope); // nie jest używane później
        rozp2 = fHTW2 + side2 + slop2;
        fTexHeight2 = trNext->fTexHeight1;
        hypot2 = hypot(slop2, fTexHeight2);
        if (hypot2 == 0.0)
            hypot2 = 1.0;
        normal2 = vector3(trNext->fTexSlope / hypot2, fTexHeight2 / hypot2, 0.0);
    }
    else // gdy nie ma następnego albo jest nieodpowiednim końcem podpięty
    {
        fHTW2 = fHTW;
        side2 = side;
        slop2 = slop;
        rozp2 = rozp;
        fTexHeight2 = fTexHeight1;
        hypot2 = hypot1;
        normal2 = normal1;
    }
    double roll1, roll2;
    switch (iCategoryFlag & 15)
    {
    case 1: // tor
    {
        if (Segment)
            Segment->GetRolls(roll1, roll2);
        else
            roll1 = roll2 = 0.0; // dla zwrotnic
        double sin1 = sin(roll1), cos1 = cos(roll1), sin2 = sin(roll2), cos2 = cos(roll2);
        // zwykla szyna: //Ra: czemu główki są asymetryczne na wysokości 0.140?
        vector6 rpts1[24], rpts2[24], rpts3[24], rpts4[24];
        int i;
        for (i = 0; i < 12; ++i)
        {
            rpts1[i] = vector6((fHTW + szyna[i].x) * cos1 + szyna[i].y * sin1,
                               -(fHTW + szyna[i].x) * sin1 + szyna[i].y * cos1, szyna[i].z,
                               +szyna[i].n.x * cos1 + szyna[i].n.y * sin1,
                               -szyna[i].n.x * sin1 + szyna[i].n.y * cos1, 0.0);
            rpts2[11 - i] = vector6((-fHTW - szyna[i].x) * cos1 + szyna[i].y * sin1,
                                    -(-fHTW - szyna[i].x) * sin1 + szyna[i].y * cos1, szyna[i].z,
                                    -szyna[i].n.x * cos1 + szyna[i].n.y * sin1,
                                    +szyna[i].n.x * sin1 + szyna[i].n.y * cos1, 0.0);
        }
        if (iTrapezoid) // trapez albo przechyłki, to oddzielne punkty na końcu
            for (i = 0; i < 12; ++i)
            {
                rpts1[12 + i] = vector6((fHTW2 + szyna[i].x) * cos2 + szyna[i].y * sin2,
                                        -(fHTW2 + szyna[i].x) * sin2 + szyna[i].y * cos2,
                                        szyna[i].z, +szyna[i].n.x * cos2 + szyna[i].n.y * sin2,
                                        -szyna[i].n.x * sin2 + szyna[i].n.y * cos2, 0.0);
                rpts2[23 - i] = vector6((-fHTW2 - szyna[i].x) * cos2 + szyna[i].y * sin2,
                                        -(-fHTW2 - szyna[i].x) * sin2 + szyna[i].y * cos2,
                                        szyna[i].z, -szyna[i].n.x * cos2 + szyna[i].n.y * sin2,
                                        +szyna[i].n.x * sin2 + szyna[i].n.y * cos2, 0.0);
            }
        switch (eType) // dalej zależnie od typu
        {
        case tt_Table: // obrotnica jak zwykły tor, tylko animacja dochodzi
            SwitchExtension->iLeftVBO = Vert - Start; // indeks toru obrotnicy
        case tt_Normal:
            if (TextureID2)
            { // podsypka z podkładami jest tylko dla zwykłego toru
                vector6 bpts1[8]; // punkty głównej płaszczyzny nie przydają się do robienia boków
                if (fTexLength == 4.0) // jeśli stare mapowanie
                { // stare mapowanie z różną gęstością pikseli i oddzielnymi teksturami na każdy
                    // profil
                    if (iTrapezoid) // trapez albo przechyłki
                    { // podsypka z podkladami trapezowata
                        // 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
                        bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.00, -0.707, 0.707,
                                           0.0); // lewy brzeg
                        bpts1[1] = vector6((fHTW + side) * cos1, -(fHTW + side) * sin1 - 0.18, 0.33,
                                           -0.707, 0.707, 0.0); // krawędź załamania
                        bpts1[2] = vector6(-bpts1[1].x, +(fHTW + side) * sin1 - 0.18, 0.67, 0.707,
                                           0.707, 0.0); // prawy brzeg początku symetrycznie
                        bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 1.00, 0.707, 0.707,
                                           0.0); // prawy skos
                        // końcowy przekrój
                        bpts1[4] = vector6(rozp2, -fTexHeight2 - 0.18, 0.00, -0.707, 0.707,
                                           0.0); // lewy brzeg
                        bpts1[5] = vector6((fHTW2 + side2) * cos2, -(fHTW2 + side2) * sin2 - 0.18,
                                           0.33, -0.707, 0.707, 0.0); // krawędź załamania
                        bpts1[6] = vector6(-bpts1[5].x, +(fHTW2 + side2) * sin2 - 0.18, 0.67, 0.707,
                                           0.707, 0.0); // prawy brzeg początku symetrycznie
                        bpts1[7] = vector6(-rozp2, -fTexHeight2 - 0.18, 1.00, 0.707, 0.707,
                                           0.0); // prawy skos
                    }
                    else
                    {
                        bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.0, -0.707, 0.707,
                                           0.0); // lewy brzeg
                        bpts1[1] = vector6(fHTW + side, -0.18, 0.33, -0.707, 0.707,
                                           0.0); // krawędź załamania
                        bpts1[2] = vector6(-fHTW - side, -0.18, 0.67, 0.707, 0.707, 0.0); // druga
                        bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 1.0, 0.707, 0.707,
                                           0.0); // prawy skos
                    }
                }
                else
                { // mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa fTexLength
                    double max = fTexRatio2 * fTexLength; // szerokość proporcjonalna do długości
                    double map11 = max > 0.0 ? (fHTW + side) / max : 0.25; // załamanie od strony 1
                    double map12 =
                        max > 0.0 ? (fHTW + side + hypot1) / max : 0.5; // brzeg od strony 1
                    if (iTrapezoid) // trapez albo przechyłki
                    { // podsypka z podkladami trapezowata
                        double map21 =
                            max > 0.0 ? (fHTW2 + side2) / max : 0.25; // załamanie od strony 2
                        double map22 =
                            max > 0.0 ? (fHTW2 + side2 + hypot2) / max : 0.5; // 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
                        bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.5 - map12, normal1.x,
                                           -normal1.y, 0.0); // lewy brzeg
                        bpts1[1] = vector6((fHTW + side) * cos1, -(fHTW + side) * sin1 - 0.18,
                                           0.5 - map11, 0.0, 1.0, 0.0); // krawędź załamania
                        bpts1[2] = vector6(-bpts1[1].x, +(fHTW + side) * sin1 - 0.18, 0.5 + map11,
                                           0.0, 1.0, 0.0); // prawy brzeg początku symetrycznie
                        bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 0.5 + map12, -normal1.x,
                                           -normal1.y, 0.0); // prawy skos
                        // przekrój końcowy
                        bpts1[4] = vector6(rozp2, -fTexHeight2 - 0.18, 0.5 - map22, normal2.x,
                                           -normal2.y, 0.0); // lewy brzeg
                        bpts1[5] = vector6((fHTW2 + side2) * cos2, -(fHTW2 + side2) * sin2 - 0.18,
                                           0.5 - map21, 0.0, 1.0, 0.0); // krawędź załamania
                        bpts1[6] = vector6(-bpts1[5].x, +(fHTW2 + side2) * sin2 - 0.18, 0.5 + map21,
                                           0.0, 1.0, 0.0); // prawy brzeg początku symetrycznie
                        bpts1[7] = vector6(-rozp2, -fTexHeight2 - 0.18, 0.5 + map22, -normal2.x,
                                           -normal2.y, 0.0); // prawy skos
                    }
                    else
                    {
                        bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.5 - map12, +normal1.x,
                                           -normal1.y, 0.0); // lewy brzeg
                        bpts1[1] = vector6(fHTW + side, -0.18, 0.5 - map11, +normal1.x, -normal1.y,
                                           0.0); // krawędź załamania
                        bpts1[2] = vector6(-fHTW - side, -0.18, 0.5 + map11, -normal1.x, -normal1.y,
                                           0.0); // druga
                        bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 0.5 + map12, -normal1.x,
                                           -normal1.y, 0.0); // prawy skos
                    }
                }
                Segment->RaRenderLoft(Vert, bpts1, iTrapezoid ? -4 : 4, fTexLength);
            }
            if (TextureID1)
            { // szyny - generujemy dwie, najwyżej rysować się będzie jedną
                Segment->RaRenderLoft(Vert, rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength);
                Segment->RaRenderLoft(Vert, rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength);
            }
            break;
        case tt_Switch: // dla zwrotnicy dwa razy szyny
            if (TextureID1) // Ra: !!!! tu jest do poprawienia
            { // iglice liczone tylko dla zwrotnic
                vector6 rpts3[24], rpts4[24];
                for (i = 0; i < 12; ++i)
                {
                    rpts3[i] =
                        vector6(+(fHTW + iglica[i].x) * cos1 + iglica[i].y * sin1,
                                -(+fHTW + iglica[i].x) * sin1 + iglica[i].y * cos1, iglica[i].z);
                    rpts3[i + 12] =
                        vector6(+(fHTW2 + szyna[i].x) * cos2 + szyna[i].y * sin2,
                                -(+fHTW2 + szyna[i].x) * sin2 + iglica[i].y * cos2, szyna[i].z);
                    rpts4[11 - i] =
                        vector6((-fHTW - iglica[i].x) * cos1 + iglica[i].y * sin1,
                                -(-fHTW - iglica[i].x) * sin1 + iglica[i].y * cos1, iglica[i].z);
                    rpts4[23 - i] =
                        vector6((-fHTW2 - szyna[i].x) * cos2 + szyna[i].y * sin2,
                                -(-fHTW2 - szyna[i].x) * sin2 + iglica[i].y * cos2, szyna[i].z);
                }
                if (SwitchExtension->RightSwitch)
                { // nowa wersja z SPKS, ale odwrotnie lewa/prawa
                    SwitchExtension->iLeftVBO = Vert - Start; // indeks lewej iglicy
                    SwitchExtension->Segments[0]->RaRenderLoft(Vert, rpts3, -nnumPts, fTexLength, 0,
                                                               2, SwitchExtension->fOffset2);
                    SwitchExtension->Segments[0]->RaRenderLoft(Vert, rpts1, nnumPts, fTexLength, 2);
                    SwitchExtension->Segments[0]->RaRenderLoft(Vert, rpts2, nnumPts, fTexLength);
                    SwitchExtension->Segments[1]->RaRenderLoft(Vert, rpts1, nnumPts, fTexLength);
                    SwitchExtension->iRightVBO = Vert - Start; // indeks prawej iglicy
                    SwitchExtension->Segments[1]->RaRenderLoft(Vert, rpts4, -nnumPts, fTexLength, 0,
                                                               2, -fMaxOffset +
                                                                      SwitchExtension->fOffset1);
                    SwitchExtension->Segments[1]->RaRenderLoft(Vert, rpts2, nnumPts, fTexLength, 2);
                }
                else
                { // lewa działa lepiej niż prawa
                    SwitchExtension->Segments[0]->RaRenderLoft(
                        Vert, rpts1, nnumPts, fTexLength); // lewa szyna normalna cała
                    SwitchExtension->iLeftVBO = Vert - Start; // indeks lewej iglicy
                    SwitchExtension->Segments[0]->RaRenderLoft(
                        Vert, rpts4, -nnumPts, fTexLength, 0, 2,
                        -SwitchExtension->fOffset2); // prawa iglica
                    SwitchExtension->Segments[0]->RaRenderLoft(Vert, rpts2, nnumPts, fTexLength,
                                                               2); // prawa szyna za iglicą
                    SwitchExtension->iRightVBO = Vert - Start; // indeks prawej iglicy
                    SwitchExtension->Segments[1]->RaRenderLoft(
                        Vert, rpts3, -nnumPts, fTexLength, 0, 2,
                        fMaxOffset - SwitchExtension->fOffset1); // lewa iglica
                    SwitchExtension->Segments[1]->RaRenderLoft(Vert, rpts1, nnumPts, fTexLength,
                                                               2); // lewa szyna za iglicą
                    SwitchExtension->Segments[1]->RaRenderLoft(
                        Vert, rpts2, nnumPts, fTexLength); // prawa szyna normalnie cała
                }
            }
            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
        {
            vector6 bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków
            if (TextureID1 || TextureID2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma
            { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100%
                double max = (iCategoryFlag & 4) ?
                                 0.0 :
                                 fTexLength; // test: szerokość dróg proporcjonalna do długości
                double map1 = max > 0.0 ? fHTW / max : 0.5; // obcięcie tekstury od strony 1
                double map2 = max > 0.0 ? fHTW2 / max : 0.5; // obcięcie tekstury od strony 2
                if (iTrapezoid) // trapez albo przechyłki
                { // nawierzchnia trapezowata
                    Segment->GetRolls(roll1, roll2);
                    bpts1[0] = vector6(fHTW * cos(roll1), -fHTW * sin(roll1),
                                       0.5 - map1); // lewy brzeg początku
                    bpts1[1] = vector6(-bpts1[0].x, -bpts1[0].y,
                                       0.5 + map1); // prawy brzeg początku symetrycznie
                    bpts1[2] = vector6(fHTW2 * cos(roll2), -fHTW2 * sin(roll2),
                                       0.5 - map2); // lewy brzeg końca
                    bpts1[3] = vector6(-bpts1[2].x, -bpts1[2].y,
                                       0.5 + map2); // prawy brzeg początku symetrycznie
                }
                else
                {
                    bpts1[0] = vector6(fHTW, 0.0, 0.5 - map1); // zawsze standardowe mapowanie
                    bpts1[1] = vector6(-fHTW, 0.0, 0.5 + map1);
                }
            }
            if (TextureID1) // jeśli podana była tekstura, generujemy trójkąty
            { // tworzenie trójkątów nawierzchni szosy
                Segment->RaRenderLoft(Vert, bpts1, iTrapezoid ? -2 : 2, fTexLength);
            }
            if (TextureID2)
            { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w
                // Midtown Madness 2?)
                vector6 rpts1[6],
                    rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony
                if (fTexHeight1 >= 0.0)
                { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo
                    rpts1[0] = vector6(rozp, -fTexHeight1, 0.0); // lewy brzeg podstawy
                    rpts1[1] =
                        vector6(bpts1[0].x + side, bpts1[0].y, 0.5), // lewa krawędź załamania
                        rpts1[2] = vector6(bpts1[0].x, bpts1[0].y,
                                           1.0); // lewy brzeg pobocza (mapowanie może być inne
                    rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 1.0); // prawy brzeg pobocza
                    rpts2[1] =
                        vector6(bpts1[1].x - side, bpts1[1].y, 0.5); // prawa krawędź załamania
                    rpts2[2] = vector6(-rozp, -fTexHeight1, 0.0); // prawy brzeg podstawy
                    if (iTrapezoid) // trapez albo przechyłki
                    { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony
                        // odcinka
                        rpts1[3] = vector6(rozp2, -fTexHeight2, 0.0); // lewy brzeg lewego pobocza
                        rpts1[4] = vector6(bpts1[2].x + side2, bpts1[2].y, 0.5); // krawędź
                        // załamania
                        rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 1.0); // brzeg pobocza
                        rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 1.0);
                        rpts2[4] = vector6(bpts1[3].x - side2, bpts1[3].y, 0.5);
                        rpts2[5] = vector6(-rozp2, -fTexHeight2, 0.0); // prawy brzeg prawego
                        // pobocza
                        Segment->RaRenderLoft(Vert, rpts1, -3, fTexLength);
                        Segment->RaRenderLoft(Vert, rpts2, -3, fTexLength);
                    }
                    else
                    { // pobocza zwykłe, brak przechyłki
                        Segment->RaRenderLoft(Vert, rpts1, 3, fTexLength);
                        Segment->RaRenderLoft(Vert, rpts2, 3, fTexLength);
                    }
                }
                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
                    double d =
                        -fTexHeight1 / 3.75; // krawężnik o wysokości 150mm jest pochylony 40mm
                    double max =
                        fTexRatio2 * fTexLength; // test: szerokość proporcjonalna do długości
                    double map1l = max > 0.0 ?
                                       side / max :
                                       0.484375; // obcięcie tekstury od lewej strony punktu 1
                    double map1r = max > 0.0 ?
                                       slop / max :
                                       0.484375; // obcięcie tekstury od prawej strony punktu 1
                    double h1r = (slop > d) ? -fTexHeight1 : 0;
                    double h1l = (side > d) ? -fTexHeight1 : 0;
                    rpts1[0] = vector6(bpts1[0].x + slop, bpts1[0].y + h1r,
                                       0.515625 + map1r); // prawy brzeg prawego chodnika
                    rpts1[1] = vector6(bpts1[0].x + d, bpts1[0].y + h1r,
                                       0.515625); // prawy krawężnik u góry
                    rpts1[2] = vector6(bpts1[0].x, bpts1[0].y,
                                       0.515625 - d / 2.56); // prawy krawężnik u dołu
                    rpts2[0] = vector6(bpts1[1].x, bpts1[1].y,
                                       0.484375 + d / 2.56); // lewy krawężnik u dołu
                    rpts2[1] = vector6(bpts1[1].x - d, bpts1[1].y + h1l,
                                       0.484375); // lewy krawężnik u góry
                    rpts2[2] = vector6(bpts1[1].x - side, bpts1[1].y + h1l,
                                       0.484375 - map1l); // lewy brzeg lewego chodnika
                    if (iTrapezoid) // trapez albo przechyłki
                    { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony
                        // odcinka
                        slop2 =
                            fabs((iTrapezoid & 2) ? slop2 : slop); // szerokość chodnika po prawej
                        double map2l = max > 0.0 ?
                                           side2 / max :
                                           0.484375; // obcięcie tekstury od lewej strony punktu 2
                        double map2r = max > 0.0 ?
                                           slop2 / max :
                                           0.484375; // obcięcie tekstury od prawej strony punktu 2
                        double h2r = (slop2 > d) ? -fTexHeight2 : 0;
                        double h2l = (side2 > d) ? -fTexHeight2 : 0;
                        rpts1[3] = vector6(bpts1[2].x + slop2, bpts1[2].y + h2r,
                                           0.515625 + map2r); // prawy brzeg prawego chodnika
                        rpts1[4] = vector6(bpts1[2].x + d, bpts1[2].y + h2r,
                                           0.515625); // prawy krawężnik u góry
                        rpts1[5] = vector6(bpts1[2].x, bpts1[2].y,
                                           0.515625 - d / 2.56); // prawy krawężnik u dołu
                        rpts2[3] = vector6(bpts1[3].x, bpts1[3].y,
                                           0.484375 + d / 2.56); // lewy krawężnik u dołu
                        rpts2[4] = vector6(bpts1[3].x - d, bpts1[3].y + h2l,
                                           0.484375); // lewy krawężnik u góry
                        rpts2[5] = vector6(bpts1[3].x - side2, bpts1[3].y + h2l,
                                           0.484375 - map2l); // lewy brzeg lewego chodnika
                        if (slop != 0.0)
                            Segment->RaRenderLoft(Vert, rpts1, -3, fTexLength);
                        if (side != 0.0)
                            Segment->RaRenderLoft(Vert, rpts2, -3, fTexLength);
                    }
                    else
                    { // pobocza zwykłe, brak przechyłki
                        if (slop != 0.0)
                            Segment->RaRenderLoft(Vert, rpts1, 3, fTexLength);
                        if (side != 0.0)
                            Segment->RaRenderLoft(Vert, rpts2, 3, fTexLength);
                    }
                }
            }
        }
        }
        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
        {
            vector6 bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków
            if (TextureID1 || TextureID2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma
            { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100%
                double max = (iCategoryFlag & 4) ?
                                 0.0 :
                                 fTexLength; // test: szerokość dróg proporcjonalna do długości
                double map1 = max > 0.0 ? fHTW / max : 0.5; // obcięcie tekstury od strony 1
                double map2 = max > 0.0 ? fHTW2 / max : 0.5; // obcięcie tekstury od strony 2
                if (iTrapezoid) // trapez albo przechyłki
                { // nawierzchnia trapezowata
                    Segment->GetRolls(roll1, roll2);
                    bpts1[0] = vector6(fHTW * cos(roll1), -fHTW * sin(roll1),
                                       0.5 - map1); // lewy brzeg początku
                    bpts1[1] = vector6(-bpts1[0].x, -bpts1[0].y,
                                       0.5 + map1); // prawy brzeg początku symetrycznie
                    bpts1[2] = vector6(fHTW2 * cos(roll2), -fHTW2 * sin(roll2),
                                       0.5 - map2); // lewy brzeg końca
                    bpts1[3] = vector6(-bpts1[2].x, -bpts1[2].y,
                                       0.5 + map2); // prawy brzeg początku symetrycznie
                }
                else
                {
                    bpts1[0] = vector6(fHTW, 0.0, 0.5 - map1); // zawsze standardowe mapowanie
                    bpts1[1] = vector6(-fHTW, 0.0, 0.5 + map1);
                }
            }
            if (TextureID1) // jeśli podana była tekstura, generujemy trójkąty
            { // tworzenie trójkątów nawierzchni szosy
                Segment->RaRenderLoft(Vert, bpts1, iTrapezoid ? -2 : 2, fTexLength);
            }
            if (TextureID2)
            { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w
                // Midtown Madness 2?)
                vector6 rpts1[6],
                    rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony
                rpts1[0] = vector6(rozp, -fTexHeight1, 0.0); // lewy brzeg podstawy
                rpts1[1] = vector6(bpts1[0].x + side, bpts1[0].y, 0.5), // lewa krawędź załamania
                    rpts1[2] = vector6(bpts1[0].x, bpts1[0].y,
                                       1.0); // lewy brzeg pobocza (mapowanie może być inne
                rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 1.0); // prawy brzeg pobocza
                rpts2[1] = vector6(bpts1[1].x - side, bpts1[1].y, 0.5); // prawa krawędź załamania
                rpts2[2] = vector6(-rozp, -fTexHeight1, 0.0); // prawy brzeg podstawy
                if (iTrapezoid) // trapez albo przechyłki
                { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka
                    rpts1[3] = vector6(rozp2, -fTexHeight2, 0.0); // lewy brzeg lewego pobocza
                    rpts1[4] = vector6(bpts1[2].x + side2, bpts1[2].y, 0.5); // krawędź załamania
                    rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 1.0); // brzeg pobocza
                    rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 1.0);
                    rpts2[4] = vector6(bpts1[3].x - side2, bpts1[3].y, 0.5);
                    rpts2[5] = vector6(-rozp2, -fTexHeight2, 0.0); // prawy brzeg prawego pobocza
                    Segment->RaRenderLoft(Vert, rpts1, -3, fTexLength);
                    Segment->RaRenderLoft(Vert, rpts2, -3, fTexLength);
                }
                else
                { // pobocza zwykłe, brak przechyłki
                    Segment->RaRenderLoft(Vert, rpts1, 3, fTexLength);
                    Segment->RaRenderLoft(Vert, rpts2, 3, fTexLength);
                }
            }
        }
        }
        break;
    }
};

void TTrack::RaRenderVBO(int iPtr)
{ // renderowanie z użyciem VBO
    // Ra 2014-07: trzeba wymienić GL_TRIANGLE_STRIP na GL_TRIANGLES i renderować trójkąty sektora
    // dla kolejnych tekstur!
    EnvironmentSet();
    int seg;
    int i;
    switch (iCategoryFlag & 15)
    {
    case 1: // tor
        if (eType == tt_Switch) // dla zwrotnicy tylko szyny
        {
            if (TextureID1)
                if ((seg = SwitchExtension->Segments[0]->RaSegCount()) > 0)
                {
                    glBindTexture(GL_TEXTURE_2D, TextureID1); // szyny +
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 24 * i, 24);
                    iPtr += 24 * seg; // pominięcie lewej szyny
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 24 * i, 24);
                    iPtr += 24 * seg; // pominięcie prawej szyny
                }
            if (TextureID2)
                if ((seg = SwitchExtension->Segments[1]->RaSegCount()) > 0)
                {
                    glBindTexture(GL_TEXTURE_2D, TextureID2); // szyny -
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 24 * i, 24);
                    iPtr += 24 * seg; // pominięcie lewej szyny
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 24 * i, 24);
                }
        }
        else // dla toru podsypka plus szyny
        {
            if ((seg = Segment->RaSegCount()) > 0)
            {
                if (TextureID2)
                {
                    glBindTexture(GL_TEXTURE_2D, TextureID2); // podsypka
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 8 * i, 8);
                    iPtr += 8 * seg; // pominięcie podsypki
                }
                if (TextureID1)
                {
                    glBindTexture(GL_TEXTURE_2D, TextureID1); // szyny
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 24 * i, 24);
                    iPtr += 24 * seg; // pominięcie lewej szyny
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 24 * i, 24);
                }
            }
        }
        break;
    case 2: // droga
        if ((seg = Segment->RaSegCount()) > 0)
        {
            if (TextureID1)
            {
                glBindTexture(GL_TEXTURE_2D, TextureID1); // nawierzchnia
                for (i = 0; i < seg; ++i)
                {
                    glDrawArrays(GL_TRIANGLE_STRIP, iPtr, 4);
                    iPtr += 4;
                }
            }
            if (TextureID2)
            {
                glBindTexture(GL_TEXTURE_2D, TextureID2); // pobocze
                if (fTexHeight1 >= 0.0)
                { // normalna droga z poboczem
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 6 * i, 6);
                    iPtr += 6 * seg; // pominięcie lewego pobocza
                    for (i = 0; i < seg; ++i)
                        glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 6 * i, 6);
                }
                else
                { // z chodnikami o różnych szerokociach
                    if (fTexWidth != 0.0)
                    {
                        for (i = 0; i < seg; ++i)
                            glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 6 * i, 6);
                        iPtr += 6 * seg; // pominięcie lewego pobocza
                    }
                    if (fTexSlope != 0.0)
                        for (i = 0; i < seg; ++i)
                            glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 6 * i, 6);
                }
            }
        }
        break;
    case 4: // rzeki - jeszcze do przemyślenia
        if ((seg = Segment->RaSegCount()) > 0)
        {
            if (TextureID1)
            {
                glBindTexture(GL_TEXTURE_2D, TextureID1); // nawierzchnia
                for (i = 0; i < seg; ++i)
                {
                    glDrawArrays(GL_TRIANGLE_STRIP, iPtr, 4);
                    iPtr += 4;
                }
            }
            if (TextureID2)
            {
                glBindTexture(GL_TEXTURE_2D, TextureID2); // pobocze
                for (i = 0; i < seg; ++i)
                    glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 6 * i, 6);
                iPtr += 6 * seg; // pominięcie lewego pobocza
                for (i = 0; i < seg; ++i)
                    glDrawArrays(GL_TRIANGLE_STRIP, iPtr + 6 * i, 6);
            }
        }
        break;
    }
    EnvironmentReset();
};

void TTrack::EnvironmentSet()
{ // ustawienie zmienionego światła
    glColor3f(1.0f, 1.0f, 1.0f); // Ra: potrzebne to?
    if (eEnvironment)
    { // McZapkie-310702: zmiana oswietlenia w tunelu, wykopie
        GLfloat ambientLight[4] = {0.5f, 0.5f, 0.5f, 1.0f};
        GLfloat diffuseLight[4] = {0.5f, 0.5f, 0.5f, 1.0f};
        GLfloat specularLight[4] = {0.5f, 0.5f, 0.5f, 1.0f};
        switch (eEnvironment)
        { // modyfikacje oświetlenia zależnie od środowiska
        case e_canyon:
            for (int li = 0; li < 3; li++)
            {
                // ambientLight[li]= Global::ambientDayLight[li]*0.8; //0.7
                diffuseLight[li] = Global::diffuseDayLight[li] * 0.4; // 0.3
                specularLight[li] = Global::specularDayLight[li] * 0.5; // 0.4
            }
            // glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);
            glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
            glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
            break;
        case e_tunnel:
            for (int li = 0; li < 3; li++)
            {
                ambientLight[li] = Global::ambientDayLight[li] * 0.2;
                diffuseLight[li] = Global::diffuseDayLight[li] * 0.1;
                specularLight[li] = Global::specularDayLight[li] * 0.2;
            }
            glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
            glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
            glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
            break;
        }
    }
};

void TTrack::EnvironmentReset()
{ // przywrócenie domyślnego światła
    switch (eEnvironment)
    { // przywrócenie globalnych ustawień światła, o ile było zmienione
    case e_canyon: // wykop
    case e_tunnel: // tunel
        glLightfv(GL_LIGHT0, GL_AMBIENT, Global::ambientDayLight);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, Global::diffuseDayLight);
        glLightfv(GL_LIGHT0, GL_SPECULAR, Global::specularDayLight);
    }
};

void TTrack::RenderDyn()
{ // renderowanie nieprzezroczystych fragmentów pojazdów
    if (!iNumDynamics)
        return; // po co kombinować, jeśli nie ma pojazdów?
    // EnvironmentSet(); //Ra: pojazdy sobie same teraz liczą cienie
    for (int i = 0; i < iNumDynamics; i++)
        Dynamics[i]->Render(); // sam sprawdza, czy VBO; zmienia kontekst VBO!
    // EnvironmentReset();
};

void TTrack::RenderDynAlpha()
{ // renderowanie przezroczystych fragmentów pojazdów
    if (!iNumDynamics)
        return; // po co kombinować, jeśli nie ma pojazdów?
    // EnvironmentSet(); //Ra: pojazdy sobie same teraz liczą cienie
    for (int i = 0; i < iNumDynamics; i++)
        Dynamics[i]->RenderAlpha(); // sam sprawdza, czy VBO; zmienia kontekst VBO!
    // EnvironmentReset();
};

void TTrack::RenderDynSounds()
{ // odtwarzanie dźwięków pojazdów jest niezależne od ich wyświetlania
    for (int i = 0; i < iNumDynamics; i++)
        Dynamics[i]->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, double t, double 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.0) // prędkość liniowa ruchu iglic
                SwitchExtension->fOffsetSpeed = t; // prędkość łatwiej zgrać z animacją modelu
            if (d >= 0.0) // 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;
            if (SwitchExtension->pOwner ? SwitchExtension->pOwner->RaTrackAnimAdd(this) :
                                          true) // jeśli nie dodane do animacji
            { // nie ma się co bawić
                SwitchExtension->fOffset = SwitchExtension->fDesiredOffset;
                RaAnimate(); // przeliczenie położenia iglic; czy zadziała na niewyświetlanym
                // sektorze w VBO?
            }
            return true;
        }
        else if (eType == tt_Table)
        { // blokowanie (0, szukanie torów) lub odblokowanie (1, rozłączenie) obrotnicy
            if (i)
            { // 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 = NULL; // rozłączamy od Point2
                    else
                        trPrev->trPrev = NULL; // 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 = NULL; // rozłączamy od Point2
                    else
                        trNext->trPrev = NULL; // rozłączamy od Point1
                trNext = trPrev =
                    NULL; // 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
            }
            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
                Global::pGround->TrackJoin(SwitchExtension->pMyNode);
                if (trNext || trPrev)
                {
                    fVelocity = 6.0; // jazda dozwolona
                    if (trPrev)
                        if (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)
                        if (trNext->fVelocity == 0.0)
                            trNext->VelocitySet(6.0);
                    if (SwitchExtension->evPlus) // w starych sceneriach może nie być
                        Global::AddToQuery(SwitchExtension->evPlus,
                                           NULL); // potwierdzenie wykonania (np. odpala WZ)
                }
            }
            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)
                        Global::AddToQuery(SwitchExtension->evPlus, o); // dodanie do kolejki
                    break;
                case 1:
                    if (SwitchExtension->evMinus)
                        Global::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("Crossing from P"+AnsiString(from+1)+" into stop on "+pMyNode->asName);
        break;
    case 1: // left
        // WriteLog("Crossing from P"+AnsiString(from+1)+" to left on "+pMyNode->asName);
        i = (SwitchExtension->iRoads == 4) ? iLewo4[from] : iLewo3[from];
        break;
    case 2: // right
        // WriteLog("Crossing from P"+AnsiString(from+1)+" to right on "+pMyNode->asName);
        i = (SwitchExtension->iRoads == 4) ? iPrawo4[from] : iPrawo3[from];
        break;
    case 3: // stright
        // WriteLog("Crossing from P"+AnsiString(from+1)+" to straight on "+pMyNode->asName);
        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 (SwitchExtension)
    {
        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 = NULL; // 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
    if (SwitchExtension->pNextAnim)
        SwitchExtension->pNextAnim = SwitchExtension->pNextAnim->RaAnimate();
    bool m = true; // animacja trwa
    if (eType == tt_Switch) // dla zwrotnicy tylko szyny
    {
        double 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.00)
            SwitchExtension->fOffset2 = 0.0; // ograniczenie animacji wewnętrznej iglicy
        if (v < 0)
        { // 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
            }
        }
        if (Global::bUseVBO)
        { // dla OpenGL 1.4 odświeży się cały sektor, w późniejszych poprawiamy fragment
            if (Global::bOpenGL_1_5) // dla OpenGL 1.4 to się nie wykona poprawnie
                if (TextureID1) // Ra: !!!! tu jest do poprawienia
                { // iglice liczone tylko dla zwrotnic
                    vector6 rpts3[24], rpts4[24];
                    double fHTW = 0.5 * fabs(fTrackWidth);
                    double fHTW2 = fHTW; // Ra: na razie niech tak będzie
                    double cos1 = 1.0, sin1 = 0.0, cos2 = 1.0, sin2 = 0.0; // Ra: ...
                    for (int i = 0; i < 12; ++i)
                    {
                        rpts3[i] =
                            vector6((fHTW + iglica[i].x) * cos1 + iglica[i].y * sin1,
                                    -(fHTW + iglica[i].x) * sin1 + iglica[i].y * cos1, iglica[i].z);
                        rpts3[i + 12] =
                            vector6((fHTW2 + szyna[i].x) * cos2 + szyna[i].y * sin2,
                                    -(fHTW2 + szyna[i].x) * sin2 + iglica[i].y * cos2, szyna[i].z);
                        rpts4[11 - i] = vector6((-fHTW - iglica[i].x) * cos1 + iglica[i].y * sin1,
                                                -(-fHTW - iglica[i].x) * sin1 + iglica[i].y * cos1,
                                                iglica[i].z);
                        rpts4[23 - i] =
                            vector6((-fHTW2 - szyna[i].x) * cos2 + szyna[i].y * sin2,
                                    -(-fHTW2 - szyna[i].x) * sin2 + iglica[i].y * cos2, szyna[i].z);
                    }
                    CVertNormTex Vert[2 * 2 * 12]; // na razie 2 segmenty
                    CVertNormTex *v = Vert; // bo RaAnimate() modyfikuje wskaźnik
                    glGetBufferSubData(
                        GL_ARRAY_BUFFER, SwitchExtension->iLeftVBO * sizeof(CVertNormTex),
                        2 * 2 * 12 * sizeof(CVertNormTex), &Vert); // pobranie fragmentu bufora VBO
                    if (SwitchExtension->RightSwitch)
                    { // nowa wersja z SPKS, ale odwrotnie lewa/prawa
                        SwitchExtension->Segments[0]->RaAnimate(v, rpts3, -nnumPts, fTexLength, 0,
                                                                2, SwitchExtension->fOffset2);
                        glBufferSubData(GL_ARRAY_BUFFER,
                                        SwitchExtension->iLeftVBO * sizeof(CVertNormTex),
                                        2 * 2 * 12 * sizeof(CVertNormTex),
                                        &Vert); // wysłanie fragmentu bufora VBO
                        v = Vert;
                        glGetBufferSubData(GL_ARRAY_BUFFER,
                                           SwitchExtension->iRightVBO * sizeof(CVertNormTex),
                                           2 * 2 * 12 * sizeof(CVertNormTex),
                                           &Vert); // pobranie fragmentu bufora VBO
                        SwitchExtension->Segments[1]->RaAnimate(v, rpts4, -nnumPts, fTexLength, 0,
                                                                2, -fMaxOffset +
                                                                       SwitchExtension->fOffset1);
                    }
                    else
                    { // oryginalnie lewa działała lepiej niż prawa
                        SwitchExtension->Segments[0]->RaAnimate(
                            v, rpts4, -nnumPts, fTexLength, 0, 2,
                            -SwitchExtension->fOffset2); // prawa iglica
                        glBufferSubData(GL_ARRAY_BUFFER,
                                        SwitchExtension->iLeftVBO * sizeof(CVertNormTex),
                                        2 * 2 * 12 * sizeof(CVertNormTex),
                                        &Vert); // wysłanie fragmentu bufora VBO
                        v = Vert;
                        glGetBufferSubData(GL_ARRAY_BUFFER,
                                           SwitchExtension->iRightVBO * sizeof(CVertNormTex),
                                           2 * 2 * 12 * sizeof(CVertNormTex),
                                           &Vert); // pobranie fragmentu bufora VBO
                        SwitchExtension->Segments[1]->RaAnimate(
                            v, rpts3, -nnumPts, fTexLength, 0, 2,
                            fMaxOffset - SwitchExtension->fOffset1); // lewa iglica
                    }
                    glBufferSubData(
                        GL_ARRAY_BUFFER, SwitchExtension->iRightVBO * sizeof(CVertNormTex),
                        2 * 2 * 12 * sizeof(CVertNormTex), &Vert); // wysłanie fragmentu bufora VBO
                }
        }
        else // gdy Display List
            Release(); // niszczenie skompilowanej listy, aby się wygenerowała nowa
    }
    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
            // SwitchExtension->fOffset1=SwitchExtension->pAnim?SwitchExtension->pAnim->AngleGet():0.0;
            // //pobranie kąta z modelu
            TAnimContainer *ac = SwitchExtension->pModel ?
                                     SwitchExtension->pModel->GetContainer(NULL) :
                                     NULL; // pobranie głównego submodelu
            // if (ac) ac->EventAssign(SwitchExtension->evMinus); //event zakończenia animacji,
            // trochę bez sensu tutaj
            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 = ac->AngleGet(); // pobranie kąta z submodelu
                    double sina = -hlen * sin(DegToRad(SwitchExtension->fOffset)),
                           cosa = -hlen * cos(DegToRad(SwitchExtension->fOffset));
                    SwitchExtension->vTrans = ac->TransGet();
                    vector3 middle =
                        SwitchExtension->pMyNode->pCenter +
                        SwitchExtension->vTrans; // SwitchExtension->Segments[0]->FastGetPoint(0.5);
                    Segment->Init(middle + vector3(sina, 0.0, cosa),
                                  middle - vector3(sina, 0.0, cosa), 5.0); // nowy odcinek
                    for (int i = 0; i < iNumDynamics; i++)
                        Dynamics[i]->Move(0.000001); // minimalny ruch, aby przeliczyć pozycję i
                    // kąty
                    if (Global::bUseVBO)
                    { // dla OpenGL 1.4 odświeży się cały sektor, w późniejszych poprawiamy fragment
                        // aktualizacja pojazdów na torze
                        if (Global::bOpenGL_1_5) // dla OpenGL 1.4 to się nie wykona poprawnie
                        {
                            int size =
                                RaArrayPrepare(); // wielkość tabeli potrzebna dla tej obrotnicy
                            CVertNormTex *Vert = new CVertNormTex[size]; // bufor roboczy
                            // CVertNormTex *v=Vert; //zmieniane przez
                            RaArrayFill(Vert,
                                        Vert -
                                            SwitchExtension
                                                ->iLeftVBO); // iLeftVBO powinno zostać niezmienione
                            glBufferSubData(
                                GL_ARRAY_BUFFER, SwitchExtension->iLeftVBO * sizeof(CVertNormTex),
                                size * sizeof(CVertNormTex), Vert); // wysłanie fragmentu bufora VBO
                        }
                    }
                    else // gdy Display List
                        Release(); // niszczenie skompilowanej listy, aby się wygenerowała nowa
                } // 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 (int i = 0; i < iNumDynamics; i++)
        Dynamics[i]->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(vector3 v1, vector3 *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(vector3 *Point)
{ // sprawdzanie, czy tory można połączyć
    switch (eType)
    {
    case tt_Normal: // zwykły odcinek
        if (trPrev == NULL)
            if (Equal(Segment->FastGetPoint_0(), Point))
                return 0;
        if (trNext == NULL)
            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 == NULL)
            // if (Equal(SwitchExtension->Segments[0]->FastGetPoint_0(),Point))
            if (Equal(Segment->FastGetPoint_0(), Point))
            {
                Switch(state);
                return 2;
            }
        if (trNext == NULL)
            // 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 == NULL) // 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 == NULL) // 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;
};

void TTrack::MovedUp1(double dh)
{ // poprawienie przechyłki wymaga wydłużenia podsypki
    fTexHeight1 += dh;
};

AnsiString TTrack::NameGet()
{ // ustalenie nazwy toru
    if (this)
        if (pMyNode)
            return pMyNode->asName;
    return "none";
};

void TTrack::VelocitySet(float v)
{ // ustawienie prędkości z ograniczeniem do pierwotnej wartości (zapisanej w scenerii)
    if (SwitchExtension ? SwitchExtension->fVelocity >= 0.0 : false)
    { // zwrotnica może mieć odgórne ograniczenie, nieprzeskakiwalne eventem
        if (v > SwitchExtension->fVelocity ? true : v < 0.0)
            return void(fVelocity =
                            SwitchExtension->fVelocity); // maksymalnie tyle, ile było we wpisie
    }
    fVelocity = v; // nie ma ograniczenia
};

float TTrack::VelocityGet()
{ // pobranie dozwolonej prędkości podczas skanowania
    return ((iDamageFlag & 128) ? 0.0f : fVelocity); // tor uszkodzony = prędkość zerowa
};

void TTrack::ConnectionsLog()
{ // wypisanie informacji o połączeniach
    int i;
    WriteLog("--> tt_Cross named " + pMyNode->asName);
    if (eType == tt_Cross)
        for (i = 0; i < 2; ++i)
        {
            if (SwitchExtension->pPrevs[i])
                WriteLog("Point " + AnsiString(i + i + 1) + " -> track " +
                         SwitchExtension->pPrevs[i]->pMyNode->asName + ":" +
                         AnsiString(int(SwitchExtension->iPrevDirection[i])));
            if (SwitchExtension->pNexts[i])
                WriteLog("Point " + AnsiString(i + i + 2) + " -> track " +
                         SwitchExtension->pNexts[i]->pMyNode->asName + ":" +
                         AnsiString(int(SwitchExtension->iNextDirection[i])));
        }
};

TTrack * TTrack::Neightbour(int s, double &d)
{ // 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 NULL;
};
