/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/

/*
    MaSzyna EU07 locomotive simulator
    Copyright (C) 2001-2004  Marcin Wozniak and others

*/

#include "stdafx.h"
#include "Ground.h"

#include "Globals.h"
#include "messaging.h"
#include "Logs.h"
#include "usefull.h"
#include "Timer.h"
#include "Event.h"
#include "EvLaunch.h"
#include "TractionPower.h"
#include "Traction.h"
#include "Track.h"
#include "RealSound.h"
#include "AnimModel.h"
#include "MemCell.h"
#include "mtable.h"
#include "DynObj.h"
#include "Data.h"
#include "parser.h" //Tolaris-010603
#include "Driver.h"
#include "uilayer.h"

//---------------------------------------------------------------------------

extern "C"
{
    GLFWAPI HWND glfwGetWin32Window( GLFWwindow* window ); //m7todo: potrzebne do directsound
}

bool bCondition; // McZapkie: do testowania warunku na event multiple

//---------------------------------------------------------------------------
// Obiekt renderujący siatkę jest sztucznie tworzonym obiektem pomocniczym,
// grupującym siatki obiektów dla danej tekstury. Obiektami składowymi mogą
// byc trójkąty terenu, szyny, podsypki, a także proste modele np. słupy.
// Obiekty składowe dodane są do listy TSubRect::nMeshed z listą zrobioną na
// TGroundNode::nNext3, gdzie są posortowane wg tekstury. Obiekty renderujące
// są wpisane na listę TSubRect::nRootMesh (TGroundNode::nNext2) oraz na
// odpowiednie listy renderowania, gdzie zastępują obiekty składowe (nNext3).
// Problematyczne są tory/drogi/rzeki, gdzie używane sa 2 tekstury. Dlatego
// tory są zdublowane jako TP_TRACK oraz TP_DUMMYTRACK. Jeśli tekstura jest
// tylko jedna (np. zwrotnice), nie jest używany TP_DUMMYTRACK.
//---------------------------------------------------------------------------
TGroundNode::TGroundNode()
{ // nowy obiekt terenu - pusty
    iType = GL_POINTS;
    nNext = nNext2 = NULL;
    iCount = 0; // wierzchołków w trójkącie
    // iNumPts=0; //punktów w linii
    m_material = 0;
    iFlags = 0; // tryb przezroczystości nie zbadany
    Pointer = NULL; // zerowanie wskaźnika kontekstowego
    bVisible = false; // czy widoczny
    fSquareRadius = 10000 * 10000;
    fSquareMinRadius = 0;
    asName = "";
    fLineThickness=1.0; //mm dla linii
    nNext3 = NULL; // nie wyświetla innych
}

TGroundNode::~TGroundNode()
{
    // if (iFlags&0x200) //czy obiekt został utworzony?
    switch (iType)
    {
    case TP_MEMCELL:
        SafeDelete(MemCell);
        break;
    case TP_EVLAUNCH:
        SafeDelete(EvLaunch);
        break;
    case TP_TRACTION:
        SafeDelete(hvTraction);
        break;
    case TP_TRACTIONPOWERSOURCE:
        SafeDelete(psTractionPowerSource);
        break;
    case TP_TRACK:
        SafeDelete(pTrack);
        break;
    case TP_DYNAMIC:
        SafeDelete(DynamicObject);
        break;
    case TP_MODEL:
        if (iFlags & 0x200) // czy model został utworzony?
            delete Model;
        Model = NULL;
        break;
	case TP_SOUND:
		SafeDelete(tsStaticSound);
		break;
    case TP_TERRAIN:
    { // pierwsze nNode zawiera model E3D, reszta to trójkąty
        delete[] nNode; // usunięcie tablicy i pierwszego elementu
    }
    case TP_SUBMODEL: // dla formalności, nie wymaga usuwania
        break;
    case GL_LINES:
    case GL_LINE_STRIP:
    case GL_LINE_LOOP:
    case GL_TRIANGLE_STRIP:
    case GL_TRIANGLE_FAN:
    case GL_TRIANGLES:
        SafeDelete( Piece );
        break;
    default:
        break;
    }
}
/*
void TGroundNode::Init(int n)
{ // utworzenie tablicy wierzchołków
    bVisible = false;
    iNumVerts = n;
    Vertices = new TGroundVertex[iNumVerts];
}
*/
TGroundNode::TGroundNode( TGroundNodeType t ) :
    TGroundNode() {
    // utworzenie obiektu
    iType = t;
    switch (iType) {
        // zależnie od typu
        case GL_TRIANGLES: {
            Piece = new piece_node;
            break;
        }
        case TP_TRACK: {
            pTrack = new TTrack( asName );
            break;
        }
        default: {
            break;
        }
    }
}

// obliczenie wektorów normalnych
void TGroundNode::InitNormals() {

    glm::dvec3 v1, v2;
    glm::vec3 n1;
    glm::vec2 t1;

    for( auto i = 0; i < iNumVerts; i += 3 ) {

        v1 = Piece->vertices[ i + 0 ].position - Piece->vertices[ i + 1 ].position;
        v2 = Piece->vertices[ i + 1 ].position - Piece->vertices[ i + 2 ].position;
        n1 = glm::normalize( glm::cross( v1, v2 ) );
        if( Piece->vertices[ i + 0 ].normal == glm::vec3() )
            Piece->vertices[ i + 0 ].normal = ( n1 );
        if( Piece->vertices[ i + 1 ].normal == glm::vec3() )
            Piece->vertices[ i + 1 ].normal = ( n1 );
        if( Piece->vertices[ i + 2 ].normal == glm::vec3() )
            Piece->vertices[ i + 2 ].normal = ( n1 );
        t1 = glm::vec2(
            std::floor( Piece->vertices[ i + 0 ].texture.s ),
            std::floor( Piece->vertices[ i + 0 ].texture.t ) );
        Piece->vertices[ i + 1 ].texture -= t1;
        Piece->vertices[ i + 2 ].texture -= t1;
        Piece->vertices[ i + 0 ].texture -= t1;
    }
}

void TGroundNode::RenderHidden()
{ // renderowanie obiektów niewidocznych
    double mgn = SquareMagnitude(pCenter - Global::pCameraPosition);
    switch (iType)
    {
    case TP_SOUND: // McZapkie - dzwiek zapetlony w zaleznosci od odleglosci
        if ((tsStaticSound->GetStatus() & DSBSTATUS_PLAYING) == DSBPLAY_LOOPING)
        {
            tsStaticSound->Play(1, DSBPLAY_LOOPING, true, tsStaticSound->vSoundPosition);
            tsStaticSound->AdjFreq(1.0, Timer::GetDeltaTime());
        }
        return;
    case TP_EVLAUNCH:
        if (EvLaunch->check_conditions())
            if ((EvLaunch->dRadius < 0) || (mgn < EvLaunch->dRadius))
            {
                WriteLog("Eventlauncher " + asName);
                if (Global::shiftState && (EvLaunch->Event2))
                    Global::AddToQuery(EvLaunch->Event2, NULL);
                else if (EvLaunch->Event1)
                    Global::AddToQuery(EvLaunch->Event1, NULL);
            }
        return;
    }
};

//------------------------------------------------------------------------------
//------------------ Podstawowy pojemnik terenu - sektor -----------------------
//------------------------------------------------------------------------------
TSubRect::~TSubRect()
{
    // TODO: usunąć obiekty z listy (nRootMesh), bo są one tworzone dla sektora
	delete[] tTracks;
}

void TSubRect::NodeAdd(TGroundNode *Node)
{ // przyczepienie obiektu do sektora, wstępna kwalifikacja na listy renderowania
    if (!this)
        return; // zabezpiecznie przed obiektami przekraczającymi obszar roboczy
    // Ra: sortowanie obiektów na listy renderowania:
    // nRenderHidden    - lista obiektów niewidocznych, "renderowanych" również z tyłu
    // nRenderRect      - lista grup renderowanych z sektora
    // nRenderRectAlpha - lista grup renderowanych z sektora z przezroczystością
    // nRender          - lista grup renderowanych z własnych VBO albo DL
    // nRenderAlpha     - lista grup renderowanych z własnych VBO albo DL z przezroczystością
    // nRenderWires     - lista grup renderowanych z własnych VBO albo DL - druty i linie

    Node->m_rootposition = m_area.center;

    // since ground rectangle can be empty, we're doing lazy initialization of the geometry bank, when something may actually use it
    // NOTE: this method is called for both subcell and cell, but subcells get first created and passed the handle from their parent
    // thus, this effectively only gets executed for the 'parent' ground cells. Not the most elegant, but for now it'll do
    if( m_geometrybank == null_handle ) {
        m_geometrybank = GfxRenderer.Create_Bank();
    }

    switch (Node->iType)
    {
    case TP_SOUND: // te obiekty są sprawdzanie niezależnie od kierunku patrzenia
    case TP_EVLAUNCH:
        Node->nNext3 = nRenderHidden;
        nRenderHidden = Node; // do listy koniecznych
        break;
    case TP_TRACK: // TODO: tory z cieniem (tunel, canyon) też dać bez łączenia?
        ++iTracks; // jeden tor więcej
#ifdef EU07_USE_OLD_GROUNDCODE
        Node->pTrack->RaOwnerSet(this); // do którego sektora ma zgłaszać animację
#endif
        // NOTE: track merge/sort temporarily disabled to simplify unification of render code
        // TODO: refactor sorting as universal part of drawing process in the renderer
        Node->nNext3 = nRenderRect;
        nRenderRect = Node;
        break;
    case GL_TRIANGLE_STRIP:
    case GL_TRIANGLE_FAN:
    case GL_TRIANGLES:
        if (Node->iFlags & 0x20) {
            // do przezroczystych z sektora
            Node->nNext3 = nRenderRectAlpha;
            nRenderRectAlpha = Node;
        }
        else {
            // do nieprzezroczystych z sektora
            Node->nNext3 = nRenderRect;
            nRenderRect = Node;
        }
        break;
    case TP_TRACTION:
    case GL_LINES:
    case GL_LINE_STRIP:
    case GL_LINE_LOOP: // te renderowane na końcu, żeby nie łapały koloru nieba
        Node->nNext3 = nRenderWires;
        nRenderWires = Node; // lista drutów
        break;
    case TP_MODEL: // modele zawsze wyświetlane z własnego VBO
        // jeśli model jest prosty, można próbować zrobić wspólną siatkę (słupy)
        if ((Node->iFlags & 0x20200020) == 0) // czy brak przezroczystości?
        {
            Node->nNext3 = nRender;
            nRender = Node;
        } // do nieprzezroczystych
        else if ((Node->iFlags & 0x10100010) == 0) // czy brak nieprzezroczystości?
        {
            Node->nNext3 = nRenderAlpha;
            nRenderAlpha = Node;
        } // do przezroczystych
        else // jak i take i takie, to będzie dwa razy renderowane...
        {
            Node->nNext3 = nRenderMixed;
            nRenderMixed = Node;
        } // do mieszanych
        break;
#ifdef EU07_SCENERY_EDITOR
    case TP_MEMCELL: {
        m_memcells.emplace_back( Node );
        break;
    }
#endif
    case TP_TRACTIONPOWERSOURCE: // a te w ogóle pomijamy
/*
        //  case TP_ISOLATED: //lista torów w obwodzie izolowanym - na razie ignorowana
        break;
*/
    case TP_DYNAMIC:
        return; // tych nie dopisujemy wcale
    }
    // dopisanie do ogólnej listy
    Node->nNext2 = nRootNode;
    nRootNode = Node;
    // licznik obiektów
    ++iNodeCount;
}

// przygotowanie sektora do renderowania
void TSubRect::Sort() {

    SafeDeleteArray( tTracks );

    if( iTracks > 0 ) {
        tTracks = new TTrack *[ iTracks ]; // tworzenie tabeli torów do renderowania pojazdów
        int i = 0;
        for( TGroundNode *node = nRootNode; node != nullptr; node = node->nNext2 ) {
            // kolejne obiekty z sektora
            if( node->iType == TP_TRACK ) {
                tTracks[ i++ ] = node->pTrack;
            }
        }
    }
    // NOTE: actual sort procedure temporarily removed as part of render code unification
}

TTrack * TSubRect::FindTrack(vector3 *Point, int &iConnection, TTrack *Exclude)
{ // szukanie toru, którego koniec jest najbliższy (*Point)
    for( int i = 0; i < iTracks; ++i ) {
        if( tTracks[ i ] != Exclude ) // można użyć tabelę torów, bo jest mniejsza
        {
            iConnection = tTracks[ i ]->TestPoint( Point );
            if( iConnection >= 0 )
                return tTracks[ i ]; // szukanie TGroundNode nie jest potrzebne
        }
    }
    return NULL;
};

#ifdef EU07_USE_OLD_GROUNDCODE
bool TSubRect::RaTrackAnimAdd(TTrack *t)
{ // aktywacja animacji torów w VBO (zwrotnica, obrotnica)
    if( false == m_geometrycreated ) {
        // nie ma animacji, gdy nie widać
        return true;
    }
    if (tTrackAnim)
        tTrackAnim->RaAnimListAdd(t);
    else
        tTrackAnim = t;
    return false; // będzie animowane...
}

// wykonanie animacji
void TSubRect::RaAnimate( unsigned int const Framestamp ) {
    
    if( ( tTrackAnim == nullptr )
     || ( Framestamp == m_framestamp ) ) {
        // nie ma nic do animowania
        return;
    }
    tTrackAnim = tTrackAnim->RaAnimate(); // przeliczenie animacji kolejnego

    m_framestamp = Framestamp;
};
#endif

TTraction * TSubRect::FindTraction(glm::dvec3 const &Point, int &iConnection, TTraction *Exclude)
{ // szukanie przęsła w sektorze, którego koniec jest najbliższy (*Point)
    TGroundNode *Current;
    for (Current = nRenderWires; Current; Current = Current->nNext3)
        if ((Current->iType == TP_TRACTION) && (Current->hvTraction != Exclude))
        {
            iConnection = Current->hvTraction->TestPoint(Point);
            if (iConnection >= 0)
                return Current->hvTraction;
        }
    return NULL;
};

// utworzenie siatek VBO dla wszystkich node w sektorze
void TSubRect::LoadNodes() {

    if( true == m_geometrycreated ) {
        // obiekty były już sprawdzone
        return;
    }
    else {
        // mark it done for future checks
        m_geometrycreated = true;
    }

    auto *node { nRootNode };
    while( node != nullptr ) {
        switch (node->iType) {
            case GL_TRIANGLES:
            case GL_LINES: {
                vertex_array vertices;
                for( auto const &vertex : node->Piece->vertices ) {
                    vertices.emplace_back(
                        vertex.position - node->m_rootposition,
                        vertex.normal,
                        vertex.texture );
                }
                node->Piece->geometry = GfxRenderer.Insert( vertices, m_geometrybank, node->iType );
                node->Piece->vertices.swap( std::vector<TGroundVertex>() ); // hipster shrink_to_fit
                // TODO: get rid of the vertex counters, they're obsolete at this point
                if( node->iType == GL_LINES ) { node->iNumVerts = 0; }
                else                          { node->iNumPts = 0; }
                break;
            }
            case TP_TRACK:
                if( node->pTrack->visible() ) { // bo tory zabezpieczające są niewidoczne
#ifdef EU07_USE_OLD_GROUNDCODE
                    node->pTrack->create_geometry( m_geometrybank, node->m_rootposition );
#else
                    node->pTrack->create_geometry( m_geometrybank );
#endif
                }
                break;
            case TP_TRACTION:
#ifdef EU07_USE_OLD_GROUNDCODE
                node->hvTraction->create_geometry( m_geometrybank, node->m_rootposition );
#else
                node->hvTraction->create_geometry( m_geometrybank );
#endif
                break;
            default: { break; }
        }
        node = node->nNext2; // następny z sektora
    }
}
#ifdef EU07_USE_OLD_GROUNDCODE
void TSubRect::RenderSounds()
{ // aktualizacja dźwięków w pojazdach sektora (sektor może nie być wyświetlany)
    for (int j = 0; j < iTracks; ++j)
        tTracks[j]->RenderDynSounds(); // dźwięki pojazdów idą niezależnie od wyświetlania
};
#endif
//---------------------------------------------------------------------------
//------------------ Kwadrat kilometrowy ------------------------------------
//---------------------------------------------------------------------------
TGroundRect::~TGroundRect()
{
    SafeDeleteArray(pSubRects);
};

void
TGroundRect::Init() {
    // since ground rectangle can be empty, we're doing lazy initialization of the geometry bank, when something may actually use it
    if( m_geometrybank == null_handle ) {
        m_geometrybank = GfxRenderer.Create_Bank();
    }

    pSubRects = new TSubRect[ iNumSubRects * iNumSubRects ];
    auto const subrectsize = 1000.0 / iNumSubRects;
    for( int column = 0; column < iNumSubRects; ++column ) {
        for( int row = 0; row < iNumSubRects; ++row ) {
            auto subcell = FastGetSubRect( column, row );
            auto &area = subcell->m_area;
            area.center =
                m_area.center
                - glm::dvec3( 500.0, 0.0, 500.0 ) // 'upper left' corner of rectangle
                + glm::dvec3( subrectsize * 0.5, 0.0, subrectsize * 0.5 ) // center of sub-rectangle
                + glm::dvec3( subrectsize * column, 0.0, subrectsize * row );
            area.radius = subrectsize * M_SQRT2;
            // all subcells share the same geometry bank with their parent, to reduce buffer switching during render
            subcell->m_geometrybank = m_geometrybank;
        }
    }
};

// dodanie obiektu do sektora na etapie rozdzielania na sektory
void
TGroundRect::NodeAdd( TGroundNode *Node ) {

    // override visibility ranges, to ensure the content is drawn from far enough
    Node->fSquareRadius = 50000.0 * 50000.0;
    Node->fSquareMinRadius = 0.0;

    // if the cell already has a node with matching material settings, we can just add the new geometry to it
    if( ( Node->iType == GL_TRIANGLES ) ) {
        // cell node only receives opaque geometry, so we can skip transparency test
        auto matchingnode { nRenderRect };
        while( ( matchingnode != nullptr )
            && ( false == mergeable( *Node, *matchingnode ) ) ) {
            // search will get us either a matching node, or a nullptr
            matchingnode = matchingnode->nNext3;
        }
        if( matchingnode != nullptr ) {
            // a valid match, so dump the content into it
            // updating centre points isn't strictly necessary since render is based off the cell's middle, but, eh
            matchingnode->pCenter =
                interpolate(
                    matchingnode->pCenter, Node->pCenter,
                    static_cast<float>( Node->iNumVerts ) / ( Node->iNumVerts + matchingnode->iNumVerts ) );
            matchingnode->iNumVerts += Node->iNumVerts;
            matchingnode->Piece->vertices.insert(
                std::end( matchingnode->Piece->vertices ),
                std::begin( Node->Piece->vertices ), std::end( Node->Piece->vertices ) );
            // clear content of the node we're copying. a minor memory saving at best, but still a saving
            std::vector<TGroundVertex>().swap( Node->Piece->vertices );
            Node->iNumVerts = 0;
            // since we've put the data in existing node we can skip adding the new one...
            return;
            // ...for others, they'll go through the regular procedure, along with other non-mergeable types
        }
    }

    return TSubRect::NodeAdd( Node );
}

// compares two provided nodes, returns true if their content can be merged
bool
TGroundRect::mergeable( TGroundNode const &Left, TGroundNode const &Right ) const {
    // since view ranges and transparency type for all nodes put through this method are guaranteed to be equal,
    // we can skip their tests and only do the material check.
    // TODO, TBD: material as dedicated type, and refactor this method into a simple equality test
    return ( ( Left.m_material == Right.m_material )
          && ( Left.Ambient == Right.Ambient )
          && ( Left.Diffuse == Right.Diffuse )
          && ( Left.Specular == Right.Specular ) );
}

//---------------------------------------------------------------------------

BYTE TempConnectionType[ 200 ]; // Ra: sprzêgi w sk³adzie; ujemne, gdy odwrotnie

TGround::TGround()
{
#ifdef EU07_USE_OLD_GROUNDCODE
    Global::pGround = this;
#endif
    for( int i = 0; i < TP_LAST; ++i ) {
        nRootOfType[ i ] = nullptr; // zerowanie tablic wyszukiwania
    }
    ::SecureZeroMemory( TempConnectionType, sizeof( TempConnectionType ) );
    // set bounding area information for ground rectangles
    float const rectsize = 1000.0f;
    glm::vec3 const worldcenter;
    for( int column = 0; column < iNumRects; ++column ) {
        for( int row = 0; row < iNumRects; ++row ) {
            auto &area = Rects[ column ][ row ].m_area;
            // NOTE: in current implementation triangles can stick out to ~200m from the area, so we add extra padding
            area.radius = ( rectsize * 0.5f * M_SQRT2 ) + 200.0f;
            area.center =
                worldcenter
                - glm::vec3( (iNumRects / 2) * rectsize, 0.0f, (iNumRects / 2) * rectsize ) // 'upper left' corner of the world
                + glm::vec3( rectsize * 0.5f, 0.0f, rectsize * 0.5f ) // center of the rectangle
                + glm::vec3( rectsize * column, 0.0f, rectsize * row );
        }
    }
}

TGround::~TGround()
{
    Free();
}

void TGround::Free()
{
#ifdef EU07_USE_OLD_GROUNDCODE
    TEvent *tmp;
    for (TEvent *Current = RootEvent; Current;)
    {
        tmp = Current;
        Current = Current->evNext2;
        delete tmp;
    }
#endif
    TGroundNode *tmpn;
    for (int i = 0; i < TP_LAST; ++i)
    {
        for (TGroundNode *Current = nRootOfType[i]; Current;)
        {
            tmpn = Current;
            Current = Current->nNext;
            delete tmpn;
        }
        nRootOfType[i] = NULL;
    }
    for (TGroundNode *Current = nRootDynamic; Current;)
    {
        tmpn = Current;
        Current = Current->nNext;
        delete tmpn;
    }
    // RootNode=NULL;
    nRootDynamic = NULL;
}
#ifdef EU07_USE_OLD_GROUNDCODE
TGroundNode * TGround::DynamicFindAny(std::string const &Name)
{ // wyszukanie pojazdu o podanej nazwie, szukanie po wszystkich (użyć drzewa!)
    for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
        if ((Current->asName == Name))
            return Current;
    return NULL;
};

TGroundNode * TGround::DynamicFind(std::string const &Name)
{ // wyszukanie pojazdu z obsadą o podanej nazwie (użyć drzewa!)
    for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
        if (Current->DynamicObject->Mechanik)
            if ((Current->asName == Name))
                return Current;
    return NULL;
};

void
TGround::DynamicList(bool all)
{ // odesłanie nazw pojazdów dostępnych na scenerii (nazwy, szczególnie wagonów, mogą się
    // powtarzać!)
    for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
        if (all || Current->DynamicObject->Mechanik)
            multiplayer::WyslijString(Current->asName, 6); // same nazwy pojazdów
    multiplayer::WyslijString("none", 6); // informacja o końcu listy
};

// wyszukiwanie obiektu o podanej nazwie i konkretnym typie
TGroundNode *
TGround::FindGroundNode(std::string const &asNameToFind, TGroundNodeType const iNodeType) {

    if( ( iNodeType == TP_TRACK )
     || ( iNodeType == TP_MEMCELL )
     || ( iNodeType == TP_MODEL ) ) {
        // wyszukiwanie w drzewie binarnym
        return m_nodemap.Find( iNodeType, asNameToFind );
    }
    // standardowe wyszukiwanie liniowe
    for( TGroundNode *Current = nRootOfType[ iNodeType ]; Current != nullptr; Current = Current->nNext ) {
        if( Current->asName == asNameToFind ) {
            return Current;
        }
    }
    return nullptr;
}
#endif
TGroundRect *
TGround::GetRect( double x, double z ) {

    auto const column = GetColFromX( x ) / iNumSubRects;
    auto const row = GetRowFromZ( z ) / iNumSubRects;

    if( ( column >= 0 ) && ( column < iNumRects )
     && ( row    >= 0 ) && ( row    < iNumRects ) ) {
        return &Rects[ column ][ row ];
    }
    else {
        return nullptr;
    }
};
#ifdef EU07_USE_OLD_GROUNDCODE
// convert tp_terrain model to a series of triangle nodes
void
TGround::convert_terrain( TGroundNode const *Terrain ) {

    TSubModel *submodel { nullptr };
    for( auto cellindex = 1; cellindex < Terrain->iCount; ++cellindex ) {
        // go through all submodels for individual terrain cells...
        submodel = Terrain->nNode[ cellindex ].smTerrain;
        convert_terrain( submodel );
        // if there's more than one group of triangles in the cell they're held as children of the primary submodel
        submodel = submodel->ChildGet();
        while( submodel != nullptr ) {
            convert_terrain( submodel );
            submodel = submodel->NextGet();
        }
    }
}

void
TGround::convert_terrain( TSubModel const *Submodel ) {

    auto groundnode = new TGroundNode( GL_TRIANGLES );
    Submodel->convert( *groundnode );

    if( groundnode->iNumVerts > 0 ) {
    // jeśli nie jest pojazdem ostatni dodany dołączamy na końcu nowego
        groundnode->nNext = nRootOfType[ groundnode->iType ];
        // ustawienie nowego na początku listy
        nRootOfType[ groundnode->iType ] = groundnode;
    }
    else {
        delete groundnode;
    }
}
#endif
double fTrainSetVel = 0;
double fTrainSetDir = 0;
double fTrainSetDist = 0; // odległość składu od punktu 1 w stronę punktu 2
std::string asTrainSetTrack = "";
int iTrainSetConnection = 0;
bool bTrainSet = false;
std::string asTrainName = "";
int iTrainSetWehicleNumber = 0;
TGroundNode *nTrainSetNode = NULL; // poprzedni pojazd do łączenia
TGroundNode *nTrainSetDriver = NULL; // pojazd, któremu zostanie wysłany rozkład

#ifdef EU07_USE_OLD_GROUNDCODE
void TGround::RaTriangleDivider(TGroundNode *node)
{ // tworzy dodatkowe trójkąty i zmiejsza podany
    // to jest wywoływane przy wczytywaniu trójkątów
    // dodatkowe trójkąty są dodawane do głównej listy trójkątów
    // podział trójkątów na sektory i kwadraty jest dokonywany później w FirstInit
    if (node->iType != GL_TRIANGLES)
        return; // tylko pojedyncze trójkąty
    if (node->iNumVerts != 3)
        return; // tylko gdy jeden trójkąt
    double x0 = 1000.0 * std::floor(0.001 * node->pCenter.x) - 200.0;
    double x1 = x0 + 1400.0;
    double z0 = 1000.0 * std::floor(0.001 * node->pCenter.z) - 200.0;
    double z1 = z0 + 1400.0;
    if ((node->Piece->vertices[0].position.x >= x0) && (node->Piece->vertices[0].position.x <= x1) &&
        (node->Piece->vertices[0].position.z >= z0) && (node->Piece->vertices[0].position.z <= z1) &&
        (node->Piece->vertices[1].position.x >= x0) && (node->Piece->vertices[1].position.x <= x1) &&
        (node->Piece->vertices[1].position.z >= z0) && (node->Piece->vertices[1].position.z <= z1) &&
        (node->Piece->vertices[2].position.x >= x0) && (node->Piece->vertices[2].position.x <= x1) &&
        (node->Piece->vertices[2].position.z >= z0) && (node->Piece->vertices[2].position.z <= z1))
        return; // trójkąt wystający mniej niż 200m z kw. kilometrowego jest do przyjęcia
    // Ra: przerobić na dzielenie na 2 trójkąty, podział w przecięciu z siatką kilometrową
    // Ra: i z rekurencją będzie dzielić trzy trójkąty, jeśli będzie taka potrzeba
    int divide = -1; // bok do podzielenia: 0=AB, 1=BC, 2=CA; +4=podział po OZ; +8 na x1/z1
    double min = 0, mul; // jeśli przechodzi przez oś, iloczyn będzie ujemny
    x0 += 200.0;
    x1 -= 200.0; // przestawienie na siatkę
    z0 += 200.0;
    z1 -= 200.0;
    mul = (node->Piece->vertices[0].position.x - x0) * (node->Piece->vertices[1].position.x - x0); // AB na wschodzie
    if (mul < min)
        min = mul, divide = 0;
    mul = (node->Piece->vertices[1].position.x - x0) * (node->Piece->vertices[2].position.x - x0); // BC na wschodzie
    if (mul < min)
        min = mul, divide = 1;
    mul = (node->Piece->vertices[2].position.x - x0) * (node->Piece->vertices[0].position.x - x0); // CA na wschodzie
    if (mul < min)
        min = mul, divide = 2;
    mul = (node->Piece->vertices[0].position.x - x1) * (node->Piece->vertices[1].position.x - x1); // AB na zachodzie
    if (mul < min)
        min = mul, divide = 8;
    mul = (node->Piece->vertices[1].position.x - x1) * (node->Piece->vertices[2].position.x - x1); // BC na zachodzie
    if (mul < min)
        min = mul, divide = 9;
    mul = (node->Piece->vertices[2].position.x - x1) * (node->Piece->vertices[0].position.x - x1); // CA na zachodzie
    if (mul < min)
        min = mul, divide = 10;
    mul = (node->Piece->vertices[0].position.z - z0) * (node->Piece->vertices[1].position.z - z0); // AB na południu
    if (mul < min)
        min = mul, divide = 4;
    mul = (node->Piece->vertices[1].position.z - z0) * (node->Piece->vertices[2].position.z - z0); // BC na południu
    if (mul < min)
        min = mul, divide = 5;
    mul = (node->Piece->vertices[2].position.z - z0) * (node->Piece->vertices[0].position.z - z0); // CA na południu
    if (mul < min)
        min = mul, divide = 6;
    mul = (node->Piece->vertices[0].position.z - z1) * (node->Piece->vertices[1].position.z - z1); // AB na północy
    if (mul < min)
        min = mul, divide = 12;
    mul = (node->Piece->vertices[1].position.z - z1) * (node->Piece->vertices[2].position.z - z1); // BC na północy
    if (mul < min)
        min = mul, divide = 13;
    mul = (node->Piece->vertices[2].position.z - z1) * (node->Piece->vertices[0].position.z - z1); // CA na północy
    if (mul < min)
        divide = 14;
    // tworzymy jeden dodatkowy trójkąt, dzieląc jeden bok na przecięciu siatki kilometrowej
    TGroundNode *ntri; // wskaźnik na nowy trójkąt
    ntri = new TGroundNode(GL_TRIANGLES); // a ten jest nowy
    // kopiowanie parametrów, przydałby się konstruktor kopiujący
    ntri->m_material = node->m_material;
    ntri->iFlags = node->iFlags;
    ntri->Ambient = node->Ambient;
    ntri->Diffuse = node->Diffuse;
    ntri->Specular = node->Specular;
    ntri->asName = node->asName;
    ntri->fSquareRadius = node->fSquareRadius;
    ntri->fSquareMinRadius = node->fSquareMinRadius;
    ntri->bVisible = node->bVisible; // a są jakieś niewidoczne?
    ntri->nNext = nRootOfType[GL_TRIANGLES];
    nRootOfType[GL_TRIANGLES] = ntri; // dopisanie z przodu do listy
    ntri->iNumVerts = 3;
    ntri->Piece->vertices.resize( 3 );
    switch (divide & 3)
    { // podzielenie jednego z boków, powstaje wierzchołek D
    case 0: // podział AB (0-1) -> ADC i DBC
        ntri->Piece->vertices[2] = node->Piece->vertices[2]; // wierzchołek C jest wspólny
        ntri->Piece->vertices[1] = node->Piece->vertices[1]; // wierzchołek B przechodzi do nowego
        // node->Vertices[1].HalfSet(node->Vertices[0],node->Vertices[1]); //na razie D tak
        if (divide & 4)
            node->Piece->vertices[1].SetByZ(node->Piece->vertices[0], node->Piece->vertices[1], (divide & 8) ? z1 : z0);
        else
            node->Piece->vertices[1].SetByX(node->Piece->vertices[0], node->Piece->vertices[1], (divide & 8) ? x1 : x0);
        ntri->Piece->vertices[0] = node->Piece->vertices[1]; // wierzchołek D jest wspólny
        break;
    case 1: // podział BC (1-2) -> ABD i ADC
        ntri->Piece->vertices[0] = node->Piece->vertices[0]; // wierzchołek A jest wspólny
        ntri->Piece->vertices[2] = node->Piece->vertices[2]; // wierzchołek C przechodzi do nowego
        // node->Vertices[2].HalfSet(node->Vertices[1],node->Vertices[2]); //na razie D tak
        if (divide & 4)
            node->Piece->vertices[2].SetByZ(node->Piece->vertices[1], node->Piece->vertices[2], (divide & 8) ? z1 : z0);
        else
            node->Piece->vertices[2].SetByX(node->Piece->vertices[1], node->Piece->vertices[2], (divide & 8) ? x1 : x0);
        ntri->Piece->vertices[1] = node->Piece->vertices[2]; // wierzchołek D jest wspólny
        break;
    case 2: // podział CA (2-0) -> ABD i DBC
        ntri->Piece->vertices[1] = node->Piece->vertices[1]; // wierzchołek B jest wspólny
        ntri->Piece->vertices[2] = node->Piece->vertices[2]; // wierzchołek C przechodzi do nowego
        // node->Vertices[2].HalfSet(node->Vertices[2],node->Vertices[0]); //na razie D tak
        if (divide & 4)
            node->Piece->vertices[2].SetByZ(node->Piece->vertices[2], node->Piece->vertices[0], (divide & 8) ? z1 : z0);
        else
            node->Piece->vertices[2].SetByX(node->Piece->vertices[2], node->Piece->vertices[0], (divide & 8) ? x1 : x0);
        ntri->Piece->vertices[0] = node->Piece->vertices[2]; // wierzchołek D jest wspólny
        break;
    }
    // przeliczenie środków ciężkości obu
    node->pCenter = ( node->Piece->vertices[ 0 ].position + node->Piece->vertices[ 1 ].position + node->Piece->vertices[ 2 ].position ) / 3.0;
    ntri->pCenter = ( ntri->Piece->vertices[ 0 ].position + ntri->Piece->vertices[ 1 ].position + ntri->Piece->vertices[ 2 ].position ) / 3.0;
    RaTriangleDivider(node); // rekurencja, bo nawet na TD raz nie wystarczy
    RaTriangleDivider(ntri);
};
#endif
TGroundNode * TGround::AddGroundNode(cParser *parser)
{ // wczytanie wpisu typu "node"
	std::string str, str1, str2, str3, str4, Skin, DriverType, asNodeName;
	double tf, r, rmin, tf1, tf3;
	int int1;
	size_t int2;
	bool bError = false;
    vector3 pt, front, up, left, pos, tv;
    matrix4x4 mat2, mat1, mat;
    TGroundNode *tmp1;
    TTrack *Track;
    std::string token;
    parser->getTokens(4);
    *parser
        >> r
        >> rmin
        >> asNodeName
        >> str;
	TGroundNode *tmp = new TGroundNode();
    tmp->asName = (asNodeName == "none" ? "" : asNodeName);
    if (r >= 0)
        tmp->fSquareRadius = r * r;
    tmp->fSquareMinRadius = rmin * rmin;
    if (str == "triangles")
        tmp->iType = GL_TRIANGLES;
    else if (str == "triangle_strip")
        tmp->iType = GL_TRIANGLE_STRIP;
    else if (str == "triangle_fan")
        tmp->iType = GL_TRIANGLE_FAN;
    else if (str == "lines")
        tmp->iType = GL_LINES;
    else if (str == "line_strip")
        tmp->iType = GL_LINE_STRIP;
    else if (str == "line_loop")
        tmp->iType = GL_LINE_LOOP;
    else if (str == "model")
        tmp->iType = TP_MODEL;
    // else if (str=="terrain")             tmp->iType=TP_TERRAIN; //tymczasowo do odwołania
    else if (str == "dynamic")
        tmp->iType = TP_DYNAMIC;
    else if (str == "sound")
        tmp->iType = TP_SOUND;
    else if (str == "track")
        tmp->iType = TP_TRACK;
    else if (str == "memcell")
        tmp->iType = TP_MEMCELL;
    else if (str == "eventlauncher")
        tmp->iType = TP_EVLAUNCH;
    else if (str == "traction")
        tmp->iType = TP_TRACTION;
    else if (str == "tractionpowersource")
        tmp->iType = TP_TRACTIONPOWERSOURCE;
    // else if (str=="isolated")            tmp->iType=TP_ISOLATED;
    else
        bError = true;
    // WriteLog("-> node "+str+" "+tmp->asName);
    if (bError)
    {
        Error("Scene parse error near " + str);
        for (int i = 0; i < 60; ++i)
        { // Ra: skopiowanie dalszej części do logu - taka prowizorka, lepsza niż nic
            parser->getTokens(); // pobranie linijki tekstu nie działa
            *parser >> token;
            WriteLog(token);
        }
        // if (tmp==RootNode) RootNode=NULL;
        delete tmp;
        return NULL;
    }
    glm::dvec3 center;
    std::vector<TGroundVertex> importedvertices;

    switch (tmp->iType)
    {
#ifdef EU07_USE_OLD_GROUNDCODE
        case TP_TRACTION:
        tmp->hvTraction = new TTraction( tmp->asName );
        parser->getTokens();
        *parser >> token;
        tmp->hvTraction->asPowerSupplyName = token; // nazwa zasilacza
        parser->getTokens(3);
        *parser
            >> tmp->hvTraction->NominalVoltage
            >> tmp->hvTraction->MaxCurrent
            >> tmp->hvTraction->fResistivity;
        if (tmp->hvTraction->fResistivity == 0.01f) // tyle jest w sceneriach [om/km]
            tmp->hvTraction->fResistivity = 0.075f; // taka sensowniejsza wartość za
        // http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf
        tmp->hvTraction->fResistivity *= 0.001f; // teraz [om/m]
        parser->getTokens();
        *parser >> token;
        // Ra 2014-02: a tutaj damy symbol sieci i jej budowę, np.:
        // SKB70-C, CuCd70-2C, KB95-2C, C95-C, C95-2C, YC95-2C, YpC95-2C, YC120-2C
        // YpC120-2C, YzC120-2C, YwsC120-2C, YC150-C150, YC150-2C150, C150-C150
        // C120-2C, 2C120-2C, 2C120-2C-1, 2C120-2C-2, 2C120-2C-3, 2C120-2C-4
        if (token.compare("none") == 0)
            tmp->hvTraction->Material = 0;
        else if (token.compare("al") == 0)
            tmp->hvTraction->Material = 2; // 1=aluminiowa, rysuje się na czarno
        else
            tmp->hvTraction->Material = 1; // 1=miedziana, rysuje się na zielono albo czerwono
        parser->getTokens();
        *parser >> tmp->hvTraction->WireThickness;
        parser->getTokens();
        *parser >> tmp->hvTraction->DamageFlag;
        parser->getTokens(3);
        *parser
            >> tmp->hvTraction->pPoint1.x
            >> tmp->hvTraction->pPoint1.y
            >> tmp->hvTraction->pPoint1.z;
        tmp->hvTraction->pPoint1 += glm::dvec3{ pOrigin };
        parser->getTokens(3);
        *parser
            >> tmp->hvTraction->pPoint2.x
            >> tmp->hvTraction->pPoint2.y
            >> tmp->hvTraction->pPoint2.z;
        tmp->hvTraction->pPoint2 += glm::dvec3{ pOrigin };
        parser->getTokens(3);
        *parser
            >> tmp->hvTraction->pPoint3.x
            >> tmp->hvTraction->pPoint3.y
            >> tmp->hvTraction->pPoint3.z;
        tmp->hvTraction->pPoint3 += glm::dvec3{ pOrigin };
        parser->getTokens(3);
        *parser
            >> tmp->hvTraction->pPoint4.x
            >> tmp->hvTraction->pPoint4.y
            >> tmp->hvTraction->pPoint4.z;
        tmp->hvTraction->pPoint4 += glm::dvec3{ pOrigin };
        parser->getTokens();
        *parser >> tf1;
        tmp->hvTraction->fHeightDifference =
            (tmp->hvTraction->pPoint3.y - tmp->hvTraction->pPoint1.y + tmp->hvTraction->pPoint4.y - tmp->hvTraction->pPoint2.y) * 0.5f - tf1;
        parser->getTokens();
        *parser >> tf1;
        if (tf1 > 0)
            tmp->hvTraction->iNumSections =
                glm::length((tmp->hvTraction->pPoint1 - tmp->hvTraction->pPoint2)) / tf1;
        else
            tmp->hvTraction->iNumSections = 0;
        parser->getTokens();
        *parser >> tmp->hvTraction->Wires;
        parser->getTokens();
        *parser >> tmp->hvTraction->WireOffset;
        parser->getTokens();
        *parser >> token;
        tmp->bVisible = (token.compare("vis") == 0);
        parser->getTokens();
        *parser >> token;
        if (token.compare("parallel") == 0)
        { // jawne wskazanie innego przęsła, na które może przestawić się pantograf
            parser->getTokens();
            *parser >> token; // wypadało by to zapamiętać...
            tmp->hvTraction->asParallel = token;
            parser->getTokens();
            *parser >> token; // a tu już powinien być koniec
        }
        if (token.compare("endtraction") != 0)
            Error("ENDTRACTION delimiter missing! " + str2 + " found instead.");
        tmp->hvTraction->Init(); // przeliczenie parametrów
        tmp->pCenter = interpolate( tmp->hvTraction->pPoint2, tmp->hvTraction->pPoint1, 0.5 );
        break;
    case TP_TRACTIONPOWERSOURCE:
        parser->getTokens(3);
        *parser
            >> tmp->pCenter.x
            >> tmp->pCenter.y
            >> tmp->pCenter.z;
        tmp->pCenter += pOrigin;
        tmp->psTractionPowerSource = new TTractionPowerSource( tmp->asName );
        tmp->psTractionPowerSource->Load(parser);
        break;
    case TP_MEMCELL:
        parser->getTokens(3);
        *parser
            >> tmp->pCenter.x
            >> tmp->pCenter.y
            >> tmp->pCenter.z;
        tmp->pCenter.RotateY(aRotate.y / 180.0 * M_PI); // Ra 2014-11: uwzględnienie rotacji
        tmp->pCenter += pOrigin;
        tmp->MemCell = new TMemCell(&tmp->pCenter);
        tmp->MemCell->Load(parser);
        if (!tmp->asName.empty()) // jest pusta gdy "none"
        { // dodanie do wyszukiwarki
            if( false == m_nodemap.Add( TP_MEMCELL, tmp->asName, tmp ) ) {
                // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność wsteczna)
                ErrorLog( "Duplicated memcell: " + tmp->asName ); // to zgłaszać duplikat
            }
        }
        break;
    case TP_EVLAUNCH:
        parser->getTokens(3);
        *parser
            >> tmp->pCenter.x
            >> tmp->pCenter.y
            >> tmp->pCenter.z;
        tmp->pCenter.RotateY(aRotate.y / 180.0 * M_PI); // Ra 2014-11: uwzględnienie rotacji
        tmp->pCenter += pOrigin;
        tmp->EvLaunch = new TEventLauncher();
        tmp->EvLaunch->Load(parser);
        break;
    case TP_TRACK:
        tmp->pTrack = new TTrack( tmp->asName );
        if (Global::iWriteLogEnabled & 4)
            if (!tmp->asName.empty())
                WriteLog(tmp->asName);
        tmp->pTrack->Load(parser, pOrigin); // w nazwie może być nazwa odcinka izolowanego
        if (!tmp->asName.empty()) // jest pusta gdy "none"
        { // dodanie do wyszukiwarki
            if( false == m_nodemap.Add( TP_TRACK, tmp->asName, tmp ) ) {
                // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność wsteczna)
                ErrorLog( "Duplicated track: " + tmp->asName ); // to zgłaszać duplikat
            }
        }
        tmp->pCenter = (
              tmp->pTrack->CurrentSegment()->FastGetPoint_0()
            + tmp->pTrack->CurrentSegment()->FastGetPoint( 0.5 )
            + tmp->pTrack->CurrentSegment()->FastGetPoint_1() )
            / 3.0;
        break;
    case TP_SOUND:
        parser->getTokens(3);
        *parser
            >> tmp->pCenter.x
            >> tmp->pCenter.y
            >> tmp->pCenter.z;
        tmp->pCenter.RotateY(aRotate.y / 180.0 * M_PI); // Ra 2014-11: uwzględnienie rotacji
        tmp->pCenter += pOrigin;
        parser->getTokens();
        *parser >> token;
		str = token;
		//str = AnsiString(token.c_str());
        tmp->tsStaticSound = new TTextSound(str, sqrt(tmp->fSquareRadius), tmp->pCenter.x, tmp->pCenter.y, tmp->pCenter.z, false, false, rmin);
        if (rmin < 0.0)
            rmin = 0.0; // przywrócenie poprawnej wartości, jeśli służyła do wyłączenia efektu Dopplera
        parser->getTokens();
        *parser >> token;
        break;
    case TP_DYNAMIC:
        tmp->DynamicObject = new TDynamicObject();
        // tmp->DynamicObject->Load(Parser);
        parser->getTokens(3);
        *parser
            >> str1 // katalog
            >> Skin // tekstura wymienna
            >> str3; // McZapkie-131102: model w MMD
        if (bTrainSet)
        { // jeśli pojazd jest umieszczony w składzie
            str = asTrainSetTrack;
            parser->getTokens(3);
            *parser
                >> tf1 // Ra: -1 oznacza odwrotne wstawienie, normalnie w składzie 0
                >> DriverType // McZapkie:010303 - w przyszlosci rozne konfiguracje mechanik/pomocnik itp
                >> str4;
            tf3 = fTrainSetVel; // prędkość
            int2 = str4.find("."); // yB: wykorzystuje tutaj zmienna, ktora potem bedzie ladunkiem
            if (int2 != std::string::npos) // yB: jesli znalazl kropke, to ja przetwarza jako parametry
            {
                size_t dlugosc = str4.length();
                int1 = atoi(str4.substr(0, int2).c_str()); // niech sprzegiem bedzie do kropki cos
                str4 = str4.substr(int2 + 1, dlugosc - int2);
            }
            else
            {
                int1 = atoi(str4.c_str());
                str4 = "";
            }
            int2 = 0; // zeruje po wykorzystaniu
            if (int1 < 0)
                int1 = (-int1) |
                       ctrain_depot; // sprzęg zablokowany (pojazdy nierozłączalne przy manewrach)
            if (tf1 != -1.0)
                if (std::fabs(tf1) > 0.5) // maksymalna odległość między sprzęgami - do przemyślenia
                    int1 = 0; // likwidacja sprzęgu, jeśli odległość zbyt duża - to powinno być
            // uwzględniane w fizyce sprzęgów...
            TempConnectionType[iTrainSetWehicleNumber] = int1; // wartość dodatnia
        }
        else
        { // pojazd wstawiony luzem
            fTrainSetDist = 0; // zerowanie dodatkowego przesunięcia
            asTrainName = ""; // puste oznacza jazdę pojedynczego bez rozkładu, "none" jest dla
            // składu (trainset)
            parser->getTokens(4);
            *parser
                >> str // track
                >> tf1 // Ra: -1 oznacza odwrotne wstawienie
                >> DriverType // McZapkie:010303: obsada
                >> tf3; // prędkość, niektórzy wpisują tu "3" jako sprzęg, żeby nie było tabliczki
            iTrainSetWehicleNumber = 0;
            TempConnectionType[iTrainSetWehicleNumber] = 3; // likwidacja tabliczki na końcu?
        }
        parser->getTokens();
        *parser >> int2; // ilość ładunku
        if (int2 > 0)
        { // jeżeli ładunku jest więcej niż 0, to rozpoznajemy jego typ
            parser->getTokens();
            *parser >> str2; // LoadType
            if (str2 == "enddynamic") // idiotoodporność: ładunek bez podanego typu
            {
                str2 = "";
                int2 = 0; // ilość bez typu się nie liczy jako ładunek
            }
        }
        else
            str2 = ""; // brak ladunku

        tmp1 = FindGroundNode(str, TP_TRACK); // poszukiwanie toru
        if (tmp1 ? tmp1->pTrack != NULL : false)
        { // jeśli tor znaleziony
            Track = tmp1->pTrack;
            if (!iTrainSetWehicleNumber) // jeśli pierwszy pojazd
                if (Track->evEvent0) // jeśli tor ma Event0
                    if (fabs(fTrainSetVel) <= 1.0) // a skład stoi
                        if (fTrainSetDist >= 0.0) // ale może nie sięgać na owy tor
                            if (fTrainSetDist < 8.0) // i raczej nie sięga
                                fTrainSetDist =
                                    8.0; // przesuwamy około pół EU07 dla wstecznej zgodności

            auto const length =
                tmp->DynamicObject->Init(
                    asNodeName, str1, Skin, str3, Track,
                    ( tf1 == -1.0 ?
                        fTrainSetDist :
                        fTrainSetDist - tf1 ),
                    DriverType, tf3, asTrainName, int2, str2, (tf1 == -1.0), str4);
            if (length != 0.0) // zero oznacza błąd
            {
                // przesunięcie dla kolejnego, minus bo idziemy w stronę punktu 1
                fTrainSetDist -= length;
                tmp->pCenter = tmp->DynamicObject->GetPosition();
                // automatically establish permanent connections for couplers which specify them in their definitions
                if( TempConnectionType[ iTrainSetWehicleNumber ] ) {
                    if( tmp->DynamicObject->MoverParameters->Couplers[ ( tf1 == -1.0 ? 0 : 1 ) ].AllowedFlag & coupling::permanent ) {
                        TempConnectionType[ iTrainSetWehicleNumber ] |= coupling::permanent;
                    }
                }
                iTrainSetWehicleNumber++;
            }
            else
            { // LastNode=NULL;
                delete tmp;
                tmp = NULL; // nie może być tu return, bo trzeba pominąć jeszcze enddynamic
            }
        }
        else
        { // gdy tor nie znaleziony
            ErrorLog("Missed track: dynamic placed on \"" + tmp->DynamicObject->asTrack + "\"");
            delete tmp;
            tmp = NULL; // nie może być tu return, bo trzeba pominąć jeszcze enddynamic
        }
        parser->getTokens();
        *parser >> token;
        if (token.compare("destination") == 0)
        { // dokąd wagon ma jechać, uwzględniane przy manewrach
            parser->getTokens();
            *parser >> token;
            if (tmp)
                tmp->DynamicObject->asDestination = token;
            *parser >> token;
        }
        if (token.compare("enddynamic") != 0)
            Error("enddynamic statement missing");
/*
        if( tmp->DynamicObject->MoverParameters->LightPowerSource.SourceType != TPowerSource::NotDefined ) {
            // if the vehicle has defined light source, it can (potentially) emit light, so add it to the light array
*/
        if( ( tmp != nullptr )
         && ( tmp->DynamicObject->MoverParameters->CategoryFlag == 1 ) // trains only
         && ( ( tmp->DynamicObject->MoverParameters->SecuritySystem.SystemType != 0 )
           || ( tmp->DynamicObject->MoverParameters->SandCapacity > 0.0 ) ) ) {
            // we check for presence of security system or sand load, as a way to determine whether the vehicle is a controllable engine
            // NOTE: this isn't 100% precise, e.g. middle EZT module comes with security system, while it has no lights, and some engines
            //       don't have security systems fitted
            simulation::Lights.insert( tmp->DynamicObject );
        }

        break;
    case TP_MODEL: {
        if( rmin < 0 ) {
            // legacy leftover: special case, terrain provided as 3d model
            tmp->iType = TP_TERRAIN;
            tmp->fSquareMinRadius = 0; // to w ogóle potrzebne?
            // we ignore center and rotation for terrain; they should be set to 0 anyway
            parser->getTokens( 4 );

            tmp->iFlags = 0x200; // flaga usuwania
            tmp->Model = new TAnimModel();
            if( false == tmp->Model->Load( parser, true ) ) {
                delete tmp;
                tmp = nullptr;
                break;
            }
            tmp->iFlags |= tmp->Model->Flags(); // ustalenie, czy przezroczysty
            tmp->pCenter = Math3D::vector3(); // enforce placement in the world center
            // jeśli model jest terenem, trzeba utworzyć dodatkowe obiekty
            tmp->iCount = tmp->Model->TerrainCount() + 1; // zliczenie submodeli
            Global::pTerrainCompact = tmp->Model;
            tmp->nNode = new TGroundNode[ tmp->iCount ]; // sztuczne node dla kwadratów
            tmp->nNode[ 0 ].iType = TP_MODEL; // pierwszy zawiera model (dla delete)
            tmp->nNode[ 0 ].Model = Global::pTerrainCompact;
            tmp->nNode[ 0 ].iFlags = 0x200; // nie wyświetlany, ale usuwany
            for( auto i = 1; i < tmp->iCount; ++i ) { // a reszta to submodele
                tmp->nNode[ i ].iType = TP_SUBMODEL;
                tmp->nNode[ i ].smTerrain = Global::pTerrainCompact->TerrainSquare( i - 1 );
                tmp->nNode[ i ].iFlags = 0x10; // nieprzezroczyste; nie usuwany
                tmp->nNode[ i ].bVisible = true;
                tmp->nNode[ i ].pCenter = tmp->pCenter; // nie przesuwamy w inne miejsce
            }
        }
        else {
            // regular 3d model
            parser->getTokens( 3 );
            *parser
                >> tmp->pCenter.x
                >> tmp->pCenter.y
                >> tmp->pCenter.z;
            parser->getTokens();
            *parser >> tf1;
            // OlO_EU&KAKISH-030103: obracanie punktow zaczepien w modelu
            tmp->pCenter.RotateY( glm::radians( aRotate.y ) );
            // McZapkie-260402: model tez ma wspolrzedne wzgledne
            tmp->pCenter += pOrigin;

            tmp->iFlags = 0x200; // flaga usuwania
            tmp->Model = new TAnimModel();
            tmp->Model->RaAnglesSet( glm::vec3{ aRotate.x, tf1 + aRotate.y, aRotate.z } ); // dostosowanie do pochylania linii
            if( false == tmp->Model->Load( parser, false ) ) {
                // model nie wczytał się - ignorowanie node
                delete tmp;
                tmp = nullptr; // nie może być tu return
                break; // nie może być tu return?
            }
            tmp->iFlags |= tmp->Model->Flags(); // ustalenie, czy przezroczysty
            if( false == tmp->asName.empty() ) { // jest pusta gdy "none"
                // dodanie do wyszukiwarki
                if( false == m_nodemap.Add( TP_MODEL, tmp->asName, tmp ) ) {
                    // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność wsteczna)
                    ErrorLog( "Duplicated model: " + tmp->asName ); // to zgłaszać duplikat
                }
            }
        }
        break;
    }
    // case TP_GEOMETRY :
    case GL_TRIANGLES:
    case GL_TRIANGLE_STRIP:
    case GL_TRIANGLE_FAN: {

        parser->getTokens();
        *parser >> token;
        // McZapkie-050702: opcjonalne wczytywanie parametrow materialu (ambient,diffuse,specular)
        if( token.compare( "material" ) == 0 ) {
            parser->getTokens();
            *parser >> token;
            while( token.compare( "endmaterial" ) != 0 ) {
                if( token.compare( "ambient:" ) == 0 ) {
                    parser->getTokens( 3 );
                    *parser
                        >> tmp->Ambient.r
                        >> tmp->Ambient.g
                        >> tmp->Ambient.b;
                    tmp->Ambient /= 255.0f;
                }
                else if( token.compare( "diffuse:" ) == 0 ) { // Ra: coś jest nie tak, bo w jednej linijce nie działa
                    parser->getTokens( 3 );
                    *parser
                        >> tmp->Diffuse.r
                        >> tmp->Diffuse.g
                        >> tmp->Diffuse.b;
                    tmp->Diffuse /= 255.0f;
                }
                else if( token.compare( "specular:" ) == 0 ) {
                    parser->getTokens( 3 );
                    *parser
                        >> tmp->Specular.r
                        >> tmp->Specular.g
                        >> tmp->Specular.b;
                    tmp->Specular /= 255.0f;
                }
                else
                    Error( "Scene material failure!" );
                parser->getTokens();
                *parser >> token;
            }
        }
        if( token.compare( "endmaterial" ) == 0 ) {
            parser->getTokens();
            *parser >> token;
        }
        str = token;
        tmp->m_material = GfxRenderer.Fetch_Material( str );
        auto const texturehandle = (
            tmp->m_material != null_handle ?
                GfxRenderer.Material( tmp->m_material ).texture1 :
                null_handle );
        auto const &texture = (
            texturehandle ?
                GfxRenderer.Texture( texturehandle ) :
                opengl_texture() ); // dirty workaround for lack of better api
        bool const clamps = (
            texturehandle ?
                texture.traits.find( 's' ) != std::string::npos :
                false );
        bool const clampt = (
            texturehandle ?
                texture.traits.find( 't' ) != std::string::npos :
                false );

        tmp->iFlags |= 200; // z usuwaniem
        // remainder of legacy 'problend' system -- geometry assigned a texture with '@' in its name is treated as translucent, opaque otherwise
        if( texturehandle != null_handle ) {
            tmp->iFlags |= (
                ( ( texture.name.find( '@' ) != std::string::npos )
               && ( true == texture.has_alpha ) ) ?
                    0x20 :
                    0x10 );
        }
        else {
            tmp->iFlags |= 0x10;
        }

        TGroundVertex vertex, vertex1, vertex2;
        std::size_t vertexcount{ 0 };
        do {
            parser->getTokens( 8, false );
            *parser
                >> vertex.position.x
                >> vertex.position.y
                >> vertex.position.z
                >> vertex.normal.x
                >> vertex.normal.y
                >> vertex.normal.z
                >> vertex.texture.s
                >> vertex.texture.t;
            vertex.position = glm::rotateZ( vertex.position, glm::radians( aRotate.z ) );
            vertex.position = glm::rotateX( vertex.position, glm::radians( aRotate.x ) );
            vertex.position = glm::rotateY( vertex.position, glm::radians( aRotate.y ) );
            vertex.normal = glm::rotateZ( vertex.normal, glm::radians<float>( aRotate.z ) );
            vertex.normal = glm::rotateX( vertex.normal, glm::radians<float>( aRotate.x ) );
            vertex.normal = glm::rotateY( vertex.normal, glm::radians<float>( aRotate.y ) );
            vertex.position += glm::dvec3{ pOrigin };
            if( true == clamps ) { vertex.texture.s = clamp( vertex.texture.s, 0.001f, 0.999f ); }
            if( true == clampt ) { vertex.texture.t = clamp( vertex.texture.t, 0.001f, 0.999f ); }
            // convert all data to gl_triangles to allow data merge for matching nodes
            switch( tmp->iType ) {
                case GL_TRIANGLES: {
                         if( vertexcount == 0 ) { vertex1 = vertex; }
                    else if( vertexcount == 1 ) { vertex2 = vertex; }
                    else if( vertexcount >= 2 ) {
                        if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) {
                            importedvertices.emplace_back( vertex1 );
                            importedvertices.emplace_back( vertex2 );
                            importedvertices.emplace_back( vertex );
                        }
                        else {
                            ErrorLog(
                                "Bad geometry: degenerate triangle encountered"
                                + ( tmp->asName != "" ? " in node \"" + tmp->asName + "\"" : "" )
                                + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" );
                        }
                    }
                    ++vertexcount;
                    if( vertexcount > 2 ) { vertexcount = 0; } // start new triangle if needed
                    break;
                }
                case GL_TRIANGLE_FAN: {
                         if( vertexcount == 0 ) { vertex1 = vertex; }
                    else if( vertexcount == 1 ) { vertex2 = vertex; }
                    else if( vertexcount >= 2 ) {
                        if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) {
                            importedvertices.emplace_back( vertex1 );
                            importedvertices.emplace_back( vertex2 );
                            importedvertices.emplace_back( vertex );
                            vertex2 = vertex;
                        }
                        else {
                            ErrorLog(
                                "Bad geometry: degenerate triangle encountered"
                                + ( tmp->asName != "" ? " in node \"" + tmp->asName + "\"" : "" )
                                + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" );
                        }
                    }
                    ++vertexcount;
                    break;
                }
                case GL_TRIANGLE_STRIP: {
                         if( vertexcount == 0 ) { vertex1 = vertex; }
                    else if( vertexcount == 1 ) { vertex2 = vertex; }
                    else if( vertexcount >= 2 ) {
                        if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) {
                            // swap order every other triangle, to maintain consistent winding
                            if( vertexcount % 2 == 0 ) {
                                importedvertices.emplace_back( vertex1 );
                                importedvertices.emplace_back( vertex2 );
                            }
                            else {
                                importedvertices.emplace_back( vertex2 );
                                importedvertices.emplace_back( vertex1 );
                            }
                            importedvertices.emplace_back( vertex );

                            vertex1 = vertex2;
                            vertex2 = vertex;
                        }
                        else {
                            ErrorLog(
                                "Bad geometry: degenerate triangle encountered"
                                + ( tmp->asName != "" ? " in node \"" + tmp->asName + "\"" : "" )
                                + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" );
                        }
                    }
                    ++vertexcount;
                    break;
                }
                default: { break; }
            }
            parser->getTokens();
            *parser >> token;

        } while( token.compare( "endtri" ) != 0 );

        tmp->iType = GL_TRIANGLES;
        tmp->Piece = new piece_node();
        tmp->iNumVerts = importedvertices.size();

        if( tmp->iNumVerts > 0 ) {

            tmp->Piece->vertices.swap( importedvertices );

            for( auto const &vertex : tmp->Piece->vertices ) {
                tmp->pCenter += vertex.position;
            }
            tmp->pCenter /= tmp->iNumVerts;

            r = 0;
            for( auto const &vertex : tmp->Piece->vertices ) {
                tf = glm::length2( vertex.position - glm::dvec3{ tmp->pCenter } );
                if( tf > r )
                    r = tf;
            }
            tmp->fSquareRadius += r;
            RaTriangleDivider( tmp ); // Ra: dzielenie trójkątów jest teraz całkiem wydajne
        } // koniec wczytywania trójkątów
        break;
    }
    case GL_LINES:
    case GL_LINE_STRIP:
    case GL_LINE_LOOP: {
            parser->getTokens( 4 );
            *parser
                >> tmp->Diffuse.r
                >> tmp->Diffuse.g
                >> tmp->Diffuse.b
                >> tmp->fLineThickness;
            tmp->Diffuse /= 255.0f;
            tmp->fLineThickness = std::min( 30.0, tmp->fLineThickness ); // 30 pix equals rougly width of a signal pole viewed from ~1m away

            TGroundVertex vertex, vertex0, vertex1;
            std::size_t vertexcount{ 0 };

            parser->getTokens();
            *parser >> token;
            do {
                vertex.position.x = std::atof( token.c_str() );
                parser->getTokens( 2 );
                *parser
                    >> vertex.position.y
                    >> vertex.position.z;
                vertex.position = glm::rotateZ( vertex.position, glm::radians( aRotate.z ) );
                vertex.position = glm::rotateX( vertex.position, glm::radians( aRotate.x ) );
                vertex.position = glm::rotateY( vertex.position, glm::radians( aRotate.y ) );

                vertex.position += glm::dvec3{ pOrigin };
                // convert all data to gl_lines to allow data merge for matching nodes
                switch( tmp->iType ) {
                    case GL_LINES: {
                        importedvertices.emplace_back( vertex );
                        break;
                    }
                    case GL_LINE_STRIP: {
                        if( vertexcount > 0 ) {
                            importedvertices.emplace_back( vertex1 );
                            importedvertices.emplace_back( vertex );
                        }
                        vertex1 = vertex;
                        ++vertexcount;
                        break;
                    }
                    case GL_LINE_LOOP: {
                        if( vertexcount == 0 ) {
                            vertex0 = vertex;
                            vertex1 = vertex;
                        }
                        else {
                            importedvertices.emplace_back( vertex1 );
                            importedvertices.emplace_back( vertex );
                        }
                        vertex1 = vertex;
                        ++vertexcount;
                        break;
                    }
                    default: { break; }
                }
                parser->getTokens();
                *parser >> token;
            } while( token.compare( "endline" ) != 0 );
            // add closing line for the loop
            if( ( tmp->iType == GL_LINE_LOOP )
             && ( vertexcount > 2 ) ) {
                importedvertices.emplace_back( vertex1 );
                importedvertices.emplace_back( vertex0 );
            }
            if( importedvertices.size() % 2 != 0 ) {
                ErrorLog( "Lines node specified odd number of vertices, encountered in file \"" + parser->Name() + "\" (line " + std::to_string( parser->Line() - 1 ) + ")" );
                importedvertices.pop_back();
            }
            tmp->iType = GL_LINES;
            tmp->Piece = new piece_node();
            tmp->iNumPts = importedvertices.size();

            if( false == importedvertices.empty() ) {

                tmp->Piece->vertices.swap( importedvertices );

                glm::dvec3 minpoint( std::numeric_limits<double>::max(), 0.0, std::numeric_limits<double>::max() );
                glm::dvec3 maxpoint( std::numeric_limits<double>::lowest(), 0.0, std::numeric_limits<double>::lowest() );
                for( auto const &vertex : tmp->Piece->vertices ) {
                    tmp->pCenter += vertex.position;
                    minpoint.x = std::min( minpoint.x, vertex.position.x );
                    minpoint.z = std::min( minpoint.z, vertex.position.z );
                    maxpoint.x = std::max( maxpoint.x, vertex.position.x );
                    maxpoint.z = std::max( maxpoint.z, vertex.position.z );
                }
                tmp->pCenter /= tmp->iNumPts;
                tmp->m_radius = static_cast<float>( glm::distance( maxpoint, minpoint ) * 0.5 );
            }
            break;
        }
#endif
        default: {
            break;
        }
    }
    return tmp;
}

TSubRect * TGround::FastGetSubRect(int iCol, int iRow)
{
    int br, bc, sr, sc;
    br = iRow / iNumSubRects;
    bc = iCol / iNumSubRects;
    sr = iRow - br * iNumSubRects;
    sc = iCol - bc * iNumSubRects;
    if ((br < 0) || (bc < 0) || (br >= iNumRects) || (bc >= iNumRects))
        return NULL;
    return (Rects[bc][br].FastGetSubRect(sc, sr));
}

TSubRect * TGround::GetSubRect(int iCol, int iRow)
{ // znalezienie małego kwadratu mapy
    int br, bc, sr, sc;
    br = iRow / iNumSubRects; // współrzędne kwadratu kilometrowego
    bc = iCol / iNumSubRects;
    sr = iRow - br * iNumSubRects; // współrzędne wzglęne małego kwadratu
    sc = iCol - bc * iNumSubRects;
    if ((br < 0) || (bc < 0) || (br >= iNumRects) || (bc >= iNumRects))
        return NULL; // jeśli poza mapą
    return (Rects[bc][br].SafeGetSubRect(sc, sr)); // pobranie małego kwadratu
}

#ifdef EU07_USE_OLD_GROUNDCODE
TEvent * TGround::FindEvent(const std::string &asEventName)
{
    if( asEventName.empty() ) { return nullptr; }

    auto const lookup = m_eventmap.find( asEventName );
    return (
        lookup != m_eventmap.end() ?
            lookup->second :
            nullptr );
}

void TGround::FirstInit()
{ // ustalanie zależności na scenerii przed wczytaniem pojazdów
    if (bInitDone)
        return; // Ra: żeby nie robiło się dwa razy
    bInitDone = true;
    WriteLog("InitNormals");
    for (int type = 0; type < TP_LAST; ++type) {
        for (TGroundNode *Current = nRootOfType[type]; Current != nullptr; Current = Current->nNext) {

            if( type == GL_TRIANGLES ) { Current->InitNormals(); }

            if (Current->iType != TP_DYNAMIC)
            { // pojazdów w ogóle nie dotyczy dodawanie do mapy
                if( ( type == TP_EVLAUNCH )
                 && ( true == Current->EvLaunch->IsGlobal() ) ) {
                    // dodanie do globalnego obiektu
                    srGlobal.NodeAdd( Current );
                }
#ifdef EU07_USE_OLD_TERRAINCODE
                else if (type == TP_TERRAIN) {
                    // specjalne przetwarzanie terenu wczytanego z pliku E3D
                    TGroundRect *gr;
                    for (int j = 1; j < Current->iCount; ++j) {
                        // od 1 do końca są zestawy trójkątów
                        std::string xxxzzz = Current->nNode[j].smTerrain->pName; // pobranie nazwy
                        gr = GetRect(
                            ( std::stoi( xxxzzz.substr( 0, 3 )) - 500 ) * 1000,
                            ( std::stoi( xxxzzz.substr( 3, 3 )) - 500 ) * 1000 );
                        gr->nTerrain = Current->nNode + j; // zapamiętanie
                    }
                }
#endif
                else {
                    TSubRect *targetcell { nullptr };
                    // test whether we can add the node to a ground cell, or a subcell
                    if( ( Current->iType != GL_TRIANGLES )
                     || ( Current->iFlags & 0x20 )
                     || ( Current->fSquareMinRadius != 0.0 )
                     || ( Current->fSquareRadius <= 90000.0 ) ) {
                        // add to sub-rectangle
                        targetcell = GetSubRect( Current->pCenter.x, Current->pCenter.z );
                    }
                    else {
                        // dodajemy do kwadratu kilometrowego
                        targetcell = GetRect( Current->pCenter.x, Current->pCenter.z );
                    }
                    if( targetcell != nullptr ) {
                        targetcell->NodeAdd( Current );
                    }
                    else {
                        ErrorLog( "Scenery node" + (
                            Current->asName == "" ?
                                "" :
                                " \"" + Current->asName + "\"" )
                            + " placed in location outside of map bounds (location: " + to_string( glm::dvec3{ Current->pCenter } ) + ")" );
                    }
                }
            }
        }
    }
    for (std::size_t i = 0; i < iNumRects; ++i)
        for (std::size_t j = 0; j < iNumRects; ++j)
            Rects[i][j].Optimize(); // optymalizacja obiektów w sektorach

    WriteLog("InitNormals OK");

    InitTracks(); //łączenie odcinków ze sobą i przyklejanie eventów
    WriteLog("InitTracks OK");
    InitTraction(); //łączenie drutów ze sobą
    WriteLog("InitTraction OK");
    InitEvents();
    WriteLog( "InitEvents OK" );
    InitLaunchers();
    WriteLog("InitLaunchers OK");
    WriteLog("FirstInit is done");
};

bool TGround::Init(std::string File)
{ // główne wczytywanie scenerii
    if (ToLower(File).substr(0, 7) == "scenery")
        File = File.erase(0, 8); // Ra: usunięcie niepotrzebnych znaków - zgodność wstecz z 2003
    WriteLog("Loading scenery from " + File);
    Global::pGround = this;
    // pTrain=NULL;
    pOrigin = aRotate = vector3(0, 0, 0); // zerowanie przesunięcia i obrotu
    std::string str;
    // int size;
    std::string subpath = Global::asCurrentSceneryPath; //   "scenery/";
    cParser parser(File, cParser::buffer_FILE, subpath, Global::bLoadTraction);
    std::string token;

    std::stack<Math3D::vector3> OriginStack; // stos zagnieżdżenia origin

    TGroundNode *LastNode = nullptr; // do użycia w trainset
    token = "";
    parser.getTokens();
    parser >> token;
    std::size_t processed = 0;

    while (token != "") //(!Parser->EndOfFile)
    {
        ++processed;
        if( processed % 1000 == 0 )
        {
            glfwPollEvents();
            UILayer.set_progress( parser.getProgress(), parser.getFullProgress() );
            GfxRenderer.Render();
        }
        str = token;
        if (str == "node") {
            // rozpoznanie węzła
            LastNode = AddGroundNode(&parser);
            if (LastNode) {
                // jeżeli przetworzony poprawnie
                switch( LastNode->iType ) {
                    // validate the new node
                    case GL_TRIANGLES: {
                        if( true == LastNode->Piece->vertices.empty() ) {
                            SafeDelete( LastNode );
                        }
                        break;
                    }
                    case TP_TRACTION: {
                        if( false == Global::bLoadTraction ) {
                            // usuwamy druty, jeśli wyłączone
                            SafeDelete( LastNode );
                        }
                        break;
                    }
#ifndef EU07_SCENERY_EDITOR
                    case TP_TERRAIN: {
                        // convert legacy terrain model to a series of triangle nodes, to take advantage of camera-centric render and geometry merging
                        // NOTE: this leaves us with a large model we don't use loaded and taking space. it'll sort of solve itself when the binary scenery file is in place
                        convert_terrain( LastNode );
                        SafeDelete( LastNode );
                        Global::pTerrainCompact = nullptr;
                    }
#endif
                    default: {
                        break;
                    }
                }

                if( LastNode ) {
                    // dopiero na koniec dopisujemy do tablic
                    if( LastNode->iType != TP_DYNAMIC ) {
                        // jeśli nie jest pojazdem ostatni dodany dołączamy na końcu nowego
                        LastNode->nNext = nRootOfType[ LastNode->iType ];
                        // ustawienie nowego na początku listy
                        nRootOfType[ LastNode->iType ] = LastNode;
                    }
                    else { // jeśli jest pojazdem
                        if( ( LastNode->DynamicObject->Mechanik != nullptr )
                         && ( LastNode->DynamicObject->Mechanik->Primary() ) ) {
                            // jeśli jest głównym (pasażer nie jest)
                            nTrainSetDriver = LastNode; // pojazd, któremu zostanie wysłany rozkład
                        }
                        LastNode->nNext = nRootDynamic;
                        nRootDynamic = LastNode; // dopisanie z przodu do listy

                        if( nTrainSetNode != nullptr ) {
                            // jeżeli istnieje wcześniejszy TP_DYNAMIC
                            nTrainSetNode->DynamicObject->AttachPrev(
                                LastNode->DynamicObject,
                                TempConnectionType[ iTrainSetWehicleNumber - 2 ] );
                        }
                        nTrainSetNode = LastNode; // ostatnio wczytany

                        if( TempConnectionType[ iTrainSetWehicleNumber - 1 ] == 0 ) // jeśli sprzęg jest zerowy, to wysłać rozkład do składu
                        { // powinien też tu wchodzić, gdy pojazd bez trainset
                            if( nTrainSetDriver ) // pojazd, któremu zostanie wysłany rozkład
                            { // wysłanie komendy "Timetable" ustawia odpowiedni tryb jazdy
                                nTrainSetDriver->DynamicObject->Mechanik->DirectionInitial();
                                nTrainSetDriver->DynamicObject->Mechanik->PutCommand(
                                    "Timetable:" + asTrainName,
                                    fTrainSetVel, 0,
                                    nullptr );
                                nTrainSetDriver = nullptr; // a przy "endtrainset" już wtedy nie potrzeba
                            }
                        }
                    }
                }
            }
            else
            {
                ErrorLog("Bad node: node parsing error, encountered in file \"" + parser.Name() + "\" (line " + std::to_string( parser.Line() - 1 ) + ")");
                // break;
            }
        }
        else if (str == "trainset")
        {
            iTrainSetWehicleNumber = 0;
            nTrainSetNode = NULL;
            nTrainSetDriver = NULL; // pojazd, któremu zostanie wysłany rozkład
            bTrainSet = true;
            parser.getTokens();
            parser >> token;
            asTrainName = token; // McZapkie: rodzaj+nazwa pociagu w SRJP
            parser.getTokens();
            parser >> token;
            asTrainSetTrack = token; //ścieżka startowa
            parser.getTokens(2);
            parser >> fTrainSetDist >> fTrainSetVel; // przesunięcie i prędkość
        }
        else if (str == "endtrainset")
        { // McZapkie-110103: sygnaly konca pociagu ale tylko dla pociagow rozkladowych
            if (nTrainSetNode) // trainset bez dynamic się sypał
            { // powinien też tu wchodzić, gdy pojazd bez trainset
                if (nTrainSetDriver) // pojazd, któremu zostanie wysłany rozkład
                { // wysłanie komendy "Timetable" ustawia odpowiedni tryb jazdy
                    nTrainSetDriver->DynamicObject->Mechanik->DirectionInitial();
                    nTrainSetDriver->DynamicObject->Mechanik->PutCommand("Timetable:" + asTrainName, fTrainSetVel, 0, NULL);
                }
            }
            if( LastNode ) {
                // ostatni wczytany obiekt
                if( LastNode->iType == TP_DYNAMIC ) {
                    // o ile jest pojazdem (na ogół jest, ale kto wie...)
                    if( ( iTrainSetWehicleNumber > 0 )
                     && ( TempConnectionType[ iTrainSetWehicleNumber - 1 ] == 0 ) ) {
                        // jeśli ostatni pojazd ma sprzęg 0 to założymy mu końcówki blaszane
                        // (jak AI się odpali, to sobie poprawi)
                        LastNode->DynamicObject->RaLightsSet( -1, 2 + 32 + 64 );
                    }
                }
            }
            bTrainSet = false;
            fTrainSetVel = 0;
            nTrainSetNode = nTrainSetDriver = nullptr;
            iTrainSetWehicleNumber = 0;
        }
        else if (str == "event")
        {
            TEvent *tmp = new TEvent();
            tmp->Load(&parser, pOrigin);
            if (tmp->Type == tp_Unknown)
                delete tmp;
            else
            { // najpierw sprawdzamy, czy nie ma, a potem dopisujemy
                TEvent *found = FindEvent(tmp->asName);
                if (found)
                { // jeśli znaleziony duplikat
                    auto const size = tmp->asName.size();
                    if( tmp->asName[0] == '#' ) // zawsze jeden znak co najmniej jest
                    {
                        delete tmp;
                        tmp = nullptr;
                    } // utylizacja duplikatu z krzyżykiem
                    else if( ( size > 8 )
                          && ( tmp->asName.substr( 0, 9 ) == "lineinfo:" ))
                        // tymczasowo wyjątki
                    {
                        delete tmp;
                        tmp = nullptr;
                    } // tymczasowa utylizacja duplikatów W5
                    else if( ( size > 8 )
                          && ( tmp->asName.substr( size - 8 ) == "_warning"))
                        // tymczasowo wyjątki
                    {
                        delete tmp;
                        tmp = nullptr;
                    } // tymczasowa utylizacja duplikatu z trąbieniem
                    else if( ( size > 4 )
                          && ( tmp->asName.substr( size - 4 ) == "_shp" ))
                          // nie podlegają logowaniu
                    {
                        delete tmp;
                        tmp = NULL;
                    } // tymczasowa utylizacja duplikatu SHP
                    if (tmp) // jeśli nie został zutylizowany
                        if (Global::bJoinEvents)
                            found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków)
                        else
                        {
                            ErrorLog("Duplicated event: " + tmp->asName);
                            found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków)
                            found->m_ignored = true; // dezaktywacja pierwotnego - taka proteza na
                            // wsteczną zgodność
                            // SafeDelete(tmp); //bezlitośnie usuwamy wszelkie duplikaty, żeby nie
                            // zaśmiecać drzewka
                        }
                }
                if ( nullptr != tmp )
                { // jeśli nie duplikat
                    tmp->evNext2 = RootEvent; // lista wszystkich eventów (m.in. do InitEvents)
                    RootEvent = tmp;
                    if (!found)
                    { // jeśli nazwa wystąpiła, to do kolejki i wyszukiwarki dodawany jest tylko pierwszy
                        if( ( RootEvent->m_ignored == false )
                         && ( RootEvent->asName.find( "onstart" ) != std::string::npos ) ) {
                            // event uruchamiany automatycznie po starcie
                            AddToQuery( RootEvent, NULL ); // dodanie do kolejki
                        }
                        m_eventmap.emplace( tmp->asName, tmp ); // dodanie do wyszukiwarki
                    }
                }
            }
        }
        else if (str == "rotate")
        {
            // parser.getTokens(3);
            // parser >> aRotate.x >> aRotate.y >> aRotate.z; //Ra: to potrafi dawać błędne rezultaty // ??? how so TODO: investigate
            parser.getTokens();
            parser >> aRotate.x;
            parser.getTokens();
            parser >> aRotate.y;
            parser.getTokens();
            parser >> aRotate.z;
        }
        else if (str == "origin")
        {
            Math3D::vector3 offset;
            parser.getTokens(3);
            parser
                >> offset.x
                >> offset.y
                >> offset.z;
            // sumowanie całkowitego przesunięcia
            OriginStack.emplace(
                offset + (
                OriginStack.empty() ?
                    Math3D::vector3() :
                    OriginStack.top() ) );
            pOrigin = OriginStack.top();
        }
        else if (str == "endorigin")
        {
            if( true == OriginStack.empty() ) {
                // report error here
            }
            else {
                OriginStack.pop();
            }
            pOrigin = ( OriginStack.empty() ?
                Math3D::vector3() :
                OriginStack.top() );
        }
        else if (str == "atmo") // TODO: uporzadkowac gdzie maja byc parametry mgly!
        { // Ra: ustawienie parametrów OpenGL przeniesione do FirstInit
            WriteLog("Scenery atmo definition");
            parser.getTokens(3);
/*
            // disabled, no longer used
            parser
                >> Global::AtmoColor[0]
                >> Global::AtmoColor[1]
                >> Global::AtmoColor[2];
*/
            parser.getTokens(2);
            parser
                >> Global::fFogStart
                >> Global::fFogEnd;
            if (Global::fFogEnd > 0.0)
            { // ostatnie 3 parametry są opcjonalne
                parser.getTokens(3);
                parser
                    >> Global::FogColor[0]
                    >> Global::FogColor[1]
                    >> Global::FogColor[2];
            }
            parser.getTokens();
            parser >> token;
            if( token != "endatmo" ) {
                // optional overcast parameter
                // NOTE: parameter system needs some decent replacement, but not worth the effort if we're moving to built-in editor
                Global::Overcast = clamp( std::stof( token ), 0.0f, 1.0f );
            }
            while (token.compare("endatmo") != 0)
            { // a kolejne parametry są pomijane
                parser.getTokens();
                parser >> token;
            }
        }
        else if (str == "time")
        {
            WriteLog("Scenery time definition");
            parser.getTokens();
            parser >> token;

            cParser timeparser( token );
            timeparser.getTokens( 2, false, ":" );
            auto &time = simulation::Time.data();
            timeparser
                >> time.wHour
                >> time.wMinute;

            // NOTE: we ignore old sunrise and sunset definitions, as they're now calculated dynamically

            while (token.compare("endtime") != 0)
            {
                parser.getTokens();
                parser >> token;
            }
        }
        else if (str == "light")
        { // Ra: ustawianie światła przeniesione do FirstInit
            WriteLog("Scenery light definition");
            parser.getTokens(3, false);
            parser
                >> Global::DayLight.direction.x
                >> Global::DayLight.direction.y
                >> Global::DayLight.direction.z;;
            Global::DayLight.direction = glm::normalize( Global::DayLight.direction );
            parser.getTokens(9, false);

            do {
                parser.getTokens();
                parser >> token;
            } while (token.compare("endlight") != 0);
        }
        else if (str == "camera")
        {
            vector3 xyz, abc;
            xyz = abc = vector3(0, 0, 0); // wartości domyślne, bo nie wszystie muszą być
            int i = -1, into = -1; // do której definicji kamery wstawić
            WriteLog("Scenery camera definition");
            do
            { // opcjonalna siódma liczba określa numer kamery, a kiedyś były tylko 3
                parser.getTokens();
                parser >> token;
                switch (++i)
                { // kiedyś camera miało tylko 3 współrzędne
                case 0:
                    xyz.x = atof(token.c_str());
                    break;
                case 1:
                    xyz.y = atof(token.c_str());
                    break;
                case 2:
                    xyz.z = atof(token.c_str());
                    break;
                case 3:
                    abc.x = atof(token.c_str());
                    break;
                case 4:
                    abc.y = atof(token.c_str());
                    break;
                case 5:
                    abc.z = atof(token.c_str());
                    break;
                case 6:
                    into = atoi(token.c_str()); // takie sobie, bo można wpisać -1
                }
            } while (token.compare("endcamera") != 0);
            if (into < 0)
                into = ++Global::iCameraLast;
            if ((into >= 0) && (into < 10))
            { // przepisanie do odpowiedniego miejsca w tabelce
                Global::FreeCameraInit[ into ] = xyz;
                Global::FreeCameraInitAngle[ into ] =
                    Math3D::vector3(
                        DegToRad( abc.x ),
                        DegToRad( abc.y ),
                        DegToRad( abc.z ) );
                Global::iCameraLast = into; // numer ostatniej
            }
        }
        else if (str == "sky")
        { // youBy - niebo z pliku
            WriteLog("Scenery sky definition");
            parser.getTokens();
            parser >> token;
            std::string SkyTemp = token;
            if (Global::asSky == "1")
                Global::asSky = SkyTemp;
            do
            { // pożarcie dodatkowych parametrów
                parser.getTokens();
                parser >> token;
            } while (token.compare("endsky") != 0);
            WriteLog(Global::asSky.c_str());
        }
        else if (str == "firstinit")
            FirstInit();
        else if (str == "description")
        {
            do
            {
                parser.getTokens();
                parser >> token;
            } while (token.compare("enddescription") != 0);
        }
        else if (str == "test")
        { // wypisywanie treści po przetworzeniu
            WriteLog("---> Parser test:");
            do
            {
                parser.getTokens();
                parser >> token;
                WriteLog(token.c_str());
            } while (token.compare("endtest") != 0);
            WriteLog("---> End of parser test.");
        }
        else if (str == "config")
        { // możliwość przedefiniowania parametrów w scenerii
            Global::ConfigParse(parser); // parsowanie dodatkowych ustawień
        }
        else if (str != "") {
            // pomijanie od nierozpoznanej komendy do jej zakończenia
            if ((token.length() > 2) && (atof(token.c_str()) == 0.0)) {
                // jeśli nie liczba, to spróbować pominąć komendę
                WriteLog( "Unrecognized command: \"" + str + "\" encountered in file \"" + parser.Name() + "\" (line " + std::to_string( parser.Line() - 1 ) + ")" );
                str = "end" + str;
                do
                {
                    parser.getTokens();
                    token = "";
                    parser >> token;
                } while ((token != "") && (token.compare(str.c_str()) != 0));
            }
            else {
                // jak liczba to na pewno błąd
                ErrorLog( "Unrecognized command: \"" + str + "\" encountered in file \"" + parser.Name() + "\" (line " + std::to_string( parser.Line() - 1 ) + ")" );
            }
        }

        token = "";
        parser.getTokens();
        parser >> token;
    }

    if (!bInitDone)
        FirstInit(); // jeśli nie było w scenerii

    if (Global::pTerrainCompact)
        TerrainWrite(); // Ra: teraz można zapisać teren w jednym pliku
    Global::iPause &= ~0x10; // koniec pauzy wczytywania
    return true;
}

bool TGround::InitEvents()
{ //łączenie eventów z pozostałymi obiektami
    TGroundNode *tmp, *trk;
    std::string cellastext;
    int i;
    for (TEvent *Current = RootEvent; Current; Current = Current->evNext2)
    {
        switch (Current->Type)
        {
        case tp_AddValues: // sumowanie wartości
        case tp_UpdateValues: // zmiana wartości
            tmp = FindGroundNode(Current->asNodeName, TP_MEMCELL); // nazwa komórki powiązanej z eventem
            if (tmp)
            { // McZapkie-100302
                if (Current->iFlags & (conditional_trackoccupied | conditional_trackfree))
                { // jeśli chodzi o zajetosc toru (tor może być inny, niż wpisany w komórce)
                    trk = FindGroundNode(Current->asNodeName, TP_TRACK); // nazwa toru ta sama, co nazwa komórki
                    if (trk)
                        Current->Params[9].asTrack = trk->pTrack;
                    if (!Current->Params[9].asTrack)
                        ErrorLog("Bad event: track \"" + Current->asNodeName + "\" referenced in event \"" + Current->asName + "\" doesn't exist");
                }
                Current->Params[4].nGroundNode = tmp;
                Current->Params[5].asMemCell = tmp->MemCell; // komórka do aktualizacji
                if (Current->iFlags & (conditional_memcompare))
                    Current->Params[9].asMemCell = tmp->MemCell; // komórka do badania warunku
                if (!tmp->MemCell->asTrackName
                         .empty()) // tor powiązany z komórką powiązaną z eventem
                { // tu potrzebujemy wskaźnik do komórki w (tmp)
                    trk = FindGroundNode(tmp->MemCell->asTrackName, TP_TRACK);
                    if (trk)
                        Current->Params[6].asTrack = trk->pTrack;
                    else
                        ErrorLog("Bad memcell: track \"" + tmp->MemCell->asTrackName + "\" referenced in memcell \"" + tmp->asName + "\" doesn't exist");
                }
                else
                    Current->Params[6].asTrack = NULL;
            }
            else
            { // nie ma komórki, to nie będzie działał poprawnie
                Current->m_ignored = true; // deaktywacja
                ErrorLog("Bad event: event \"" + Current->asName + "\" cannot find memcell \"" + Current->asNodeName + "\"");
            }
            break;
        case tp_LogValues: // skojarzenie z memcell
            if (Current->asNodeName.empty())
            { // brak skojarzenia daje logowanie wszystkich
                Current->Params[9].asMemCell = NULL;
                break;
            }
        case tp_GetValues:
        case tp_WhoIs:
            tmp = FindGroundNode(Current->asNodeName, TP_MEMCELL);
            if (tmp)
            {
                Current->Params[8].nGroundNode = tmp;
                Current->Params[9].asMemCell = tmp->MemCell;
                if (Current->Type == tp_GetValues) // jeśli odczyt komórki
                    if (tmp->MemCell->IsVelocity()) // a komórka zawiera komendę SetVelocity albo
                        // ShuntVelocity
                        Current->bEnabled = false; // to event nie będzie dodawany do kolejki
            }
            else
            { // nie ma komórki, to nie będzie działał poprawnie
                Current->m_ignored = true; // deaktywacja
                ErrorLog("Bad event: event \"" + Current->asName + "\" cannot find memcell \"" + Current->asNodeName + "\"");
            }
            break;
        case tp_CopyValues: // skopiowanie komórki do innej
            tmp = FindGroundNode(Current->asNodeName, TP_MEMCELL); // komórka docelowa
            if (tmp)
            {
                Current->Params[4].nGroundNode = tmp;
                Current->Params[5].asMemCell = tmp->MemCell; // komórka docelowa
                if (!tmp->MemCell->asTrackName
                         .empty()) // tor powiązany z komórką powiązaną z eventem
                { // tu potrzebujemy wskaźnik do komórki w (tmp)
                    trk = FindGroundNode(tmp->MemCell->asTrackName, TP_TRACK);
                    if (trk)
                        Current->Params[6].asTrack = trk->pTrack;
                    else
                        ErrorLog("Bad memcell: track \"" + tmp->MemCell->asTrackName + "\" referenced in memcell \"" + tmp->asName + "\" doesn't exists");
                }
                else
                    Current->Params[6].asTrack = NULL;
            }
            else
                ErrorLog("Bad event: copyvalues event \"" + Current->asName + "\" cannot find memcell \"" + Current->asNodeName + "\"");
            cellastext = Current->Params[ 9 ].asText;
            SafeDeleteArray(Current->Params[9].asText); // usunięcie nazwy komórki
            tmp = FindGroundNode( cellastext, TP_MEMCELL); // komórka źódłowa
            if (tmp != nullptr ) {
                Current->Params[8].nGroundNode = tmp;
                Current->Params[9].asMemCell = tmp->MemCell; // komórka źródłowa
            }
            else
                ErrorLog("Bad event: copyvalues event \"" + Current->asName + "\" cannot find memcell \"" + cellastext + "\"");
            break;
        case tp_Animation: // animacja modelu
            tmp = FindGroundNode(Current->asNodeName, TP_MODEL); // egzemplarza modelu do animowania
            if (tmp)
            {
                cellastext = Current->Params[9].asText; // skopiowanie nazwy submodelu do bufora roboczego
                SafeDeleteArray(Current->Params[9].asText); // usunięcie nazwy submodelu
                if (Current->Params[0].asInt == 4)
                    Current->Params[9].asModel = tmp->Model; // model dla całomodelowych animacji
                else
                { // standardowo przypisanie submodelu
                    Current->Params[9].asAnimContainer = tmp->Model->GetContainer(cellastext); // submodel
                    if (Current->Params[9].asAnimContainer)
                    {
                        Current->Params[9].asAnimContainer->WillBeAnimated(); // oflagowanie
                        // animacji
                        if (!Current->Params[9]
                                 .asAnimContainer->Event()) // nie szukać, gdy znaleziony
                            Current->Params[9].asAnimContainer->EventAssign(
                                FindEvent(Current->asNodeName + "." + cellastext + ":done"));
                    }
                }
            }
            else
                ErrorLog("Bad event: animation event \"" + Current->asName + "\" cannot find model \"" + Current->asNodeName + "\"");
            Current->asNodeName = "";
            break;
        case tp_Lights: // zmiana świeteł modelu
            tmp = FindGroundNode(Current->asNodeName, TP_MODEL);
            if (tmp)
                Current->Params[9].asModel = tmp->Model;
            else
                ErrorLog("Bad event: lights event \"" + Current->asName + "\" cannot find model \"" + Current->asNodeName + "\"");
            Current->asNodeName = "";
            break;
        case tp_Visible: // ukrycie albo przywrócenie obiektu
            tmp = FindGroundNode(Current->asNodeName, TP_MODEL); // najpierw model
            if (!tmp)
                tmp = FindGroundNode(Current->asNodeName, TP_TRACK); // albo tory?
            if (!tmp)
                tmp = FindGroundNode(Current->asNodeName, TP_TRACTION); // może druty?
            if (tmp)
                Current->Params[9].nGroundNode = tmp;
            else
                ErrorLog("Bad event: visibility event \"" + Current->asName + "\" cannot find item \"" + Current->asNodeName + "\"");
            Current->asNodeName = "";
            break;
        case tp_Switch: // przełożenie zwrotnicy albo zmiana stanu obrotnicy
            tmp = FindGroundNode(Current->asNodeName, TP_TRACK);
            if (tmp)
            { // dowiązanie toru
                if (!tmp->pTrack->iAction) // jeśli nie jest zwrotnicą ani obrotnicą
                    tmp->pTrack->iAction |= 0x100; // to będzie się zmieniał stan uszkodzenia
                Current->Params[9].asTrack = tmp->pTrack;
                if (!Current->Params[0].asInt) // jeśli przełącza do stanu 0
                    if (Current->Params[2].asdouble >=
                        0.0) // jeśli jest zdefiniowany dodatkowy ruch iglic
                        Current->Params[9].asTrack->Switch(
                            0, Current->Params[1].asdouble,
                            Current->Params[2].asdouble); // przesłanie parametrów
            }
            else
                ErrorLog("Bad event: switch event \"" + Current->asName + "\" cannot find track \"" + Current->asNodeName + "\"");
            Current->asNodeName = "";
            break;
        case tp_Sound: // odtworzenie dźwięku
            tmp = FindGroundNode(Current->asNodeName, TP_SOUND);
            if (tmp)
                Current->Params[9].tsTextSound = tmp->tsStaticSound;
            else
                ErrorLog("Bad event: sound event \"" + Current->asName + "\" cannot find static sound \"" + Current->asNodeName + "\"");
            Current->asNodeName = "";
            break;
        case tp_TrackVel: // ustawienie prędkości na torze
            if (!Current->asNodeName.empty())
            {
                tmp = FindGroundNode(Current->asNodeName, TP_TRACK);
                if (tmp)
                {
                    tmp->pTrack->iAction |=
                        0x200; // flaga zmiany prędkości toru jest istotna dla skanowania
                    Current->Params[9].asTrack = tmp->pTrack;
                }
                else
                    ErrorLog("Bad event: track velocity event \"" + Current->asName + "\" cannot find track \"" + Current->asNodeName + "\"");
            }
            Current->asNodeName = "";
            break;
        case tp_DynVel: // komunikacja z pojazdem o konkretnej nazwie
            if (Current->asNodeName == "activator")
                Current->Params[9].asDynamic = NULL;
            else
            {
                tmp = FindGroundNode(Current->asNodeName, TP_DYNAMIC);
                if (tmp)
                    Current->Params[9].asDynamic = tmp->DynamicObject;
                else
                    Error("Bad event: vehicle velocity event \"" + Current->asName + "\" cannot find vehicle \"" + Current->asNodeName + "\"");
            }
            Current->asNodeName = "";
            break;
        case tp_Multiple:
            if (Current->Params[9].asText != NULL)
            { // przepisanie nazwy do bufora
                cellastext = Current->Params[ 9 ].asText;
                SafeDeleteArray(Current->Params[9].asText);
                Current->Params[9].asPointer = NULL; // zerowanie wskaźnika, aby wykryć brak obeiktu
            }
            else
                cellastext = "";
            if (Current->iFlags & (conditional_trackoccupied | conditional_trackfree))
            { // jeśli chodzi o zajetosc toru
                tmp = FindGroundNode(cellastext, TP_TRACK);
                if (tmp)
                    Current->Params[9].asTrack = tmp->pTrack;
                if (!Current->Params[9].asTrack)
                {
                    ErrorLog( "Bad event: multi-event \"" + Current->asName + "\" cannot find track \"" + cellastext + "\"" );
                    Current->iFlags &= ~(conditional_trackoccupied | conditional_trackfree); // zerowanie flag
                }
            }
            else if (Current->iFlags & (conditional_memstring | conditional_memval1 | conditional_memval2))
            { // jeśli chodzi o komorke pamieciową
                tmp = FindGroundNode(cellastext, TP_MEMCELL);
                if (tmp)
                    Current->Params[9].asMemCell = tmp->MemCell;
                if (!Current->Params[9].asMemCell)
                {
                    ErrorLog( "Bad event: multi-event \"" + Current->asName + "\" cannot find memory cell \"" + cellastext + "\"" );
                    Current->iFlags &= ~(conditional_memstring | conditional_memval1 | conditional_memval2);
                }
            }
            for (i = 0; i < 8; ++i)
            {
                if (Current->Params[i].asText != NULL)
                {
                    cellastext = Current->Params[ i ].asText;
                    SafeDeleteArray(Current->Params[i].asText);
                    Current->Params[i].asEvent = FindEvent(cellastext);
					if( !Current->Params[ i ].asEvent ) {
                        // Ra: tylko w logu informacja o braku
						if( ( Current->Params[ i ].asText == NULL )
						 || ( std::string( Current->Params[ i ].asText ).substr( 0, 5 ) != "none_" ) ) {
                            ErrorLog( "Bad event: multi-event \"" + Current->asName + "\" cannot find event \"" + cellastext + "\"" );
						}
                    }
                }
            }
            break;
        case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource)
            if (!Current->asNodeName.empty())
            {
                tmp = FindGroundNode(Current->asNodeName, TP_TRACTIONPOWERSOURCE); // podłączenie zasilacza
                if (tmp)
                    Current->Params[9].psPower = tmp->psTractionPowerSource;
                else
                    ErrorLog("Bad event: voltage event \"" + Current->asName + "\" cannot find power source \"" + Current->asNodeName + "\"");
            }
            Current->asNodeName = "";
            break;
        case tp_Message: // wyświetlenie komunikatu
            break;
        }
        if (Current->fDelay < 0)
            AddToQuery(Current, NULL);
    }
    for (TGroundNode *Current = nRootOfType[TP_MEMCELL]; Current; Current = Current->nNext)
    { // Ra: eventy komórek pamięci, wykonywane po wysłaniu komendy do zatrzymanego pojazdu
        Current->MemCell->AssignEvents(FindEvent(Current->asName + ":sent"));
    }
    return true;
}

void TGround::InitTracks()
{ //łączenie torów ze sobą i z eventami
    TGroundNode *Current, *Model;
    TTrack *tmp; // znaleziony tor
    TTrack *Track;
	int iConnection;

    for (Current = nRootOfType[TP_TRACK]; Current; Current = Current->nNext) {
        Track = Current->pTrack;
        // assign track events
        Track->AssignEvents(
            FindEvent( Track->asEvent0Name ),
            FindEvent( Track->asEvent1Name ),
            FindEvent( Track->asEvent2Name ) );
        Track->AssignallEvents(
            FindEvent( Track->asEventall0Name ),
            FindEvent( Track->asEventall1Name ),
            FindEvent( Track->asEventall2Name ) );
        if( ( Global::iHiddenEvents & 1 )
         && ( false == Current->asName.empty() ) ) {
            // jeśli podana jest nazwa torów, można szukać eventów skojarzonych przez nazwę
            Track->AssignEvents(
                FindEvent( Current->asName + ":event0" ),
                FindEvent( Current->asName + ":event1" ),
                FindEvent( Current->asName + ":event2" ) );
            Track->AssignallEvents(
                FindEvent( Current->asName + ":eventall0" ),
                FindEvent( Current->asName + ":eventall1" ),
                FindEvent( Current->asName + ":eventall2" ) );
        }
        switch (Track->eType)
        {
        case tt_Table: // obrotnicę też łączymy na starcie z innymi torami
            Model = FindGroundNode(Current->asName, TP_MODEL); // szukamy modelu o tej samej nazwie
            // if (tmp) //mamy model, trzeba zapamiętać wskaźnik do jego animacji
            { // jak coś pójdzie źle, to robimy z tego normalny tor
                // Track->ModelAssign(tmp->Model->GetContainer(NULL)); //wiązanie toru z modelem
                // obrotnicy
#ifdef EU07_USE_OLD_GROUNDCODE
                Track->RaAssign(
                    Current, Model ? Model->Model : NULL, FindEvent( Current->asName + ":done" ),
                    FindEvent( Current->asName + ":joined" ) ); // wiązanie toru z modelem obrotnicy
#else
                Track->RaAssign(
                    Current, Model ? Model->Model : NULL, simulation::Events.FindEvent(Current->asName + ":done"),
                    simulation::Events.FindEvent(Current->asName + ":joined")); // wiązanie toru z modelem obrotnicy
#endif
                // break; //jednak połączę z sąsiednim, jak ma się wysypywać null track
            }
            if (!Model) // jak nie ma modelu
                break; // to pewnie jest wykolejnica, a ta jest domyślnie zamknięta i wykoleja
        case tt_Normal: // tylko proste są podłączane do rozjazdów, stąd dwa rozjazdy się nie
            // połączą ze sobą
            if (Track->CurrentPrev() == NULL) // tylko jeśli jeszcze nie podłączony
            {
                tmp = FindTrack(Track->CurrentSegment()->FastGetPoint_0(), iConnection, Current);
                switch (iConnection)
                {
                case -1: // Ra: pierwsza koncepcja zawijania samochodów i statków
                    // if ((Track->iCategoryFlag&1)==0) //jeśli nie jest torem szynowym
                    // Track->ConnectPrevPrev(Track,0); //łączenie końca odcinka do samego siebie
                    break;
                case 0:
                    Track->ConnectPrevPrev(tmp, 0);
                    break;
                case 1:
                    Track->ConnectPrevNext(tmp, 1);
                    break;
                case 2:
                    Track->ConnectPrevPrev(tmp, 0); // do Point1 pierwszego
                    tmp->SetConnections(0); // zapamiętanie ustawień w Segmencie
                    break;
                case 3:
                    Track->ConnectPrevNext(tmp, 1); // do Point2 pierwszego
                    tmp->SetConnections(0); // zapamiętanie ustawień w Segmencie
                    break;
                case 4:
                    tmp->Switch(1);
                    Track->ConnectPrevPrev(tmp, 2); // do Point1 drugiego
                    tmp->SetConnections(1); // robi też Switch(0)
                    tmp->Switch(0);
                    break;
                case 5:
                    tmp->Switch(1);
                    Track->ConnectPrevNext(tmp, 3); // do Point2 drugiego
                    tmp->SetConnections(1); // robi też Switch(0)
                    tmp->Switch(0);
                    break;
                }
            }
            if (Track->CurrentNext() == NULL) // tylko jeśli jeszcze nie podłączony
            {
                tmp = FindTrack(Track->CurrentSegment()->FastGetPoint_1(), iConnection, Current);
                switch (iConnection)
                {
                case -1: // Ra: pierwsza koncepcja zawijania samochodów i statków
                    // if ((Track->iCategoryFlag&1)==0) //jeśli nie jest torem szynowym
                    // Track->ConnectNextNext(Track,1); //łączenie końca odcinka do samego siebie
                    break;
                case 0:
                    Track->ConnectNextPrev(tmp, 0);
                    break;
                case 1:
                    Track->ConnectNextNext(tmp, 1);
                    break;
                case 2:
                    Track->ConnectNextPrev(tmp, 0);
                    tmp->SetConnections(0); // zapamiętanie ustawień w Segmencie
                    break;
                case 3:
                    Track->ConnectNextNext(tmp, 1);
                    tmp->SetConnections(0); // zapamiętanie ustawień w Segmencie
                    break;
                case 4:
                    tmp->Switch(1);
                    Track->ConnectNextPrev(tmp, 2);
                    tmp->SetConnections(1); // robi też Switch(0)
                    // tmp->Switch(0);
                    break;
                case 5:
                    tmp->Switch(1);
                    Track->ConnectNextNext(tmp, 3);
                    tmp->SetConnections(1); // robi też Switch(0)
                    // tmp->Switch(0);
                    break;
                }
            }
            break;
        case tt_Switch: // dla rozjazdów szukamy eventów sygnalizacji rozprucia
            Track->AssignForcedEvents(
                FindEvent( Current->asName + ":forced+" ),
                FindEvent( Current->asName + ":forced-" ) );
            break;
        }
        std::string const name = Track->IsolatedName(); // pobranie nazwy odcinka izolowanego
        if( !name.empty() ) // jeśli została zwrócona nazwa
            Track->IsolatedEventsAssign(
                FindEvent( name + ":busy" ),
                FindEvent( name + ":free" ) );
        // możliwy portal, jeśli nie podłączony od striny 1
        if (Current->asName.substr(0, 1) == "*")
            if (!Track->CurrentPrev() && Track->CurrentNext())
                Track->iCategoryFlag |= 0x100; // ustawienie flagi portalu
    }
    // WriteLog("Total "+AnsiString(tracks)+", far "+AnsiString(tracksfar));
    TIsolated *p = TIsolated::Root();
    while (p)
    { // jeśli się znajdzie, to podać wskaźnik
        Current = FindGroundNode(p->asName, TP_MEMCELL); // czy jest komóka o odpowiedniej nazwie
        if (Current)
            p->pMemCell = Current->MemCell; // przypisanie powiązanej komórki
        else
        { // utworzenie automatycznej komórki
            Current = new TGroundNode(); // to nie musi mieć nazwy, nazwa w wyszukiwarce wystarczy
            // Current->asName=p->asName; //mazwa identyczna, jak nazwa odcinka izolowanego
            Current->MemCell = new TMemCell(NULL); // nowa komórka
            m_nodemap.Add( TP_MEMCELL, p->asName, Current );
            Current->nNext =
                nRootOfType[TP_MEMCELL]; // to nie powinno tutaj być, bo robi się śmietnik
            nRootOfType[TP_MEMCELL] = Current;
            p->pMemCell = Current->MemCell; // wskaźnik komóki przekazany do odcinka izolowanego
        }
        p = p->Next();
    }
}

void TGround::InitTraction()
{ //łączenie drutów ze sobą oraz z torami i eventami
    TGroundNode *nCurrent, *nTemp;
    TTraction *tmp; // znalezione przęsło
    TTraction *Traction;
    int iConnection;
    std::string name;
    for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext)
    { // podłączenie do zasilacza, żeby można było sumować prąd kilku pojazdów
        // a jednocześnie z jednego miejsca zmieniać napięcie eventem
        // wykonywane najpierw, żeby można było logować podłączenie 2 zasilaczy do jednego drutu
        // izolator zawieszony na przęśle jest ma być osobnym odcinkiem drutu o długości ok. 1m,
        // podłączonym do zasilacza o nazwie "*" (gwiazka); "none" nie będzie odpowiednie
        Traction = nCurrent->hvTraction;
        nTemp = FindGroundNode(Traction->asPowerSupplyName, TP_TRACTIONPOWERSOURCE);
        if (nTemp) // jak zasilacz znaleziony
            Traction->PowerSet(nTemp->psTractionPowerSource); // to podłączyć do przęsła
        else if (Traction->asPowerSupplyName != "*") // gwiazdka dla przęsła z izolatorem
            if (Traction->asPowerSupplyName != "none") // dopuszczamy na razie brak podłączenia?
            { // logowanie błędu i utworzenie zasilacza o domyślnej zawartości
                ErrorLog("Missed TractionPowerSource: " + Traction->asPowerSupplyName);
                nTemp = new TGroundNode();
                nTemp->iType = TP_TRACTIONPOWERSOURCE;
                nTemp->asName = Traction->asPowerSupplyName;
                nTemp->psTractionPowerSource = new TTractionPowerSource(nTemp->asName);
                nTemp->psTractionPowerSource->Init(Traction->NominalVoltage, Traction->MaxCurrent);
                nTemp->nNext = nRootOfType[nTemp->iType]; // ostatni dodany dołączamy na końcu
                // nowego
                nRootOfType[nTemp->iType] = nTemp; // ustawienie nowego na początku listy
            }
    }
    for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext)
    {
        Traction = nCurrent->hvTraction;
        if (!Traction->hvNext[0]) // tylko jeśli jeszcze nie podłączony
        {
            tmp = FindTraction(Traction->pPoint1, iConnection, nCurrent);
            switch (iConnection)
            {
            case 0:
                Traction->Connect(0, tmp, 0);
                break;
            case 1:
                Traction->Connect(0, tmp, 1);
                break;
            }
            if (Traction->hvNext[0]) // jeśli został podłączony
                if (Traction->psSection && tmp->psSection) // tylko przęsło z izolatorem może nie
                    // mieć zasilania, bo ma 2, trzeba
                    // sprawdzać sąsiednie
                    if (Traction->psSection !=
                        tmp->psSection) // połączone odcinki mają różne zasilacze
                    { // to może być albo podłączenie podstacji lub kabiny sekcyjnej do sekcji, albo
                        // błąd
                        if (Traction->psSection->bSection && !tmp->psSection->bSection)
                        { //(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji
                            tmp->PowerSet(Traction->psSection); // zastąpienie wskazaniem sekcji
                        }
                        else if (!Traction->psSection->bSection && tmp->psSection->bSection)
                        { //(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji
                            Traction->PowerSet(tmp->psSection); // zastąpienie wskazaniem sekcji
                        }
                        else // jeśli obie to sekcje albo obie podstacje, to będzie błąd
                            ErrorLog("Bad power: at " +
                                     to_string(Traction->pPoint1.x, 2, 6) + " " +
                                     to_string(Traction->pPoint1.y, 2, 6) + " " +
                                     to_string(Traction->pPoint1.z, 2, 6));
                    }
        }
        if (!Traction->hvNext[1]) // tylko jeśli jeszcze nie podłączony
        {
            tmp = FindTraction(Traction->pPoint2, iConnection, nCurrent);
            switch (iConnection)
            {
            case 0:
                Traction->Connect(1, tmp, 0);
                break;
            case 1:
                Traction->Connect(1, tmp, 1);
                break;
            }
            if (Traction->hvNext[1]) // jeśli został podłączony
                if (Traction->psSection && tmp->psSection) // tylko przęsło z izolatorem może nie
                    // mieć zasilania, bo ma 2, trzeba
                    // sprawdzać sąsiednie
                    if (Traction->psSection != tmp->psSection)
                    { // to może być albo podłączenie podstacji lub kabiny sekcyjnej do sekcji, albo
                        // błąd
                        if (Traction->psSection->bSection && !tmp->psSection->bSection)
                        { //(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji
                            tmp->PowerSet(Traction->psSection); // zastąpienie wskazaniem sekcji
                        }
                        else if (!Traction->psSection->bSection && tmp->psSection->bSection)
                        { //(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji
                            Traction->PowerSet(tmp->psSection); // zastąpienie wskazaniem sekcji
                        }
                        else // jeśli obie to sekcje albo obie podstacje, to będzie błąd
                            ErrorLog("Bad power: at " +
                                     to_string(Traction->pPoint2.x, 2, 6) + " " +
                                     to_string(Traction->pPoint2.y, 2, 6) + " " +
                                     to_string(Traction->pPoint2.z, 2, 6));
                    }
        }
    }
    iConnection = 0; // teraz będzie licznikiem końców
    for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext)
    { // operacje mające na celu wykrywanie bieżni wspólnych i łączenie przęseł naprążania
        if (nCurrent->hvTraction->WhereIs()) // oznakowanie przedostatnich przęseł
        { // poszukiwanie bieżni wspólnej dla przedostatnich przęseł, również w celu połączenia
            // zasilania
            // to się nie sprawdza, bo połączyć się mogą dwa niezasilane odcinki jako najbliższe
            // sobie
            // nCurrent->hvTraction->hvParallel=TractionNearestFind(nCurrent->pCenter,0,nCurrent);
            // //szukanie najbliższego przęsła
            // trzeba by zliczać końce, a potem wpisać je do tablicy, aby sukcesywnie podłączać do
            // zasilaczy
            nCurrent->hvTraction->iTries = 5; // oznaczanie końcowych
            ++iConnection;
        }
        if (nCurrent->hvTraction->fResistance[0] == 0.0)
        {
            nCurrent->hvTraction
                ->ResistanceCalc(); // obliczanie przęseł w segmencie z bezpośrednim zasilaniem
            // ErrorLog("Section "+nCurrent->hvTraction->asPowerSupplyName+" connected"); //jako
            // niby błąd będzie bardziej widoczne
            nCurrent->hvTraction->iTries = 0; // nie potrzeba mu szukać zasilania
        }
        // if (!Traction->hvParallel) //jeszcze utworzyć pętle z bieżni wspólnych
    }
    int zg = 0; // zgodność kierunku przęseł, tymczasowo iterator do tabeli końców
    TGroundNode **nEnds = new TGroundNode *[iConnection]; // końców jest ok. 10 razy mniej niż
    // wszystkich przęseł (Quark: 216)
    for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext)
    { //łączenie bieżni wspólnych, w tym oznaczanie niepodanych jawnie
        Traction = nCurrent->hvTraction;
        if (!Traction->asParallel.empty()) // będzie wskaźnik na inne przęsło
            if ((Traction->asParallel == "none") ||
                (Traction->asParallel == "*")) // jeśli nieokreślone
                Traction->iLast =
                    2; // jakby przedostatni - niech po prostu szuka (iLast już przeliczone)
            else if (!Traction->hvParallel) // jeśli jeszcze nie został włączony w kółko
            {
                nTemp = FindGroundNode(Traction->asParallel, TP_TRACTION);
                if (nTemp)
                { // o ile zostanie znalezione przęsło o takiej nazwie
                    if (!nTemp->hvTraction
                             ->hvParallel) // jeśli tamten jeszcze nie ma wskaźnika bieżni wspólnej
                        Traction->hvParallel =
                            nTemp->hvTraction; // wpisać siebie i dalej dać mu wskaźnik zwrotny
                    else // a jak ma, to albo dołączyć się do kółeczka
                        Traction->hvParallel =
                            nTemp->hvTraction->hvParallel; // przjąć dotychczasowy wskaźnik od niego
                    nTemp->hvTraction->hvParallel =
                        Traction; // i na koniec ustawienie wskaźnika zwrotnego
                }
                if (!Traction->hvParallel)
                    ErrorLog("Missed overhead: " + Traction->asParallel); // logowanie braku
            }
        if (Traction->iTries == 5) // jeśli zaznaczony do podłączenia
            // if (!nCurrent->hvTraction->psPower[0]||!nCurrent->hvTraction->psPower[1])
            if (zg < iConnection) // zabezpieczenie
                nEnds[zg++] = nCurrent; // wypełnianie tabeli końców w celu szukania im połączeń
    }
    while (zg < iConnection)
        nEnds[zg++] = NULL; // zapełnienie do końca tablicy, jeśli by jakieś końce wypadły
    zg = 1; // nieefektywny przebieg kończy łączenie
    while (zg)
    { // ustalenie zastępczej rezystancji dla każdego przęsła
        zg = 0; // flaga podłączonych przęseł końcowych: -1=puste wskaźniki, 0=coś zostało,
        // 1=wykonano łączenie
        for (int i = 0; i < iConnection; ++i)
            if (nEnds[i]) // załatwione będziemy zerować
            { // każdy przebieg to próba podłączenia końca segmentu naprężania do innego zasilanego
                // przęsła
                if (nEnds[i]->hvTraction->hvNext[0])
                { // jeśli końcowy ma ciąg dalszy od strony 0 (Point1), szukamy odcinka najbliższego
                    // do Point2
                    if (TractionNearestFind(nEnds[i]->hvTraction->pPoint2, 0,
                                            nEnds[i])) // poszukiwanie przęsła
                    {
                        nEnds[i] = NULL;
                        zg = 1; // jak coś zostało podłączone, to może zasilanie gdzieś dodatkowo
                        // dotrze
                    }
                }
                else if (nEnds[i]->hvTraction->hvNext[1])
                { // jeśli końcowy ma ciąg dalszy od strony 1 (Point2), szukamy odcinka najbliższego
                    // do Point1
                    if (TractionNearestFind(nEnds[i]->hvTraction->pPoint1, 1,
                                            nEnds[i])) // poszukiwanie przęsła
                    {
                        nEnds[i] = NULL;
                        zg = 1; // jak coś zostało podłączone, to może zasilanie gdzieś dodatkowo
                        // dotrze
                    }
                }
                else
                { // gdy koniec jest samotny, to na razie nie zostanie podłączony (nie powinno
                    // takich być)
                    nEnds[i] = NULL;
                }
            }
    }
    delete[] nEnds; // nie potrzebne już
};

void TGround::TrackJoin(TGroundNode *Current)
{ // wyszukiwanie sąsiednich torów do podłączenia (wydzielone na użytek obrotnicy)
    TTrack *Track = Current->pTrack;
    TTrack *tmp;
    int iConnection;
    if (!Track->CurrentPrev())
    {
        tmp = FindTrack(Track->CurrentSegment()->FastGetPoint_0(), iConnection, Current); // Current do pominięcia
        switch (iConnection)
        {
        case 0:
            Track->ConnectPrevPrev(tmp, 0);
            break;
        case 1:
            Track->ConnectPrevNext(tmp, 1);
            break;
        }
    }
    if (!Track->CurrentNext())
    {
        tmp = FindTrack(Track->CurrentSegment()->FastGetPoint_1(), iConnection, Current);
        switch (iConnection)
        {
        case 0:
            Track->ConnectNextPrev(tmp, 0);
            break;
        case 1:
            Track->ConnectNextNext(tmp, 1);
            break;
        }
    }
}

// McZapkie-070602: wyzwalacze zdarzen
bool TGround::InitLaunchers()
{
    TGroundNode *Current, *tmp;
    TEventLauncher *EventLauncher;

    for (Current = nRootOfType[TP_EVLAUNCH]; Current; Current = Current->nNext)
    {
        EventLauncher = Current->EvLaunch;
        if (EventLauncher->iCheckMask != 0)
            if (EventLauncher->asMemCellName != "none")
            { // jeśli jest powiązana komórka pamięci
                tmp = FindGroundNode(EventLauncher->asMemCellName, TP_MEMCELL);
                if (tmp)
                    EventLauncher->MemCell = tmp->MemCell; // jeśli znaleziona, dopisać
                else
                    MessageBox(0, "Cannot find Memory Cell for Event Launcher", "Error", MB_OK);
            }
            else
                EventLauncher->MemCell = nullptr;
        EventLauncher->Event1 = ( EventLauncher->asEvent1Name != "none") ?
                                    FindEvent(EventLauncher->asEvent1Name) :
                                    nullptr;
        EventLauncher->Event2 = ( EventLauncher->asEvent2Name != "none") ?
                                    FindEvent(EventLauncher->asEvent2Name) :
                                    nullptr;
    }
    return true;
}

TTrack * TGround::FindTrack(vector3 Point, int &iConnection, TGroundNode *Exclude)
{ // wyszukiwanie innego toru kończącego się w (Point)
    TTrack *tmp;
    iConnection = -1;
    TSubRect *sr;
    // najpierw szukamy w okolicznych segmentach
    int c = GetColFromX(Point.x);
    int r = GetRowFromZ(Point.z);
    if ((sr = FastGetSubRect(c, r)) != NULL) // 75% torów jest w tym samym sektorze
        if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL)
            return tmp;
    int i, x, y;
    for (i = 1; i < 9;
         ++i) // sektory w kolejności odległości, 4 jest tu wystarczające, 9 na wszelki wypadek
    { // niemal wszystkie podłączone tory znajdują się w sąsiednich 8 sektorach
        x = SectorOrder[i].x;
        y = SectorOrder[i].y;
        if ((sr = FastGetSubRect(c + y, r + x)) != NULL)
            if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL)
                return tmp;
        if (x)
            if ((sr = FastGetSubRect(c + y, r - x)) != NULL)
                if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL)
                    return tmp;
        if (y)
            if ((sr = FastGetSubRect(c - y, r + x)) != NULL)
                if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL)
                    return tmp;
        if ((sr = FastGetSubRect(c - y, r - x)) != NULL)
            if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL)
                return tmp;
    }
    return NULL;
}

TTraction * TGround::FindTraction(glm::dvec3 const &Point, int &iConnection, TGroundNode *Exclude)
{ // wyszukiwanie innego przęsła kończącego się w (Point)
    TTraction *tmp;
    iConnection = -1;
    TSubRect *sr;
    // najpierw szukamy w okolicznych segmentach
    int c = GetColFromX(Point.x);
    int r = GetRowFromZ(Point.z);
    if ((sr = FastGetSubRect(c, r)) != NULL) // większość będzie w tym samym sektorze
        if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL)
            return tmp;
    int i, x, y;
    for (i = 1; i < 9;
         ++i) // sektory w kolejności odległości, 4 jest tu wystarczające, 9 na wszelki wypadek
    { // wszystkie przęsła powinny zostać znajdować się w sąsiednich 8 sektorach
        x = SectorOrder[i].x;
        y = SectorOrder[i].y;
        if ((sr = FastGetSubRect(c + y, r + x)) != NULL)
            if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL)
                return tmp;
        if (x & y)
        {
            if ((sr = FastGetSubRect(c + y, r - x)) != NULL)
                if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL)
                    return tmp;
            if ((sr = FastGetSubRect(c - y, r + x)) != NULL)
                if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL)
                    return tmp;
        }
        if ((sr = FastGetSubRect(c - y, r - x)) != NULL)
            if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL)
                return tmp;
    }
    return NULL;
};

TTraction * TGround::TractionNearestFind(glm::dvec3 &p, int dir, TGroundNode *n)
{ // wyszukanie najbliższego do (p) przęsła o tej samej nazwie sekcji (ale innego niż podłączone)
    // oraz zasilanego z kierunku (dir)
    TGroundNode *nCurrent, *nBest = NULL;
    int i, j, k, zg;
    double d, dist = 200.0 * 200.0; //[m] odległość graniczna
    // najpierw szukamy w okolicznych segmentach
    int c = GetColFromX(n->pCenter.x);
    int r = GetRowFromZ(n->pCenter.z);
    TSubRect *sr;
    for (i = -1; i <= 1; ++i) // przeglądamy 9 najbliższych sektorów
        for (j = -1; j <= 1; ++j) //
            if ((sr = FastGetSubRect(c + i, r + j)) != NULL) // o ile w ogóle sektor jest
                for (nCurrent = sr->nRenderWires; nCurrent; nCurrent = nCurrent->nNext3)
                    if (nCurrent->iType == TP_TRACTION)
                        if (nCurrent->hvTraction->psSection == n->hvTraction->psSection) // jeśli ta sama sekcja
                            if (nCurrent != n) // ale nie jest tym samym
                                if (nCurrent->hvTraction != n->hvTraction->hvNext[0]) // ale nie jest bezpośrednio podłączonym
                                    if (nCurrent->hvTraction != n->hvTraction->hvNext[1])
                                        if (nCurrent->hvTraction->psPower
                                                [k = (glm::dot(
                                                          n->hvTraction->vParametric,
                                                          nCurrent->hvTraction->vParametric) >= 0 ?
                                                          dir ^ 1 :
                                                          dir)]) // ma zasilanie z odpowiedniej strony
                                            if (nCurrent->hvTraction->fResistance[k] >= 0.0) { // żeby się nie propagowały jakieś ujemne
                                                // znaleziony kandydat do połączenia
                                                d = glm::length2( p - glm::dvec3{ nCurrent->pCenter } ); // kwadrat odległości środków
                                                if (dist > d) {
                                                    // zapamiętanie nowego najbliższego
                                                    dist = d; // nowy rekord odległości
                                                    nBest = nCurrent;
                                                    zg = k; // z którego końca brać wskaźnik zasilacza
                                                }
                                            }
    if (nBest) {
        // jak znalezione przęsło z zasilaniem, to podłączenie "równoległe"
        n->hvTraction->ResistanceCalc(dir, nBest->hvTraction->fResistance[zg], nBest->hvTraction->psPower[zg]);
    }
    return (nBest ? nBest->hvTraction : nullptr);
};

bool TGround::AddToQuery(TEvent *Event, TDynamicObject *Node)
{
    if( ( false == Event->m_ignored ) && ( true == Event->bEnabled ) ) {
        // jeśli może być dodany do kolejki (nie używany w skanowaniu)
        if( !Event->iQueued ) // jeśli nie dodany jeszcze do kolejki
        { // kolejka eventów jest posortowana względem (fStartTime)
            Event->Activator = Node;
            if( ( Event->Type == tp_AddValues )
             && ( Event->fDelay == 0.0 ) ) {
                // eventy AddValues trzeba wykonywać natychmiastowo, inaczej kolejka może zgubić jakieś dodawanie
                // Ra: kopiowanie wykonania tu jest bez sensu, lepiej by było wydzielić funkcję
                // wykonującą eventy i ją wywołać
                if( EventConditon( Event ) ) { // teraz mogą być warunki do tych eventów
                    Event->Params[ 5 ].asMemCell->UpdateValues(
                        Event->Params[ 0 ].asText, Event->Params[ 1 ].asdouble,
                        Event->Params[ 2 ].asdouble, Event->iFlags );
                    if( Event->Params[ 6 ].asTrack ) { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla
                        // wszystkich 'dynamic' na danym torze
                        for( auto dynamic : Event->Params[ 6 ].asTrack->Dynamics ) {
                            Event->Params[ 5 ].asMemCell->PutCommand(
                                dynamic->Mechanik,
                                &Event->Params[ 4 ].nGroundNode->pCenter );
                        }
                        //if (DebugModeFlag)
                        WriteLog(
                            "EVENT EXECUTED" + ( Node ? ( " by " + Node->asName ) : "" ) + ": AddValues & Track command ( "
                            + std::string( Event->Params[ 0 ].asText ) + " "
                            + std::to_string( Event->Params[ 1 ].asdouble ) + " "
                            + std::to_string( Event->Params[ 2 ].asdouble ) + " )" );
                    }
                    //else if (DebugModeFlag)
                    WriteLog(
                        "EVENT EXECUTED" + ( Node ? ( " by " + Node->asName ) : "" ) + ": AddValues ( "
                        + std::string( Event->Params[ 0 ].asText ) + " "
                        + std::to_string( Event->Params[ 1 ].asdouble ) + " "
                        + std::to_string( Event->Params[ 2 ].asdouble ) + " )" );
                }
                // jeśli jest kolejny o takiej samej nazwie, to idzie do kolejki (and if there's no joint event it'll be set to null and processing will end here)
                do {
                    Event = Event->evJoined;
                    // NOTE: we could've received a new event from joint event above, so we need to check conditions just in case and discard the bad events
                    // TODO: refactor this arrangement, it's hardly optimal
                } while( ( Event != nullptr )
                    && ( ( false == Event->bEnabled )
                      || ( Event->iQueued > 0 ) ) );
            }
            if( Event != nullptr ) {
                // standardowe dodanie do kolejki
                ++Event->iQueued; // zabezpieczenie przed podwójnym dodaniem do kolejki
                WriteLog( "EVENT ADDED TO QUEUE" + ( Node ? ( " by " + Node->asName ) : "" ) + ": " + Event->asName );
                Event->fStartTime = std::abs( Event->fDelay ) + Timer::GetTime(); // czas od uruchomienia scenerii
                if( Event->fRandomDelay > 0.0 ) {
                    // doliczenie losowego czasu opóźnienia
                    Event->fStartTime += Event->fRandomDelay * Random( 10000 ) * 0.0001;
                }
                if( QueryRootEvent != nullptr ) {
                    TEvent::AddToQuery( Event, QueryRootEvent );
                }
                else {
                    QueryRootEvent = Event;
                    QueryRootEvent->evNext = nullptr;
                }
            }
        }
    }
    return true;
}

// sprawdzenie spelnienia warunków dla eventu
bool TGround::EventConditon(TEvent *e){

    if (e->iFlags <= update_only)
        return true; // bezwarunkowo

    if (e->iFlags & conditional_trackoccupied)
        return (!e->Params[9].asTrack->IsEmpty());
    else if (e->iFlags & conditional_trackfree)
        return (e->Params[9].asTrack->IsEmpty());
    else if (e->iFlags & conditional_propability)
    {
        double rprobability = Random();
        WriteLog("Random integer: " + std::to_string(rprobability) + " / " + std::to_string(e->Params[10].asdouble));
        return (e->Params[10].asdouble > rprobability);
    }
    else if( e->iFlags & conditional_memcompare ) {
        // porównanie wartości
        if( nullptr == e->Params[9].asMemCell ) {

            ErrorLog( "Event " + e->asName + " trying conditional_memcompare with nonexistent memcell" );
            return true; // though this is technically error, we report success to maintain backward compatibility
        }
        auto const comparisonresult =
            tmpEvent->Params[ 9 ].asMemCell->Compare(
                ( e->Params[ 10 ].asText != nullptr ?
                    e->Params[ 10 ].asText :
                    "" ),
                e->Params[ 11 ].asdouble,
                e->Params[ 12 ].asdouble,
                e->iFlags );

        std::string comparisonlog = "Type: MemCompare - ";

        comparisonlog +=
               "[" + e->Params[ 9 ].asMemCell->Text() + "]"
            + " [" + to_string( e->Params[ 9 ].asMemCell->Value1(), 2 ) + "]"
            + " [" + to_string( tmpEvent->Params[ 9 ].asMemCell->Value2(), 2 ) + "]";

        comparisonlog += (
            true == comparisonresult ?
                " == " :
                " != " );

        comparisonlog += (
            TestFlag( e->iFlags, conditional_memstring ) ?
                "[" + std::string( tmpEvent->Params[ 10 ].asText ) + "]" :
                "[*]" );
        comparisonlog += (
            TestFlag( tmpEvent->iFlags, conditional_memval1 ) ?
                " [" + to_string( tmpEvent->Params[ 11 ].asdouble, 2 ) + "]" :
                " [*]" );
        comparisonlog += (
            TestFlag( tmpEvent->iFlags, conditional_memval2 ) ?
                " [" + to_string( tmpEvent->Params[ 12 ].asdouble, 2 ) + "]" :
                " [*]" );

        WriteLog( comparisonlog );
        return comparisonresult;
    }
    // unrecognized request
    return false;
};

bool TGround::CheckQuery()
{ // sprawdzenie kolejki eventów oraz wykonanie tych, którym czas minął
    TLocation loc;
    int i;
    while( ( QueryRootEvent != nullptr )
        && ( QueryRootEvent->fStartTime < Timer::GetTime() ) )
    { // eventy są posortowana wg czasu wykonania
        tmpEvent = QueryRootEvent; // wyjęcie eventu z kolejki
        if (QueryRootEvent->evJoined) // jeśli jest kolejny o takiej samej nazwie
        { // to teraz on będzie następny do wykonania
            QueryRootEvent = QueryRootEvent->evJoined; // następny będzie ten doczepiony
            QueryRootEvent->evNext = tmpEvent->evNext; // pamiętając o następnym z kolejki
            QueryRootEvent->fStartTime = tmpEvent->fStartTime; // czas musi być ten sam, bo nie jest aktualizowany
            QueryRootEvent->Activator = tmpEvent->Activator; // pojazd aktywujący
            QueryRootEvent->iQueued = 1;
            // w sumie można by go dodać normalnie do kolejki, ale trzeba te połączone posortować wg czasu wykonania
        }
        else // a jak nazwa jest unikalna, to kolejka idzie dalej
            QueryRootEvent = QueryRootEvent->evNext; // NULL w skrajnym przypadku
        if( ( false == tmpEvent->m_ignored ) && ( true == tmpEvent->bEnabled ) ) {
            // w zasadzie te wyłączone są skanowane i nie powinny się nigdy w kolejce znaleźć
            --tmpEvent->iQueued; // teraz moze być ponownie dodany do kolejki
            WriteLog( "EVENT LAUNCHED" + ( tmpEvent->Activator ? ( " by " + tmpEvent->Activator->asName ) : "" ) + ": " + tmpEvent->asName );
            switch (tmpEvent->Type)
            {
            case tp_CopyValues: // skopiowanie wartości z innej komórki
                tmpEvent->Params[5].asMemCell->UpdateValues(
                    tmpEvent->Params[9].asMemCell->Text(),
                    tmpEvent->Params[9].asMemCell->Value1(),
                    tmpEvent->Params[9].asMemCell->Value2(),
                    tmpEvent->iFlags // flagi określają, co ma być skopiowane
                    );
            // break; //żeby się wysłało do torów i nie było potrzeby na AddValues * 0 0
            case tp_AddValues: // różni się jedną flagą od UpdateValues
            case tp_UpdateValues:
                if (EventConditon(tmpEvent))
                { // teraz mogą być warunki do tych eventów
                    if (tmpEvent->Type != tp_CopyValues) // dla CopyValues zrobiło się wcześniej
                        tmpEvent->Params[5].asMemCell->UpdateValues(
                            tmpEvent->Params[0].asText,
                            tmpEvent->Params[1].asdouble,
                            tmpEvent->Params[2].asdouble,
                            tmpEvent->iFlags);
                    if (tmpEvent->Params[6].asTrack)
                    { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla
                        // wszystkich 'dynamic' na danym torze
                        for( auto dynamic : tmpEvent->Params[ 6 ].asTrack->Dynamics ) {
                            tmpEvent->Params[ 5 ].asMemCell->PutCommand(
                                dynamic->Mechanik,
                                &tmpEvent->Params[ 4 ].nGroundNode->pCenter );
                        }
                        //if (DebugModeFlag)
                        WriteLog("Type: UpdateValues & Track command - [" +
                            tmpEvent->Params[5].asMemCell->Text() + "] [" +
                            to_string( tmpEvent->Params[ 5 ].asMemCell->Value1(), 2 ) + "] [" +
                            to_string( tmpEvent->Params[ 5 ].asMemCell->Value2(), 2 ) + "]" );
                    }
                    else //if (DebugModeFlag) 
                        WriteLog("Type: UpdateValues - [" +
                            tmpEvent->Params[5].asMemCell->Text() + "] [" +
                            to_string( tmpEvent->Params[ 5 ].asMemCell->Value1(), 2 ) + "] [" +
                            to_string( tmpEvent->Params[ 5 ].asMemCell->Value2(), 2 ) + "]" );
                }
                break;
            case tp_GetValues:
                if (tmpEvent->Activator)
                {
                    // loc.X= -tmpEvent->Params[8].nGroundNode->pCenter.x;
                    // loc.Y=  tmpEvent->Params[8].nGroundNode->pCenter.z;
                    // loc.Z=  tmpEvent->Params[8].nGroundNode->pCenter.y;
                    if (Global::iMultiplayer) // potwierdzenie wykonania dla serwera (odczyt
                        // semafora już tak nie działa)
                        multiplayer::WyslijEvent(tmpEvent->asName, tmpEvent->Activator->name());
                    // tmpEvent->Params[9].asMemCell->PutCommand(tmpEvent->Activator->Mechanik,loc);
                    tmpEvent->Params[9].asMemCell->PutCommand(
                        tmpEvent->Activator->Mechanik, &tmpEvent->Params[8].nGroundNode->pCenter);
                }
                WriteLog("Type: GetValues");
                break;
            case tp_PutValues:
                if (tmpEvent->Activator) {
                    // zamiana, bo fizyka ma inaczej niż sceneria
                    loc.X = -tmpEvent->Params[3].asdouble;
                    loc.Y =  tmpEvent->Params[5].asdouble;
                    loc.Z =  tmpEvent->Params[4].asdouble;
                    if (tmpEvent->Activator->Mechanik) // przekazanie rozkazu do AI
                        tmpEvent->Activator->Mechanik->PutCommand(
                            tmpEvent->Params[0].asText, tmpEvent->Params[1].asdouble,
                            tmpEvent->Params[2].asdouble, loc);
                    else {
                        // przekazanie do pojazdu
                        tmpEvent->Activator->MoverParameters->PutCommand(
                            tmpEvent->Params[0].asText, tmpEvent->Params[1].asdouble,
                            tmpEvent->Params[2].asdouble, loc);
                    }
                    WriteLog("Type: PutValues - [" +
                        std::string(tmpEvent->Params[0].asText) + "] [" +
                        to_string( tmpEvent->Params[ 1 ].asdouble, 2 ) + "] [" +
                        to_string( tmpEvent->Params[ 2 ].asdouble, 2 ) + "]" );
                }
                break;
            case tp_Lights:
                if (tmpEvent->Params[9].asModel)
                    for (i = 0; i < iMaxNumLights; i++)
                        if (tmpEvent->Params[i].asdouble >= 0) //-1 zostawia bez zmiany
                            tmpEvent->Params[9].asModel->LightSet(
                                i, tmpEvent->Params[i].asdouble); // teraz też ułamek
                break;
            case tp_Visible:
                if (tmpEvent->Params[9].nGroundNode)
                    tmpEvent->Params[9].nGroundNode->bVisible = (tmpEvent->Params[i].asInt > 0);
                break;
            case tp_Velocity:
                Error("Not implemented yet :(");
                break;
            case tp_Exit:
                MessageBox(0, tmpEvent->asNodeName.c_str(), " THE END ", MB_OK);
                Global::iTextMode = -1; // wyłączenie takie samo jak sekwencja F10 -> Y
                return false;
            case tp_Sound:
                switch (tmpEvent->Params[0].asInt)
                { // trzy możliwe przypadki:
                case 0:
                    tmpEvent->Params[9].tsTextSound->Stop();
                    break;
                case 1:
                    tmpEvent->Params[9].tsTextSound->Play(
                        1, 0, true, tmpEvent->Params[9].tsTextSound->vSoundPosition);
                    break;
                case -1:
                    tmpEvent->Params[9].tsTextSound->Play(
                        1, DSBPLAY_LOOPING, true, tmpEvent->Params[9].tsTextSound->vSoundPosition);
                    break;
                }
                break;
            case tp_Disable:
                Error("Not implemented yet :(");
                break;
            case tp_Animation: // Marcin: dorobic translacje - Ra: dorobiłem ;-)
                if (tmpEvent->Params[0].asInt == 1)
                    tmpEvent->Params[9].asAnimContainer->SetRotateAnim(
                        vector3(tmpEvent->Params[1].asdouble, tmpEvent->Params[2].asdouble,
                                tmpEvent->Params[3].asdouble),
                        tmpEvent->Params[4].asdouble);
                else if (tmpEvent->Params[0].asInt == 2)
                    tmpEvent->Params[9].asAnimContainer->SetTranslateAnim(
                        vector3(tmpEvent->Params[1].asdouble, tmpEvent->Params[2].asdouble,
                                tmpEvent->Params[3].asdouble),
                        tmpEvent->Params[4].asdouble);
                else if (tmpEvent->Params[0].asInt == 4)
                    tmpEvent->Params[9].asModel->AnimationVND(
                        tmpEvent->Params[8].asPointer,
                        tmpEvent->Params[1].asdouble, // tu mogą być dodatkowe parametry, np. od-do
                        tmpEvent->Params[2].asdouble, tmpEvent->Params[3].asdouble,
                        tmpEvent->Params[4].asdouble);
                break;
            case tp_Switch:
                if (tmpEvent->Params[9].asTrack)
                    tmpEvent->Params[9].asTrack->Switch(tmpEvent->Params[0].asInt,
                                                        tmpEvent->Params[1].asdouble,
                                                        tmpEvent->Params[2].asdouble);
                if (Global::iMultiplayer) // dajemy znać do serwera o przełożeniu
                    multiplayer::WyslijEvent(tmpEvent->asName, ""); // wysłanie nazwy eventu przełączajacego
                // Ra: bardziej by się przydała nazwa toru, ale nie ma do niej stąd dostępu
                break;
            case tp_TrackVel:
                if (tmpEvent->Params[9].asTrack)
                { // prędkość na zwrotnicy może być ograniczona z góry we wpisie, większej się nie
                    // ustawi eventem
                    WriteLog("Type: TrackVel");
                    tmpEvent->Params[9].asTrack->VelocitySet(tmpEvent->Params[0].asdouble);
                    if (DebugModeFlag) // wyświetlana jest ta faktycznie ustawiona
                        WriteLog(" - velocity: ", tmpEvent->Params[9].asTrack->VelocityGet());
                }
                break;
            case tp_DynVel:
                Error("Event \"DynVel\" is obsolete");
                break;
            case tp_Multiple:
            {
                bCondition = EventConditon(tmpEvent);
                if (bCondition || (tmpEvent->iFlags &
                                   conditional_anyelse)) // warunek spelniony albo było użyte else
                {
                    WriteLog("Type: Multi-event");
                    for (i = 0; i < 8; ++i) {
                        // dodawane do kolejki w kolejności zapisania
                        if( tmpEvent->Params[ i ].asEvent ) {
                            if( bCondition != ( ( ( tmpEvent->iFlags & ( conditional_else << i ) ) != 0 ) ) ) {
                                if( tmpEvent->Params[ i ].asEvent != tmpEvent )
                                    AddToQuery( tmpEvent->Params[ i ].asEvent, tmpEvent->Activator ); // normalnie dodać
                                else {
                                    // jeśli ma być rekurencja to musi mieć sensowny okres powtarzania
                                    if( tmpEvent->fDelay >= 5.0 ) {
                                        AddToQuery( tmpEvent, tmpEvent->Activator );
                                    }
                                }
                            }
                        }
                    }
                    if (Global::iMultiplayer) // dajemy znać do serwera o wykonaniu
                        if ((tmpEvent->iFlags & conditional_anyelse) ==
                            0) // jednoznaczne tylko, gdy nie było else
                        {
                            if (tmpEvent->Activator)
                                multiplayer::WyslijEvent(tmpEvent->asName, tmpEvent->Activator->name());
                            else
                                multiplayer::WyslijEvent(tmpEvent->asName, "");
                        }
                }
            }
            break;
            case tp_WhoIs: {
                // pobranie nazwy pociągu do komórki pamięci
                if (tmpEvent->iFlags & update_load) {
                    // jeśli pytanie o ładunek
                    if( tmpEvent->iFlags & update_memadd ) {
                        // jeśli typ pojazdu
                        // TODO: define and recognize individual request types
                        auto const owner = (
                            ( ( tmpEvent->Activator->Mechanik != nullptr ) && ( tmpEvent->Activator->Mechanik->Primary() ) ) ?
                                tmpEvent->Activator->Mechanik :
                                tmpEvent->Activator->ctOwner );
                        auto const consistbrakelevel = (
                            owner != nullptr ?
                                owner->fReady :
                                -1.0 );
                        auto const collisiondistance = (
                            owner != nullptr ?
                                owner->TrackBlock() :
                                -1.0 );

                        tmpEvent->Params[ 9 ].asMemCell->UpdateValues(
                            tmpEvent->Activator->MoverParameters->TypeName, // typ pojazdu
                            consistbrakelevel,
                            collisiondistance,
                            tmpEvent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) );

                        WriteLog(
                              "Type: WhoIs (" + to_string( tmpEvent->iFlags ) + ") - "
                            + "[name: " + tmpEvent->Activator->MoverParameters->TypeName + "], "
                            + "[consist brake level: " + to_string( consistbrakelevel, 2 ) + "], "
                            + "[obstacle distance: " + to_string( collisiondistance, 2 ) + " m]" );
                    }
                    else {
                        // jeśli parametry ładunku
                        tmpEvent->Params[ 9 ].asMemCell->UpdateValues(
                            tmpEvent->Activator->MoverParameters->LoadType, // nazwa ładunku
                            tmpEvent->Activator->MoverParameters->Load, // aktualna ilość
                            tmpEvent->Activator->MoverParameters->MaxLoad, // maksymalna ilość
                            tmpEvent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) );

                        WriteLog(
                              "Type: WhoIs (" + to_string( tmpEvent->iFlags ) + ") - "
                            + "[load type: " + tmpEvent->Activator->MoverParameters->LoadType + "], "
                            + "[current load: " + to_string( tmpEvent->Activator->MoverParameters->Load, 2 ) + "], "
                            + "[max load: " + to_string( tmpEvent->Activator->MoverParameters->MaxLoad, 2 ) + "]" );
                    }
                }
                else if (tmpEvent->iFlags & update_memadd)
                { // jeśli miejsce docelowe pojazdu
                    tmpEvent->Params[ 9 ].asMemCell->UpdateValues(
                        tmpEvent->Activator->asDestination, // adres docelowy
                        tmpEvent->Activator->DirectionGet(), // kierunek pojazdu względem czoła składu (1=zgodny,-1=przeciwny)
                        tmpEvent->Activator->MoverParameters->Power, // moc pojazdu silnikowego: 0 dla wagonu
                        tmpEvent->iFlags & (update_memstring | update_memval1 | update_memval2));

                        WriteLog(
                              "Type: WhoIs (" + to_string( tmpEvent->iFlags ) + ") - "
                            + "[destination: " + tmpEvent->Activator->asDestination + "], "
                            + "[direction: " + to_string( tmpEvent->Activator->DirectionGet() ) + "], "
                            + "[engine power: " + to_string( tmpEvent->Activator->MoverParameters->Power, 2 ) + "]" );
                }
                else if (tmpEvent->Activator->Mechanik)
                    if (tmpEvent->Activator->Mechanik->Primary())
                    { // tylko jeśli ktoś tam siedzi - nie powinno dotyczyć pasażera!
                        tmpEvent->Params[ 9 ].asMemCell->UpdateValues(
							tmpEvent->Activator->Mechanik->TrainName(),
                            tmpEvent->Activator->Mechanik->StationCount() - tmpEvent->Activator->Mechanik->StationIndex(), // ile przystanków do końca
                            tmpEvent->Activator->Mechanik->IsStop() ? 1 :
                                                                      0, // 1, gdy ma tu zatrzymanie
                            tmpEvent->iFlags);
                        WriteLog("Train detected: " + tmpEvent->Activator->Mechanik->TrainName());
                    }
                break;
            }
            case tp_LogValues: // zapisanie zawartości komórki pamięci do logu
                if (tmpEvent->Params[9].asMemCell) // jeśli była podana nazwa komórki
                    WriteLog("Memcell \"" + tmpEvent->asNodeName + "\": " +
                             tmpEvent->Params[9].asMemCell->Text() + " " +
                             std::to_string(tmpEvent->Params[9].asMemCell->Value1()) + " " +
                             std::to_string(tmpEvent->Params[9].asMemCell->Value2()));
                else // lista wszystkich
                    for (TGroundNode *Current = nRootOfType[TP_MEMCELL]; Current;
                         Current = Current->nNext)
                        WriteLog("Memcell \"" + Current->asName + "\": " +
                                 Current->MemCell->Text() + " " + std::to_string(Current->MemCell->Value1()) + " " +
                                 std::to_string(Current->MemCell->Value2()));
                break;
            case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource)
                if (tmpEvent->Params[9].psPower)
                { // na razie takie chamskie ustawienie napięcia zasilania
                    WriteLog("Type: Voltage");
                    tmpEvent->Params[9].psPower->VoltageSet(tmpEvent->Params[0].asdouble);
                }
            case tp_Friction: // zmiana tarcia na scenerii
            { // na razie takie chamskie ustawienie napięcia zasilania
                WriteLog("Type: Friction");
                Global::fFriction = (tmpEvent->Params[0].asdouble);
            }
            break;
            case tp_Message: // wyświetlenie komunikatu
                break;
            } // switch (tmpEvent->Type)
        } // if (tmpEvent->bEnabled)
    } // while
    return true;
}

void TGround::UpdatePhys(double dt, int iter)
{ // aktualizacja fizyki stałym krokiem: dt=krok czasu [s], dt*iter=czas od ostatnich przeliczeń
    for (TGroundNode *Current = nRootOfType[TP_TRACTIONPOWERSOURCE]; Current;
         Current = Current->nNext)
        Current->psTractionPowerSource->Update(dt * iter); // zerowanie sumy prądów
};

bool TGround::Update(double dt, int iter)
{ // aktualizacja animacji krokiem FPS: dt=krok czasu [s], dt*iter=czas od ostatnich przeliczeń
    if (dt == 0.0)
    { // jeśli załączona jest pauza, to tylko obsłużyć ruch w kabinie trzeba
        return true;
    }
    // Ra: w zasadzie to trzeba by utworzyć oddzielną listę taboru do liczenia fizyki
    //    na którą by się zapisywały wszystkie pojazdy będące w ruchu
    //    pojazdy stojące nie potrzebują aktualizacji, chyba że np. ktoś im zmieni nastawę hamulca
    //    oddzielną listę można by zrobić na pojazdy z napędem, najlepiej posortowaną wg typu napędu
    if (iter > 1) // ABu: ponizsze wykonujemy tylko jesli wiecej niz jedna iteracja
    { // pierwsza iteracja i wyznaczenie stalych:
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
        { //
            Current->DynamicObject->MoverParameters->ComputeConstans();
            Current->DynamicObject->CoupleDist();
            Current->DynamicObject->UpdateForce(dt, dt, false);
        }
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
            Current->DynamicObject->FastUpdate(dt);
        // pozostale iteracje
        for (int i = 1; i < (iter - 1); ++i) // jeśli iter==5, to wykona się 3 razy
        {
            for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
                Current->DynamicObject->UpdateForce(dt, dt, false);
            for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
                Current->DynamicObject->FastUpdate(dt);
        }
        // ABu 200205: a to robimy tylko raz, bo nie potrzeba więcej
        // Winger 180204 - pantografy
        double dt1 = dt * iter; // całkowity czas
        UpdatePhys(dt1, 1);
        TAnimModel::AnimUpdate(dt1); // wykonanie zakolejkowanych animacji
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
        { // Ra: zmienić warunek na sprawdzanie pantografów w jednej zmiennej: czy pantografy i czy
            // podniesione
            if (Current->DynamicObject->MoverParameters->EnginePowerSource.SourceType ==
                CurrentCollector)
                GetTraction(Current->DynamicObject); // poszukiwanie drutu dla pantografów
            Current->DynamicObject->UpdateForce(dt, dt1, true); //,true);
        }
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
            Current->DynamicObject->Update(dt, dt1); // Ra 2015-01: tylko tu przelicza sieć
        // trakcyjną
    }
    else
    { // jezeli jest tylko jedna iteracja
        UpdatePhys(dt, 1);
        TAnimModel::AnimUpdate(dt); // wykonanie zakolejkowanych animacji
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
        {
            if (Current->DynamicObject->MoverParameters->EnginePowerSource.SourceType ==
                CurrentCollector)
                GetTraction(Current->DynamicObject);
            Current->DynamicObject->MoverParameters->ComputeConstans();
            Current->DynamicObject->CoupleDist();
            Current->DynamicObject->UpdateForce(dt, dt, true); //,true);
        }
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
            Current->DynamicObject->Update(dt, dt); // Ra 2015-01: tylko tu przelicza sieć trakcyjną
    }
    if (TDynamicObject::bDynamicRemove)
    { // jeśli jest coś do usunięcia z listy, to trzeba na końcu
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
            if ( false == Current->DynamicObject->bEnabled)
            {
                DynamicRemove(Current->DynamicObject); // usunięcie tego i podłączonych
                Current = nRootDynamic; // sprawdzanie listy od początku
            }
        TDynamicObject::bDynamicRemove = false; // na razie koniec
    }
    return true;
};

void
TGround::Update_Hidden() {

    // rednerowanie globalnych (nie za często?)
    for( TGroundNode *node = srGlobal.nRenderHidden; node; node = node->nNext3 ) {
        node->RenderHidden();
    }

    // render events and sounds from sectors near enough to the viewer
    auto const range = 2750.0; // audible range of 100 db sound
    int const camerax = static_cast<int>( std::floor( Global::pCameraPosition.x / 1000.0 ) + iNumRects / 2 );
    int const cameraz = static_cast<int>( std::floor( Global::pCameraPosition.z / 1000.0 ) + iNumRects / 2 );
    int const segmentcount = 2 * static_cast<int>( std::ceil( range / 1000.0 ) );
    int const originx = std::max( 0, camerax - segmentcount / 2 );
    int const originz = std::max( 0, cameraz - segmentcount / 2 );

    for( int column = originx; column <= originx + segmentcount; ++column ) {
        for( int row = originz; row <= originz + segmentcount; ++row ) {

            auto &cell = Rects[ column ][ row ];

            for( int subcellcolumn = 0; subcellcolumn < iNumSubRects; ++subcellcolumn ) {
                for( int subcellrow = 0; subcellrow < iNumSubRects; ++subcellrow ) {
                    auto subcell = cell.FastGetSubRect( subcellcolumn, subcellrow );
                    if( subcell == nullptr ) { continue; }
                    // renderowanie obiektów aktywnych a niewidocznych
                    for( auto node = subcell->nRenderHidden; node; node = node->nNext3 ) {
                        node->RenderHidden();
                    }
                    // jeszcze dźwięki pojazdów by się przydały, również niewidocznych
                    // TODO: move to sound renderer
                    subcell->RenderSounds();
                }
            }
        }
    }
}

// Winger 170204 - szukanie trakcji nad pantografami
bool TGround::GetTraction(TDynamicObject *model)
{ // aktualizacja drutu zasilającego dla każdego pantografu, żeby odczytać napięcie
    // jeśli pojazd się nie porusza, to nie ma sensu przeliczać tego więcej niż raz
    double fRaParam; // parametr równania parametrycznego odcinka drutu
    double fVertical; // odległość w pionie; musi być w zasięgu ruchu "pionowego" pantografu
    double fHorizontal; // odległość w bok; powinna być mniejsza niż pół szerokości pantografu
    glm::dvec3 vLeft, vUp, vFront, dwys;
    glm::dvec3 pant0;
    glm::dvec3 vParam; // współczynniki równania parametrycznego drutu
    glm::dvec3 vStyk; // punkt przebicia drutu przez płaszczyznę ruchu pantografu
    glm::dvec3 vGdzie; // wektor położenia drutu względem pojazdu
    vFront = glm::make_vec3(model->VectorFront().getArray()); // wektor normalny dla płaszczyzny ruchu pantografu
    vUp = glm::make_vec3(model->VectorUp().getArray()); // wektor pionu pudła (pochylony od pionu na przechyłce)
    vLeft = glm::make_vec3(model->VectorLeft().getArray()); // wektor odległości w bok (odchylony od poziomu na przechyłce)
    auto const position = model->GetPosition(); // współrzędne środka pojazdu
    dwys = glm::dvec3( position.x, position.y, position.z );
    TAnimPant *p; // wskaźnik do obiektu danych pantografu
    for (int k = 0; k < model->iAnimType[ANIM_PANTS]; ++k)
    { // pętla po pantografach
        p = model->pants[k].fParamPants;
        if (k ? model->MoverParameters->PantRearUp : model->MoverParameters->PantFrontUp)
        { // jeśli pantograf podniesiony
            pant0 = dwys + (vLeft * p->vPos.z) + (vUp * p->vPos.y) + (vFront * p->vPos.x);
            if (p->hvPowerWire)
            { // jeżeli znamy drut z poprzedniego przebiegu
                int n = 30; //żeby się nie zapętlił
                while (p->hvPowerWire)
                { // powtarzane aż do znalezienia odpowiedniego odcinka na liście dwukierunkowej
                    // obliczamy wyraz wolny równania płaszczyzny (to miejsce nie jest odpowienie)
                    vParam = p->hvPowerWire->vParametric; // współczynniki równania parametrycznego
                    fRaParam = -glm::dot(pant0, vFront);
                    // podstawiamy równanie parametryczne drutu do równania płaszczyzny pantografu
                    // vFront.x*(t1x+t*vParam.x)+vFront.y*(t1y+t*vParam.y)+vFront.z*(t1z+t*vParam.z)+fRaDist=0;
                    fRaParam = -(glm::dot(p->hvPowerWire->pPoint1, vFront) + fRaParam) /
                               glm::dot(vParam, vFront);
                    if (fRaParam <
                        -0.001) // histereza rzędu 7cm na 70m typowego przęsła daje 1 promil
                        p->hvPowerWire = p->hvPowerWire->hvNext[0];
                    else if (fRaParam > 1.001)
                        p->hvPowerWire = p->hvPowerWire->hvNext[1];
                    else if (p->hvPowerWire->iLast & 3)
                    { // dla ostatniego i przedostatniego przęsła wymuszamy szukanie innego
                        p->hvPowerWire = NULL; // nie to, że nie ma, ale trzeba sprawdzić inne
                        // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu
                        break;
                    }
                    else if (p->hvPowerWire->hvParallel)
                    { // jeśli przęsło tworzy bieżnię wspólną, to trzeba sprawdzić pozostałe
                        p->hvPowerWire = NULL; // nie to, że nie ma, ale trzeba sprawdzić inne
                        // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu
                        break; // tymczasowo dla bieżni wspólnych poszukiwanie po całości
                    }
                    else
                    { // jeśli t jest w przedziale, wyznaczyć odległość wzdłuż wektorów vUp i vLeft
                        vStyk = p->hvPowerWire->pPoint1 + fRaParam * vParam; // punkt styku
                        // płaszczyzny z drutem
                        // (dla generatora łuku
                        // el.)
                        vGdzie = vStyk - pant0; // wektor
                        // odległość w pionie musi być w zasięgu ruchu "pionowego" pantografu
                        fVertical = glm::dot(
                            vGdzie, vUp); // musi się mieścić w przedziale ruchu pantografu
                        // odległość w bok powinna być mniejsza niż pół szerokości pantografu
                        fHorizontal = std::fabs(glm::dot(vGdzie, vLeft)) -
                                      p->fWidth; // to się musi mieścić w przedziale zależnym od
                        // szerokości pantografu
                        // jeśli w pionie albo w bok jest za daleko, to dany drut jest nieużyteczny
                        if (fHorizontal > 0) // 0.635 dla AKP-1 AKP-4E
                        { // drut wyszedł poza zakres roboczy, ale jeszcze jest nabieżnik -
                            // pantograf się unosi bez utraty prądu
                            if (fHorizontal > p->fWidthExtra) // czy wyszedł za nabieżnik
                            {
                                p->hvPowerWire = NULL; // dotychczasowy drut nie liczy się
                                // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu
                            }
                            else
                            { // problem jest, gdy nowy drut jest wyżej, wtedy pantograf odłącza się
                                // od starego, a na podniesienie do nowego potrzebuje czasu
                                p->PantTraction =
                                    fVertical +
                                    0.15 * fHorizontal / p->fWidthExtra; // na razie liniowo na
                                // nabieżniku, dokładność
                                // poprawi się później
                                // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu
                            }
                        }
                        else
                        { // po wyselekcjonowaniu drutu, przypisać go do toru, żeby nie trzeba było
                            // szukać
                            // dla 3 końcowych przęseł sprawdzić wszystkie dostępne przęsła
                            // bo mogą być umieszczone równolegle nad torem - połączyć w pierścień
                            // najlepiej, jakby odcinki równoległe były oznaczone we wpisach
                            // WriteLog("Drut: "+AnsiString(fHorizontal)+" "+AnsiString(fVertical));
                            p->PantTraction = fVertical;
                            // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu
                            break; // koniec pętli, aktualny drut pasuje
                        }
                    }
                    if (--n <= 0) // coś za długo to szukanie trwa
                        p->hvPowerWire = NULL;
                }
            }
            if (!p->hvPowerWire) // else nie, bo mógł zostać wyrzucony
            { // poszukiwanie po okolicznych sektorach
                int c = GetColFromX(dwys.x) + 1;
                int r = GetRowFromZ(dwys.z) + 1;
                TSubRect *tmp;
                TGroundNode *node;
                p->PantTraction = 5.0; // taka za duża wartość
                for (int j = r - 2; j <= r; j++)
                    for (int i = c - 2; i <= c; i++)
                    { // poszukiwanie po najbliższych sektorach niewiele da przy większym
                        // zagęszczeniu
                        tmp = FastGetSubRect(i, j);
                        if (tmp)
                        { // dany sektor może nie mieć nic w środku
                            for (node = tmp->nRenderWires; node;
                                 node = node->nNext3) // następny z grupy
                                if (node->iType == TP_TRACTION) // w grupie tej są druty oraz inne linie
                                {
                                    // współczynniki równania parametrycznego
                                    vParam = node->hvTraction->vParametric;
                                    fRaParam = -glm::dot(pant0, vFront);
                                    auto const paramfrontdot = glm::dot( vParam, vFront );
                                    fRaParam =
                                        -( glm::dot( node->hvTraction->pPoint1, vFront ) + fRaParam )
                                        / ( paramfrontdot != 0.0 ? paramfrontdot : 0.001 ); // div0 trap
                                    if ((fRaParam >= -0.001) ? (fRaParam <= 1.001) : false)
                                    { // jeśli tylko jest w przedziale, wyznaczyć odległość wzdłuż wektorów vUp i vLeft
                                      // punkt styku płaszczyzny z drutem (dla generatora łuku el.)
                                        vStyk = node->hvTraction->pPoint1 + fRaParam * vParam;
                                        // wektor musi się mieścić w przedziale ruchu pantografu
                                        vGdzie = vStyk - pant0;
                                        fVertical = glm::dot( vGdzie, vUp);
                                        if (fVertical >= 0.0) // jeśli ponad pantografem (bo może
                                            // łapać druty spod wiaduktu)
                                            if (Global::bEnableTraction ?
                                                    fVertical < p->PantWys - 0.15 :
                                                    false) {
                                                // jeśli drut jest niżej niż 15cm pod ślizgiem
                                                // przełączamy w tryb połamania, o ile jedzie;
                                                // (bEnableTraction) aby dało się jeździć na
                                                // koślawych
                                                // sceneriach
                                                fHorizontal = std::fabs(glm::dot(vGdzie, vLeft)) -
                                                              p->fWidth; // i do tego jeszcze
                                                // wejdzie pod ślizg
                                                if (fHorizontal <= 0.0) // 0.635 dla AKP-1 AKP-4E
                                                {
                                                    p->PantWys =
                                                        -1.0; // ujemna liczba oznacza połamanie
                                                    p->hvPowerWire = NULL; // bo inaczej się zasila
                                                    // w nieskończoność z
                                                    // połamanego
                                                    // p->fHorizontal=fHorizontal; //zapamiętanie
                                                    // położenia drutu
                                                    if (model->MoverParameters->EnginePowerSource
                                                            .CollectorParameters.CollectorsNo >
                                                        0) // liczba pantografów
                                                        --model->MoverParameters->EnginePowerSource
                                                              .CollectorParameters
                                                              .CollectorsNo; // teraz będzie
                                                    // mniejsza
                                                    if (DebugModeFlag)
                                                        ErrorLog(
                                                            "Pant. break: at " +
                                                            to_string(pant0.x, 2, 7) +
                                                            " " +
                                                            to_string(pant0.y, 2, 7) +
                                                            " " +
                                                            to_string(pant0.z, 2, 7));
                                                }
                                            }
                                            else if (fVertical < p->PantTraction) // ale niżej, niż
                                            // poprzednio
                                            // znaleziony
                                            {
                                                fHorizontal =
                                                    std::fabs(glm::dot(vGdzie, vLeft)) - p->fWidth;
                                                if (fHorizontal <= 0.0) // 0.635 dla AKP-1 AKP-4E
                                                { // to się musi mieścić w przedziale zaleznym od
                                                    // szerokości pantografu
                                                    p->hvPowerWire =
                                                        node->hvTraction; // jakiś znaleziony
                                                    p->PantTraction =
                                                        fVertical; // zapamiętanie nowej wysokości
                                                    // p->fHorizontal=fHorizontal; //zapamiętanie
                                                    // położenia drutu
                                                }
                                                else if (fHorizontal <
                                                         p->fWidthExtra) // czy zmieścił się w
                                                // zakresie nabieżnika?
                                                { // problem jest, gdy nowy drut jest wyżej, wtedy
                                                    // pantograf odłącza się od starego, a na
                                                    // podniesienie do nowego potrzebuje czasu
                                                    fVertical +=
                                                        0.15 * fHorizontal /
                                                        p->fWidthExtra; // korekta wysokości o
                                                    // nabieżnik - drut nad
                                                    // nabieżnikiem jest
                                                    // geometrycznie jakby nieco
                                                    // wyżej
                                                    if (fVertical <
                                                        p->PantTraction) // gdy po korekcie jest
                                                    // niżej, niż poprzednio
                                                    // znaleziony
                                                    { // gdyby to wystarczyło, to możemy go uznać
                                                        p->hvPowerWire =
                                                            node->hvTraction; // może być
                                                        p->PantTraction =
                                                            fVertical; // na razie liniowo na
                                                        // nabieżniku, dokładność
                                                        // poprawi się później
                                                        // p->fHorizontal=fHorizontal;
                                                        // //zapamiętanie położenia drutu
                                                    }
                                                }
                                            }
                                    } // warunek na parametr drutu <0;1>
                                } // pętla po drutach
                        } // sektor istnieje
                    } // pętla po sektorach
            } // koniec poszukiwania w sektorach
            if (!p->hvPowerWire) // jeśli drut nie znaleziony
                if (!Global::bLiveTraction) // ale można oszukiwać
                    model->pants[k].fParamPants->PantTraction = 1.4; // to dajemy coś tam dla picu
        } // koniec obsługi podniesionego
        else
            p->hvPowerWire = NULL; // pantograf opuszczony
    }

    return true;
};

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void TGround::RadioStop(vector3 pPosition)
{ // zatrzymanie pociągów w okolicy
    TGroundNode *node;
    TSubRect *tmp;
    int c = GetColFromX(pPosition.x);
    int r = GetRowFromZ(pPosition.z);
    int i, j;
    int n = 2 * iNumSubRects; // przeglądanie czołgowe okolicznych torów w kwadracie 4km×4km
    for (j = r - n; j <= r + n; j++)
        for (i = c - n; i <= c + n; i++)
            if ((tmp = FastGetSubRect(i, j)) != NULL)
                for (node = tmp->nRootNode; node != NULL; node = node->nNext2)
                    if (node->iType == TP_TRACK)
                        node->pTrack->RadioStop(); // przekazanie do każdego toru w każdym segmencie
};

TDynamicObject * TGround::DynamicNearest(vector3 pPosition, double distance, bool mech)
{ // wyszukanie pojazdu najbliższego względem (pPosition)
    TGroundNode *node;
    TSubRect *tmp;
    TDynamicObject *dyn = NULL;
    int c = GetColFromX(pPosition.x);
    int r = GetRowFromZ(pPosition.z);
	int i, j;
    double sqm = distance * distance, sqd; // maksymalny promien poszukiwań do kwadratu
    for (j = r - 1; j <= r + 1; j++) // plus dwa zewnętrzne sektory, łącznie 9
        for (i = c - 1; i <= c + 1; i++)
            if ((tmp = FastGetSubRect(i, j)) != NULL)
                for (node = tmp->nRootNode; node; node = node->nNext2) // następny z sektora
                    if (node->iType == TP_TRACK) // Ra: przebudować na użycie tabeli torów?
                        for( auto dynamic : node->pTrack->Dynamics ) {
                            if( mech ? ( dynamic->Mechanik != nullptr ) : true ) {
                                // czy ma mieć obsadę
                                if( ( sqd = SquareMagnitude( dynamic->GetPosition() - pPosition ) ) < sqm ) {
                                    sqm = sqd; // nowa odległość
                                    dyn = dynamic; // nowy lider
                                }
                            }
                        }
    return dyn;
};

TDynamicObject * TGround::CouplerNearest(vector3 pPosition, double distance, bool mech)
{ // wyszukanie pojazdu, którego sprzęg jest najbliżej względem (pPosition)
    TGroundNode *node;
    TSubRect *tmp;
    TDynamicObject *dyn = NULL;
    int c = GetColFromX(pPosition.x);
    int r = GetRowFromZ(pPosition.z);
	int i, j;
    double sqm = distance * distance, sqd; // maksymalny promien poszukiwań do kwadratu
    for (j = r - 1; j <= r + 1; j++) // plus dwa zewnętrzne sektory, łącznie 9
        for (i = c - 1; i <= c + 1; i++)
            if ((tmp = FastGetSubRect(i, j)) != NULL)
                for (node = tmp->nRootNode; node; node = node->nNext2) // następny z sektora
                    if (node->iType == TP_TRACK) // Ra: przebudować na użycie tabeli torów?
                        for( auto dynamic : node->pTrack->Dynamics ) {
                            if( mech ? ( dynamic->Mechanik != nullptr ) : true ) {
                                // czy ma mieć obsadę
                                if( ( sqd = SquareMagnitude( dynamic->HeadPosition() - pPosition ) ) < sqm ) {
                                    sqm = sqd; // nowa odległość
                                    dyn = dynamic; // nowy lider
                                }
                                if( ( sqd = SquareMagnitude( dynamic->RearPosition() - pPosition ) ) < sqm ) {
                                    sqm = sqd; // nowa odległość
                                    dyn = dynamic; // nowy lider
                                }
                            }
                        }
    return dyn;
};
#endif
//---------------------------------------------------------------------------
void TGround::DynamicRemove(TDynamicObject *dyn)
{ // Ra: usunięcie pojazdów ze scenerii (gdy dojadą na koniec i nie sa potrzebne)
    TDynamicObject *d = dyn->Prev();
    if (d) // jeśli coś jest z przodu
        DynamicRemove(d); // zaczynamy od tego z przodu
    else
    { // jeśli mamy już tego na początku
        TGroundNode **n, *node;
        d = dyn; // od pierwszego
        while (d)
        {
            if (d->MyTrack)
                d->MyTrack->RemoveDynamicObject(d); // usunięcie z toru o ile nie usunięty
            n = &nRootDynamic; // lista pojazdów od początku
            // node=NULL; //nie znalezione
            while (*n ? (*n)->DynamicObject != d : false)
            { // usuwanie z listy pojazdów
                n = &((*n)->nNext); // sprawdzenie kolejnego pojazdu na liście
            }
            if ((*n)->DynamicObject == d)
            { // jeśli znaleziony
                node = (*n); // zapamiętanie węzła, aby go usunąć
                (*n) = node->nNext; // pominięcie na liście
                Global::TrainDelete(d);
                // remove potential entries in the light array
                simulation::Lights.remove( d );
                d = d->Next(); // przejście do kolejnego pojazdu, póki jeszcze jest
                delete node; // usuwanie fizyczne z pamięci
            }
            else
                d = NULL; // coś nie tak!
        }
    }
};

//---------------------------------------------------------------------------

void TGround::TerrainRead(std::string const &f){
    // Ra: wczytanie trójkątów terenu z pliku E3D
};

//---------------------------------------------------------------------------

void TGround::TerrainWrite()
{ // Ra: zapisywanie trójkątów terenu do pliku E3D
    // TODO: re-implement
/*
    if (Global::pTerrainCompact->TerrainLoaded())
        return; // jeśli zostało wczytane, to nie ma co dalej robić
    if (Global::asTerrainModel.empty())
        return;
    // Trójkąty są zapisywane kwadratami kilometrowymi.
    // Kwadrat 500500 jest na środku (od 0.0 do 1000.0 na OX oraz OZ).
    // Ewentualnie w numerowaniu kwadratów uwzględnic wpis //$g.
    // Trójkąty są grupowane w submodele wg tekstury.
    // Triangle_strip oraz triangle_fan są rozpisywane na pojedyncze trójkąty,
    // chyba że dla danej tekstury wychodzi tylko jeden submodel.
    TModel3d *m = new TModel3d(); // wirtualny model roboczy z oddzielnymi submodelami
    TSubModel *sk; // wskaźnik roboczy na submodel kwadratu
    // Zliczamy kwadraty z trójkątami, ilość tekstur oraz wierzchołków.
    // Ilość kwadratów i ilość tekstur określi ilość submodeli.
    // int sub=0; //całkowita ilość submodeli
    // int ver=0; //całkowita ilość wierzchołków
    int i, j, k; // indeksy w pętli
    TGroundNode *Current;
    basic_vertex *ver; // trójkąty
    TSubModel::iInstance = 0; // pozycja w tabeli wierzchołków liczona narastająco
    for (i = 0; i < iNumRects; ++i) // pętla po wszystkich kwadratach kilometrowych
        for (j = 0; j < iNumRects; ++j)
            if (Rects[i][j].iNodeCount)
            { // o ile są jakieś trójkąty w środku
                sk = new TSubModel(); // nowy submodel dla kawadratu
                // numer kwadratu XXXZZZ, przy czym X jest ujemne - XXX rośnie na wschód, ZZZ rośnie
                // na północ
                sk->NameSet( std::to_string(1000 * (500 + i - iNumRects / 2) + (500 + j - iNumRects / 2)).c_str() ); // nazwa=numer kwadratu
                m->AddTo(NULL, sk); // dodanie submodelu dla kwadratu
                for (Current = Rects[i][j].nRootNode; Current; Current = Current->nNext2)
                    if (Current->TextureID)
                        switch (Current->iType)
                        { // pętla po trójkątach - zliczanie wierzchołków, dodaje submodel dla
                        // każdej tekstury
                        case GL_TRIANGLES:
                            Current->iVboPtr = sk->TriangleAdd(
                                m, Current->TextureID,
                                Current->iNumVerts); // zwiększenie ilości trójkątów w submodelu
                            m->iNumVerts +=
                                Current->iNumVerts; // zwiększenie całkowitej ilości wierzchołków
                            break;
                        case GL_TRIANGLE_STRIP: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty
                            break;
                        case GL_TRIANGLE_FAN: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty
                            break;
                        }
                for (Current = Rects[i][j].nRootNode; Current; Current = Current->nNext2)
                    if (Current->TextureID)
                        switch (Current->iType)
                        { // pętla po trójkątach - dopisywanie wierzchołków
                        case GL_TRIANGLES:
                            // //wskaźnik na początek
                            ver = sk->TrianglePtr(Current->TextureID, Current->iVboPtr,
                                                  Current->Ambient, Current->Diffuse,
                                                  Current->Specular); // wskaźnik na początek
                            Current->iVboPtr = -1; // bo to było tymczasowo używane
                            for (k = 0; k < Current->iNumVerts; ++k)
                            { // przepisanie współrzędnych
                                ver[ k ].position = Current->Vertices[ k ].position;
                                ver[ k ].normal = Current->Vertices[ k ].normal;
                                ver[ k ].texture = Current->Vertices[ k ].texture;
                            }
                            break;
                        case GL_TRIANGLE_STRIP: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty
                            break;
                        case GL_TRIANGLE_FAN: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty
                            break;
                        }
            }
    m->SaveToBinFile(strdup(("models\\" + Global::asTerrainModel).c_str()));
*/
};

//---------------------------------------------------------------------------
#ifdef EU07_USE_OLD_GROUNDCODE
void TGround::TrackBusyList()
{ // wysłanie informacji o wszystkich zajętych odcinkach
    TGroundNode *Current;
    for (Current = nRootOfType[TP_TRACK]; Current; Current = Current->nNext)
        if (!Current->asName.empty()) // musi być nazwa
            if( false == Current->pTrack->Dynamics.empty() )
                multiplayer::WyslijString(Current->asName, 8); // zajęty
};
//---------------------------------------------------------------------------

void TGround::IsolatedBusyList()
{ // wysłanie informacji o wszystkich odcinkach izolowanych
    TIsolated *Current;
    for (Current = TIsolated::Root(); Current; Current = Current->Next())
        if (Current->Busy()) // sprawdź zajętość
            multiplayer::WyslijString(Current->asName, 11); // zajęty
        else
            multiplayer::WyslijString(Current->asName, 10); // wolny
    multiplayer::WyslijString("none", 10); // informacja o końcu listy
};
//---------------------------------------------------------------------------

void TGround::IsolatedBusy(const std::string t)
{ // wysłanie informacji o odcinku izolowanym (t)
    // Ra 2014-06: do wyszukania użyć drzewka nazw
    TIsolated *Current;
    for (Current = TIsolated::Root(); Current; Current = Current->Next())
        if (Current->asName == t) // wyszukiwanie odcinka o nazwie (t)
            if (Current->Busy()) // sprawdź zajetość
            {
                multiplayer::WyslijString(Current->asName, 11); // zajęty
                return; // nie sprawdzaj dalszych
            }
    multiplayer::WyslijString(t, 10); // wolny
};
//---------------------------------------------------------------------------

void TGround::Silence(vector3 gdzie)
{ // wyciszenie wszystkiego w sektorach przed przeniesieniem kamery z (gdzie)
    TGroundNode *node;
    int n = 2 * iNumSubRects; //(2*==2km) promień wyświetlanej mapy w sektorach
    int c = GetColFromX(gdzie.x); // sektory wg dotychczasowej pozycji kamery
    int r = GetRowFromZ(gdzie.z);
    TSubRect *tmp;
	int i, j;
    // renderowanie czołgowe dla obiektów aktywnych a niewidocznych
    for (j = r - n; j <= r + n; j++)
        for (i = c - n; i <= c + n; i++)
            if ((tmp = FastGetSubRect(i, j)) != NULL)
            { // tylko dźwięki interesują
                for (node = tmp->nRenderHidden; node; node = node->nNext3)
                    node->RenderHidden();
                tmp->RenderSounds(); // dźwięki pojazdów by się przydało wyłączyć
            }
};
#endif
//---------------------------------------------------------------------------
