/*
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 "system.hpp"
#include "classes.hpp"

#include "opengl/glew.h"
#include "opengl/glut.h"

#pragma hdrstop

#include "Timer.h"
#include "Texture.h"
#include "Ground.h"
#include "Globals.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.hpp"
#include "DynObj.h"
#include "Data.h"
#include "parser.h" //Tolaris-010603
#include "Driver.h"
#include "Console.h"
#include "Names.h"

#define _PROBLEND 1
//---------------------------------------------------------------------------
#pragma package(smart_init)

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

//---------------------------------------------------------------------------
// 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;
    Vertices = NULL;
    nNext = nNext2 = NULL;
    pCenter = vector3(0, 0, 0);
    iCount = 0; // wierzchołków w trójkącie
    // iNumPts=0; //punktów w linii
    TextureID = 0;
    iFlags = 0; // tryb przezroczystości nie zbadany
    DisplayListID = 0;
    Pointer = NULL; // zerowanie wskaźnika kontekstowego
    bVisible = false; // czy widoczny
    fSquareRadius = 10000 * 10000;
    fSquareMinRadius = 0;
    asName = "";
    // Color= TMaterialColor(1);
    // fAngle=0; //obrót dla modelu
    // fLineThickness=1.0; //mm dla linii
    for (int i = 0; i < 3; i++)
    {
        Ambient[i] = Global::whiteLight[i] * 255;
        Diffuse[i] = Global::whiteLight[i] * 255;
        Specular[i] = Global::noLight[i] * 255;
    }
    nNext3 = NULL; // nie wyświetla innych
    iVboPtr = -1; // indeks w VBO sektora (-1: nie używa VBO)
    iVersion = 0; // wersja siatki
}

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_TERRAIN:
    { // pierwsze nNode zawiera model E3D, reszta to trójkąty
        for (int i = 1; i < iCount; ++i)
            nNode->Vertices =
                NULL; // zerowanie wskaźników w kolejnych elementach, bo nie są do usuwania
        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:
        SafeDeleteArray(Points);
        break;
    case GL_TRIANGLE_STRIP:
    case GL_TRIANGLE_FAN:
    case GL_TRIANGLES:
        SafeDeleteArray(Vertices);
        break;
    }
}

void TGroundNode::Init(int n)
{ // utworzenie tablicy wierzchołków
    bVisible = false;
    iNumVerts = n;
    Vertices = new TGroundVertex[iNumVerts];
}

TGroundNode::TGroundNode(TGroundNodeType t, int n)
{ // utworzenie obiektu
    TGroundNode(); // domyślne ustawienia
    iNumVerts = n;
    if (iNumVerts)
        Vertices = new TGroundVertex[iNumVerts];
    iType = t;
    switch (iType)
    { // zależnie od typu
    case TP_TRACK:
        pTrack = new TTrack(this);
        break;
    }
}

void TGroundNode::InitCenter()
{ // obliczenie środka ciężkości obiektu
    for (int i = 0; i < iNumVerts; i++)
        pCenter += Vertices[i].Point;
    pCenter /= iNumVerts;
}

void TGroundNode::InitNormals()
{ // obliczenie wektorów normalnych
    vector3 v1, v2, v3, v4, v5, n1, n2, n3, n4;
    int i;
    float tu, tv;
    switch (iType)
    {
    case GL_TRIANGLE_STRIP:
        v1 = Vertices[0].Point - Vertices[1].Point;
        v2 = Vertices[1].Point - Vertices[2].Point;
        n1 = SafeNormalize(CrossProduct(v1, v2));
        if (Vertices[0].Normal == vector3(0, 0, 0))
            Vertices[0].Normal = n1;
        v3 = Vertices[2].Point - Vertices[3].Point;
        n2 = SafeNormalize(CrossProduct(v3, v2));
        if (Vertices[1].Normal == vector3(0, 0, 0))
            Vertices[1].Normal = (n1 + n2) * 0.5;

        for (i = 2; i < iNumVerts - 2; i += 2)
        {
            v4 = Vertices[i - 1].Point - Vertices[i].Point;
            v5 = Vertices[i].Point - Vertices[i + 1].Point;
            n3 = SafeNormalize(CrossProduct(v3, v4));
            n4 = SafeNormalize(CrossProduct(v5, v4));
            if (Vertices[i].Normal == vector3(0, 0, 0))
                Vertices[i].Normal = (n1 + n2 + n3) / 3;
            if (Vertices[i + 1].Normal == vector3(0, 0, 0))
                Vertices[i + 1].Normal = (n2 + n3 + n4) / 3;
            n1 = n3;
            n2 = n4;
            v3 = v5;
        }
        if (Vertices[i].Normal == vector3(0, 0, 0))
            Vertices[i].Normal = (n1 + n2) / 2;
        if (Vertices[i + 1].Normal == vector3(0, 0, 0))
            Vertices[i + 1].Normal = n2;
        break;
    case GL_TRIANGLE_FAN:

        break;
    case GL_TRIANGLES:
        for (i = 0; i < iNumVerts; i += 3)
        {
            v1 = Vertices[i + 0].Point - Vertices[i + 1].Point;
            v2 = Vertices[i + 1].Point - Vertices[i + 2].Point;
            n1 = SafeNormalize(CrossProduct(v1, v2));
            if (Vertices[i + 0].Normal == vector3(0, 0, 0))
                Vertices[i + 0].Normal = (n1);
            if (Vertices[i + 1].Normal == vector3(0, 0, 0))
                Vertices[i + 1].Normal = (n1);
            if (Vertices[i + 2].Normal == vector3(0, 0, 0))
                Vertices[i + 2].Normal = (n1);
            tu = floor(Vertices[i + 0].tu);
            tv = floor(Vertices[i + 0].tv);
            Vertices[i + 1].tv -= tv;
            Vertices[i + 2].tv -= tv;
            Vertices[i + 0].tv -= tv;
            Vertices[i + 1].tu -= tu;
            Vertices[i + 2].tu -= tu;
            Vertices[i + 0].tu -= tu;
        }
        break;
    }
}

void TGroundNode::MoveMe(vector3 pPosition)
{ // przesuwanie obiektów scenerii o wektor w celu redukcji trzęsienia
    pCenter += pPosition;
    switch (iType)
    {
    case TP_TRACTION:
        hvTraction->pPoint1 += pPosition;
        hvTraction->pPoint2 += pPosition;
        hvTraction->pPoint3 += pPosition;
        hvTraction->pPoint4 += pPosition;
        hvTraction->Optimize();
        break;
    case TP_MODEL:
    case TP_DYNAMIC:
    case TP_MEMCELL:
    case TP_EVLAUNCH:
        break;
    case TP_TRACK:
        pTrack->MoveMe(pPosition);
        break;
    case TP_SOUND: // McZapkie - dzwiek zapetlony w zaleznosci od odleglosci
        tsStaticSound->vSoundPosition += pPosition;
        break;
    case GL_LINES:
    case GL_LINE_STRIP:
    case GL_LINE_LOOP:
        for (int i = 0; i < iNumPts; i++)
            Points[i] += pPosition;
        ResourceManager::Unregister(this);
        break;
    default:
        for (int i = 0; i < iNumVerts; i++)
            Vertices[i].Point += pPosition;
        ResourceManager::Unregister(this);
    }
}

void TGroundNode::RaRenderVBO()
{ // renderowanie z domyslnego bufora VBO
    glColor3ub(Diffuse[0], Diffuse[1], Diffuse[2]);
    if (TextureID)
        glBindTexture(GL_TEXTURE_2D, TextureID); // Ustaw aktywną teksturę
    glDrawArrays(iType, iVboPtr, iNumVerts); // Narysuj naraz wszystkie trójkąty
}

void TGroundNode::RenderVBO()
{ // renderowanie obiektu z VBO - faza nieprzezroczystych
    double mgn = SquareMagnitude(pCenter - Global::pCameraPosition);
    if ((mgn > fSquareRadius || (mgn < fSquareMinRadius)) &&
        (iType != TP_EVLAUNCH)) // McZapkie-070602: nie rysuj odleglych obiektow ale sprawdzaj
        // wyzwalacz zdarzen
        return;
    int i, a;
    switch (iType)
    {
    case TP_TRACTION:
        return;
    case TP_TRACK:
        if (iNumVerts)
            pTrack->RaRenderVBO(iVboPtr);
        return;
    case TP_MODEL:
        Model->RenderVBO(&pCenter);
        return;
    // case TP_SOUND: //McZapkie - dzwiek zapetlony w zaleznosci od odleglosci
    // if ((pStaticSound->GetStatus()&DSBSTATUS_PLAYING)==DSBPLAY_LOOPING)
    // {
    //  pStaticSound->Play(1,DSBPLAY_LOOPING,true,pStaticSound->vSoundPosition);
    //  pStaticSound->AdjFreq(1.0,Timer::GetDeltaTime());
    // }
    // return; //Ra: TODO sprawdzić, czy dźwięki nie są tylko w RenderHidden
    case TP_MEMCELL:
        return;
    case TP_EVLAUNCH:
        if (EvLaunch->Render())
            if ((EvLaunch->dRadius < 0) || (mgn < EvLaunch->dRadius))
            {
                if (Console::Pressed(VK_SHIFT) && EvLaunch->Event2 != NULL)
                    Global::AddToQuery(EvLaunch->Event2, NULL);
                else if (EvLaunch->Event1 != NULL)
                    Global::AddToQuery(EvLaunch->Event1, NULL);
            }
        return;
    case GL_LINES:
    case GL_LINE_STRIP:
    case GL_LINE_LOOP:
        if (iNumPts)
        {
            float linealpha = 255000 * fLineThickness / (mgn + 1.0);
            if (linealpha > 255)
                linealpha = 255;
            float r, g, b;
            r = floor(Diffuse[0] * Global::ambientDayLight[0]); // w zaleznosci od koloru swiatla
            g = floor(Diffuse[1] * Global::ambientDayLight[1]);
            b = floor(Diffuse[2] * Global::ambientDayLight[2]);
            glColor4ub(r, g, b, linealpha); // przezroczystosc dalekiej linii
            // glDisable(GL_LIGHTING); //nie powinny świecić
            glDrawArrays(iType, iVboPtr, iNumPts); // rysowanie linii
            // glEnable(GL_LIGHTING);
        }
        return;
    default:
        if (iVboPtr >= 0)
            RaRenderVBO();
    };
    return;
};

void TGroundNode::RenderAlphaVBO()
{ // renderowanie obiektu z VBO - faza przezroczystych
    double mgn = SquareMagnitude(pCenter - Global::pCameraPosition);
    float r, g, b;
    if (mgn < fSquareMinRadius)
        return;
    if (mgn > fSquareRadius)
        return;
    int i, a;
#ifdef _PROBLEND
    if ((PROBLEND)) // sprawdza, czy w nazwie nie ma @    //Q: 13122011 - Szociu: 27012012
    {
        glDisable(GL_BLEND);
        glAlphaFunc(GL_GREATER, 0.45); // im mniejsza wartość, tym większa ramka, domyślnie 0.1f
    };
#endif
    switch (iType)
    {
    case TP_TRACTION:
        if (bVisible)
        {
#ifdef _PROBLEND
            glEnable(GL_BLEND);
            glAlphaFunc(GL_GREATER, 0.04);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
            hvTraction->RenderVBO(mgn, iVboPtr);
        }
        return;
    case TP_MODEL:
#ifdef _PROBLEND
        glEnable(GL_BLEND);
        glAlphaFunc(GL_GREATER, 0.04);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
        Model->RenderAlphaVBO(&pCenter);
        return;
    case GL_LINES:
    case GL_LINE_STRIP:
    case GL_LINE_LOOP:
        if (iNumPts)
        {
            float linealpha = 255000 * fLineThickness / (mgn + 1.0);
            if (linealpha > 255)
                linealpha = 255;
            r = Diffuse[0] * Global::ambientDayLight[0]; // w zaleznosci od koloru swiatla
            g = Diffuse[1] * Global::ambientDayLight[1];
            b = Diffuse[2] * Global::ambientDayLight[2];
            glColor4ub(r, g, b, linealpha); // przezroczystosc dalekiej linii
            // glDisable(GL_LIGHTING); //nie powinny świecić
            glDrawArrays(iType, iVboPtr, iNumPts); // rysowanie linii
// glEnable(GL_LIGHTING);
#ifdef _PROBLEND
            glEnable(GL_BLEND);
            glAlphaFunc(GL_GREATER, 0.04);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
        }
#ifdef _PROBLEND
        glEnable(GL_BLEND);
        glAlphaFunc(GL_GREATER, 0.04);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
        return;
    default:
        if (iVboPtr >= 0)
        {
            RaRenderVBO();
#ifdef _PROBLEND
            glEnable(GL_BLEND);
            glAlphaFunc(GL_GREATER, 0.04);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
            return;
        }
    }
#ifdef _PROBLEND
    glEnable(GL_BLEND);
    glAlphaFunc(GL_GREATER, 0.04);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
    return;
}

void TGroundNode::Compile(bool many)
{ // tworzenie skompilowanej listy w wyświetlaniu DL
    if (!many)
    { // obsługa pojedynczej listy
        if (DisplayListID)
            Release();
        if (Global::bManageNodes)
        {
            DisplayListID = glGenLists(1);
            glNewList(DisplayListID, GL_COMPILE);
            iVersion = Global::iReCompile; // aktualna wersja siatek (do WireFrame)
        }
    }
    if ((iType == GL_LINES) || (iType == GL_LINE_STRIP) || (iType == GL_LINE_LOOP))
    {
#ifdef USE_VERTEX_ARRAYS
        glVertexPointer(3, GL_DOUBLE, sizeof(vector3), &Points[0].x);
#endif
        glBindTexture(GL_TEXTURE_2D, 0);
#ifdef USE_VERTEX_ARRAYS
        glDrawArrays(iType, 0, iNumPts);
#else
        glBegin(iType);
        for (int i = 0; i < iNumPts; i++)
            glVertex3dv(&Points[i].x);
        glEnd();
#endif
    }
    else if (iType == GL_TRIANGLE_STRIP || iType == GL_TRIANGLE_FAN || iType == GL_TRIANGLES)
    { // jak nie linie, to trójkąty
        TGroundNode *tri = this;
        do
        { // pętla po obiektach w grupie w celu połączenia siatek
#ifdef USE_VERTEX_ARRAYS
            glVertexPointer(3, GL_DOUBLE, sizeof(TGroundVertex), &tri->Vertices[0].Point.x);
            glNormalPointer(GL_DOUBLE, sizeof(TGroundVertex), &tri->Vertices[0].Normal.x);
            glTexCoordPointer(2, GL_FLOAT, sizeof(TGroundVertex), &tri->Vertices[0].tu);
#endif
            glColor3ub(tri->Diffuse[0], tri->Diffuse[1], tri->Diffuse[2]);
            glBindTexture(GL_TEXTURE_2D, Global::bWireFrame ? 0 : tri->TextureID);
#ifdef USE_VERTEX_ARRAYS
            glDrawArrays(Global::bWireFrame ? GL_LINE_LOOP : tri->iType, 0, tri->iNumVerts);
#else
            glBegin(Global::bWireFrame ? GL_LINE_LOOP : tri->iType);
            for (int i = 0; i < tri->iNumVerts; i++)
            {
                glNormal3d(tri->Vertices[i].Normal.x, tri->Vertices[i].Normal.y,
                           tri->Vertices[i].Normal.z);
                glTexCoord2f(tri->Vertices[i].tu, tri->Vertices[i].tv);
                glVertex3dv(&tri->Vertices[i].Point.x);
            };
            glEnd();
#endif
            /*
               if (tri->pTriGroup) //jeśli z grupy
               {tri=tri->pNext2; //następny w sektorze
                while (tri?!tri->pTriGroup:false) tri=tri->pNext2; //szukamy kolejnego należącego do
               grupy
               }
               else
            */
            tri = NULL; // a jak nie, to koniec
        } while (tri);
    }
    else if (iType == TP_MESH)
    { // grupa ze wspólną teksturą - wrzucanie do wspólnego Display List
        if (TextureID)
            glBindTexture(GL_TEXTURE_2D, TextureID); // Ustaw aktywną teksturę
        TGroundNode *n = nNode;
        while (n ? n->TextureID == TextureID : false)
        { // wszystkie obiekty o tej samej testurze
            switch (n->iType)
            { // poszczególne typy różnie się tworzy
            case TP_TRACK:
            case TP_DUMMYTRACK:
                n->pTrack->Compile(TextureID); // dodanie trójkątów dla podanej tekstury
                break;
            }
            n = n->nNext3; // następny z listy
        }
    }
    if (!many)
        if (Global::bManageNodes)
            glEndList();
};

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

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->Render())
            if ((EvLaunch->dRadius < 0) || (mgn < EvLaunch->dRadius))
            {
                WriteLog("Eventlauncher " + asName);
                if (Console::Pressed(VK_SHIFT) && (EvLaunch->Event2))
                    Global::AddToQuery(EvLaunch->Event2, NULL);
                else if (EvLaunch->Event1)
                    Global::AddToQuery(EvLaunch->Event1, NULL);
            }
        return;
    }
};

void TGroundNode::RenderDL()
{ // wyświetlanie obiektu przez Display List
    switch (iType)
    { // obiekty renderowane niezależnie od odległości
    case TP_SUBMODEL:
        TSubModel::fSquareDist = 0;
        return smTerrain->RenderDL();
    }
    // if (pTriGroup) if (pTriGroup!=this) return; //wyświetla go inny obiekt
    double mgn = SquareMagnitude(pCenter - Global::pCameraPosition);
    if ((mgn > fSquareRadius) || (mgn < fSquareMinRadius)) // McZapkie-070602: nie rysuj odleglych
        // obiektow ale sprawdzaj wyzwalacz
        // zdarzen
        return;
    int i, a;
    switch (iType)
    {
    case TP_TRACK:
        return pTrack->Render();
    case TP_MODEL:
        return Model->RenderDL(&pCenter);
    }
    // TODO: sprawdzic czy jest potrzebny warunek fLineThickness < 0
    // if ((iNumVerts&&(iFlags&0x10))||(iNumPts&&(fLineThickness<0)))
    if ((iFlags & 0x10) || (fLineThickness < 0))
    {
        if (!DisplayListID || (iVersion != Global::iReCompile)) // Ra: wymuszenie rekompilacji
        {
            Compile();
            if (Global::bManageNodes)
                ResourceManager::Register(this);
        };

        if ((iType == GL_LINES) || (iType == GL_LINE_STRIP) || (iType == GL_LINE_LOOP))
        // if (iNumPts)
        { // wszelkie linie są rysowane na samym końcu
            float r, g, b;
            r = Diffuse[0] * Global::ambientDayLight[0]; // w zaleznosci od koloru swiatla
            g = Diffuse[1] * Global::ambientDayLight[1];
            b = Diffuse[2] * Global::ambientDayLight[2];
            glColor4ub(r, g, b, 1.0);
            glCallList(DisplayListID);
            // glColor4fv(Diffuse); //przywrócenie koloru
            // glColor3ub(Diffuse[0],Diffuse[1],Diffuse[2]);
        }
        // GL_TRIANGLE etc
        else
            glCallList(DisplayListID);
        SetLastUsage(Timer::GetSimulationTime());
    };
};

void TGroundNode::RenderAlphaDL()
{
    // SPOSOB NA POZBYCIE SIE RAMKI DOOKOLA TEXTURY ALPHA DLA OBIEKTOW ZAGNIEZDZONYCH W SCN JAKO
    // NODE

    // W GROUND.H dajemy do klasy TGroundNode zmienna bool PROBLEND to samo robimy w klasie TGround
    // nastepnie podczas wczytywania textury dla TRIANGLES w TGround::AddGroundNode
    // sprawdzamy czy w nazwie jest @ i wg tego
    // ustawiamy PROBLEND na true dla wlasnie wczytywanego trojkata (kazdy trojkat jest osobnym
    // nodem)
    // nastepnie podczas renderowania w bool TGroundNode::RenderAlpha()
    // na poczatku ustawiamy standardowe GL_GREATER = 0.04
    // pozniej sprawdzamy czy jest wlaczony PROBLEND dla aktualnie renderowanego noda TRIANGLE,
    // wlasciwie dla kazdego node'a
    // i jezeli tak to odpowiedni GL_GREATER w przeciwnym wypadku standardowy 0.04

    // if (pTriGroup) if (pTriGroup!=this) return; //wyświetla go inny obiekt
    double mgn = SquareMagnitude(pCenter - Global::pCameraPosition);
    float r, g, b;
    if (mgn < fSquareMinRadius)
        return;
    if (mgn > fSquareRadius)
        return;
    int i, a;
    switch (iType)
    {
    case TP_TRACTION:
        if (bVisible)
            hvTraction->RenderDL(mgn);
        return;
    case TP_MODEL:
        Model->RenderAlphaDL(&pCenter);
        return;
    case TP_TRACK:
        // pTrack->RenderAlpha();
        return;
    };

    // TODO: sprawdzic czy jest potrzebny warunek fLineThickness < 0
    if ((iNumVerts && (iFlags & 0x20)) || (iNumPts && (fLineThickness > 0)))
    {
#ifdef _PROBLEND
        if ((PROBLEND)) // sprawdza, czy w nazwie nie ma @    //Q: 13122011 - Szociu: 27012012
        {
            glDisable(GL_BLEND);
            glAlphaFunc(GL_GREATER, 0.45); // im mniejsza wartość, tym większa ramka, domyślnie 0.1f
        };
#endif
        if (!DisplayListID) //||Global::bReCompile) //Ra: wymuszenie rekompilacji
        {
            Compile();
            if (Global::bManageNodes)
                ResourceManager::Register(this);
        };

        // GL_LINE, GL_LINE_STRIP, GL_LINE_LOOP
        if (iNumPts)
        {
            float linealpha = 255000 * fLineThickness / (mgn + 1.0);
            if (linealpha > 255)
                linealpha = 255;
            r = Diffuse[0] * Global::ambientDayLight[0]; // w zaleznosci od koloru swiatla
            g = Diffuse[1] * Global::ambientDayLight[1];
            b = Diffuse[2] * Global::ambientDayLight[2];
            glColor4ub(r, g, b, linealpha); // przezroczystosc dalekiej linii
            glCallList(DisplayListID);
        }
        // GL_TRIANGLE etc
        else
            glCallList(DisplayListID);
        SetLastUsage(Timer::GetSimulationTime());
    };
#ifdef _PROBLEND
    if ((PROBLEND)) // sprawdza, czy w nazwie nie ma @    //Q: 13122011 - Szociu: 27012012
    {
        glEnable(GL_BLEND);
        glAlphaFunc(GL_GREATER, 0.04);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    };
#endif
}

//------------------------------------------------------------------------------
//------------------ Podstawowy pojemnik terenu - sektor -----------------------
//------------------------------------------------------------------------------
TSubRect::TSubRect()
{
    nRootNode = NULL; // lista wszystkich obiektów jest pusta
    nRenderHidden = nRenderRect = nRenderRectAlpha = nRender = nRenderMixed = nRenderAlpha =
        nRenderWires = NULL;
    tTrackAnim = NULL; // nic nie animujemy
    tTracks = NULL; // nie ma jeszcze torów
    nRootMesh = nMeshed = NULL; // te listy też są puste
    iNodeCount = 0; // licznik obiektów
    iTracks = 0; // licznik torów
}
TSubRect::~TSubRect()
{
    if (Global::bManageNodes) // Ra: tu się coś sypie
        ResourceManager::Unregister(this); // wyrejestrowanie ze sprzątacza
    // TODO: usunąć obiekty z listy (nRootMesh), bo są one tworzone dla sektora
}

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
    // nMeshed          - obiekty do pogrupowania wg tekstur
    GLuint t; // pomocniczy kod tekstury
    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
        Node->pTrack->RaOwnerSet(this); // do którego sektora ma zgłaszać animację
        // if (Global::bUseVBO?false:!Node->pTrack->IsGroupable())
        if (Global::bUseVBO ? true :
                              !Node->pTrack->IsGroupable()) // TODO: tymczasowo dla VBO wyłączone
            RaNodeAdd(
                Node); // tory ruchome nie są grupowane przy Display Lists (wymagają odświeżania DL)
        else
        { // tory nieruchome mogą być pogrupowane wg tekstury, przy VBO wszystkie
            Node->TextureID = Node->pTrack->TextureGet(0); // pobranie tekstury do sortowania
            t = Node->pTrack->TextureGet(1);
            if (Node->TextureID) // jeżeli jest pierwsza
            {
                if (t && (Node->TextureID != t))
                { // jeśli są dwie różne tekstury, dodajemy drugi obiekt dla danego toru
                    TGroundNode *n = new TGroundNode();
                    n->iType = TP_DUMMYTRACK; // obiekt renderujący siatki dla tekstury
                    n->TextureID = t;
                    n->pTrack = Node->pTrack; // wskazuje na ten sam tor
                    n->pCenter = Node->pCenter;
                    n->fSquareRadius = Node->fSquareRadius;
                    n->fSquareMinRadius = Node->fSquareMinRadius;
                    n->iFlags = Node->iFlags;
                    n->nNext2 = nRootMesh;
                    nRootMesh = n; // podczepienie do listy, żeby usunąć na końcu
                    n->nNext3 = nMeshed;
                    nMeshed = n;
                }
            }
            else
                Node->TextureID = t; // jest tylko druga tekstura
            if (Node->TextureID)
            {
                Node->nNext3 = nMeshed;
                nMeshed = Node;
            } // do podzielenia potem
        }
        break;
    case GL_TRIANGLE_STRIP:
    case GL_TRIANGLE_FAN:
    case GL_TRIANGLES:
        // Node->nNext3=nMeshed; nMeshed=Node; //do podzielenia potem
        if (Node->iFlags & 0x20) // czy jest przezroczyste?
        {
            Node->nNext3 = nRenderRectAlpha;
            nRenderRectAlpha = Node;
        } // DL: do przezroczystych z sektora
        else if (Global::bUseVBO)
        {
            Node->nNext3 = nRenderRect;
            nRenderRect = Node;
        } // VBO: do nieprzezroczystych z sektora
        else
        {
            Node->nNext3 = nRender;
            nRender = Node;
        } // DL: do nieprzezroczystych wszelakich
        /*
           //Ra: na razie wyłączone do testów VBO
           //if
           ((Node->iType==GL_TRIANGLE_STRIP)||(Node->iType==GL_TRIANGLE_FAN)||(Node->iType==GL_TRIANGLES))
            if (Node->fSquareMinRadius==0.0) //znikające z bliska nie mogą być optymalizowane
             if (Node->fSquareRadius>=160000.0) //tak od 400m to już normalne trójkąty muszą być
             //if (Node->iFlags&0x10) //i nieprzezroczysty
             {if (pTriGroup) //jeżeli był już jakiś grupujący
              {if (pTriGroup->fSquareRadius>Node->fSquareRadius) //i miał większy zasięg
                Node->fSquareRadius=pTriGroup->fSquareRadius; //zwiększenie zakresu widoczności
           grupującego
               pTriGroup->pTriGroup=Node; //poprzedniemu doczepiamy nowy
              }
              Node->pTriGroup=Node; //nowy lider ma się sam wyświetlać - wskaźnik na siebie
              pTriGroup=Node; //zapamiętanie lidera
             }
        */
        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
        // Node->nNext3=nMeshed; //dopisanie do listy sortowania
        // nMeshed=Node;
        break;
    case TP_MEMCELL:
    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
    }
    Node->nNext2 = nRootNode; // dopisanie do ogólnej listy
    nRootNode = Node;
    ++iNodeCount; // licznik obiektów
}

void TSubRect::RaNodeAdd(TGroundNode *Node)
{ // finalna kwalifikacja na listy renderowania, jeśli nie obsługiwane grupowo
    switch (Node->iType)
    {
    case TP_TRACK:
        if (Global::bUseVBO)
        {
            Node->nNext3 = nRenderRect;
            nRenderRect = Node;
        } // VBO: do nieprzezroczystych z sektora
        else
        {
            Node->nNext3 = nRender;
            nRender = Node;
        } // DL: do nieprzezroczystych
        break;
    case GL_TRIANGLE_STRIP:
    case GL_TRIANGLE_FAN:
    case GL_TRIANGLES:
        if (Node->iFlags & 0x20) // czy jest przezroczyste?
        {
            Node->nNext3 = nRenderRectAlpha;
            nRenderRectAlpha = Node;
        } // DL: do przezroczystych z sektora
        else if (Global::bUseVBO)
        {
            Node->nNext3 = nRenderRect;
            nRenderRect = Node;
        } // VBO: do nieprzezroczystych z sektora
        else
        {
            Node->nNext3 = nRender;
            nRender = Node;
        } // DL: do nieprzezroczystych wszelakich
        break;
    case TP_MODEL: // modele zawsze wyświetlane z własnego VBO
        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;
    case TP_MESH: // grupa ze wspólną teksturą
        //{Node->nNext3=nRenderRect; nRenderRect=Node;} //do nieprzezroczystych z sektora
        {
            Node->nNext3 = nRender;
            nRender = Node;
        } // do nieprzezroczystych
        break;
    case TP_SUBMODEL: // submodele terenu w kwadracie kilometrowym idą do nRootMesh
        // WriteLog("nRootMesh was "+AnsiString(nRootMesh?"not null ":"null
        // ")+IntToHex(int(this),8));
        Node->nNext3 = nRootMesh; // przy VBO musi być inaczej
        nRootMesh = Node;
        break;
    }
}

void TSubRect::Sort()
{ // przygotowanie sektora do renderowania
    TGroundNode **n0, *n1, *n2; // wskaźniki robocze
    delete[] tTracks; // usunięcie listy
    tTracks =
        iTracks ? new TTrack *[iTracks] : NULL; // tworzenie tabeli torów do renderowania pojazdów
    if (tTracks)
    { // wypełnianie tabeli torów
        int i = 0;
        for (n1 = nRootNode; n1; n1 = n1->nNext2) // kolejne obiekty z sektora
            if (n1->iType == TP_TRACK)
                tTracks[i++] = n1->pTrack;
    }
    // sortowanie obiektów w sektorze na listy renderowania
    if (!nMeshed)
        return; // nie ma nic do sortowania
    bool sorted = false;
    while (!sorted)
    { // sortowanie bąbelkowe obiektów wg tekstury
        sorted = true; // zakładamy posortowanie
        n0 = &nMeshed; // wskaźnik niezbędny do zamieniania obiektów
        n1 = nMeshed; // lista obiektów przetwarzanych na statyczne siatki
        while (n1)
        { // sprawdzanie stanu posortowania obiektów i ewentualne zamiany
            n2 = n1->nNext3; // kolejny z tej listy
            if (n2) // jeśli istnieje
                if (n1->TextureID > n2->TextureID)
                { // zamiana elementów miejscami
                    *n0 = n2; // drugi będzie na początku
                    n1->nNext3 = n2->nNext3; // ten zza drugiego będzie za pierwszym
                    n2->nNext3 = n1; // a za drugim będzie pierwszy
                    sorted = false; // potrzebny kolejny przebieg
                }
            n0 = &(n1->nNext3);
            n1 = n2;
        };
    }
    // wyrzucenie z listy obiektów pojedynczych (nie ma z czym ich grupować)
    // nawet jak są pojedyncze, to i tak lepiej, aby były w jednym Display List
    /*
        else
        {//dodanie do zwykłej listy renderowania i usunięcie z grupowego
         *n0=n2; //drugi będzie na początku
         RaNodeAdd(n1); //nie ma go z czym zgrupować; (n1->nNext3) zostanie nadpisane
         n1=n2; //potrzebne do ustawienia (n0)
        }
    */
    //...
    // przeglądanie listy i tworzenie obiektów renderujących dla danej tekstury
    GLuint t = 0; // pomocniczy kod tekstury
    n1 = nMeshed; // lista obiektów przetwarzanych na statyczne siatki
    while (n1)
    { // dla każdej tekstury powinny istnieć co najmniej dwa obiekty, ale dla DL nie ma to znaczenia
        if (t < n1->TextureID) // jeśli (n1) ma inną teksturę niż poprzednie
        { // można zrobić obiekt renderujący
            t = n1->TextureID;
            n2 = new TGroundNode();
            n2->nNext2 = nRootMesh;
            nRootMesh = n2; // podczepienie na początku listy
            nRootMesh->iType = TP_MESH; // obiekt renderujący siatki dla tekstury
            nRootMesh->TextureID = t;
            nRootMesh->nNode = n1; // pierwszy element z listy
            nRootMesh->pCenter = n1->pCenter;
            nRootMesh->fSquareRadius = 1e8; // widać bez ograniczeń
            nRootMesh->fSquareMinRadius = 0.0;
            nRootMesh->iFlags = 0x10;
            RaNodeAdd(nRootMesh); // dodanie do odpowiedniej listy renderowania
        }
        n1 = n1->nNext3; // kolejny z tej listy
    };
}

TTrack * TSubRect::FindTrack(vector3 *Point, int &iConnection, TTrack *Exclude)
{ // szukanie toru, którego koniec jest najbliższy (*Point)
    TTrack *Track;
    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
        }
    /*
     TGroundNode *Current;
     for (Current=nRootNode;Current;Current=Current->Next)
      if ((Current->iType==TP_TRACK)&&(Current->pTrack!=Exclude)) //można użyć tabelę torów
       {
        iConnection=Current->pTrack->TestPoint(Point);
        if (iConnection>=0) return Current;
       }
    */
    return NULL;
};

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

void TSubRect::RaAnimate()
{ // wykonanie animacji
    if (!tTrackAnim)
        return; // nie ma nic do animowania
    if (Global::bUseVBO)
    { // odświeżenie VBO sektora
        if (Global::bOpenGL_1_5) // modyfikacje VBO są dostępne od OpenGL 1.5
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nVBOVertices);
        else // dla OpenGL 1.4 z GL_ARB_vertex_buffer_object odświeżenie całego sektora
            Release(); // opróżnienie VBO sektora, aby się odświeżył z nowymi ustawieniami
    }
    tTrackAnim = tTrackAnim->RaAnimate(); // przeliczenie animacji kolejnego
};

TTraction * TSubRect::FindTraction(vector3 *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;
};

void TSubRect::LoadNodes()
{ // utworzenie siatek VBO dla wszystkich node w sektorze
    if (m_nVertexCount >= 0)
        return; // obiekty były już sprawdzone
    m_nVertexCount = 0; //-1 oznacza, że nie sprawdzono listy obiektów
    if (!nRootNode)
        return;
    TGroundNode *n = nRootNode;
    while (n)
    {
        switch (n->iType)
        {
        case GL_TRIANGLE_STRIP:
        case GL_TRIANGLE_FAN:
        case GL_TRIANGLES:
            n->iVboPtr = m_nVertexCount; // nowy początek
            m_nVertexCount += n->iNumVerts;
            break;
        case GL_LINES:
        case GL_LINE_STRIP:
        case GL_LINE_LOOP:
            n->iVboPtr = m_nVertexCount; // nowy początek
            m_nVertexCount +=
                n->iNumPts; // miejsce w tablicach normalnych i teksturowania się zmarnuje...
            break;
        case TP_TRACK:
            n->iVboPtr = m_nVertexCount; // nowy początek
            n->iNumVerts = n->pTrack->RaArrayPrepare(); // zliczenie wierzchołków
            m_nVertexCount += n->iNumVerts;
            break;
        case TP_TRACTION:
            n->iVboPtr = m_nVertexCount; // nowy początek
            n->iNumVerts = n->hvTraction->RaArrayPrepare(); // zliczenie wierzchołków
            m_nVertexCount += n->iNumVerts;
            break;
        }
        n = n->nNext2; // następny z sektora
    }
    if (!m_nVertexCount)
        return; // jeśli nie ma obiektów do wyświetlenia z VBO, to koniec
    if (Global::bUseVBO)
    { // tylko liczenie wierzchołów, gdy nie ma VBO
        MakeArray(m_nVertexCount);
        n = nRootNode;
        int i;
        while (n)
        {
            if (n->iVboPtr >= 0)
                switch (n->iType)
                {
                case GL_TRIANGLE_STRIP:
                case GL_TRIANGLE_FAN:
                case GL_TRIANGLES:
                    for (i = 0; i < n->iNumVerts; ++i)
                    { // Ra: trójkąty można od razu wczytywać do takich tablic... to może poczekać
                        m_pVNT[n->iVboPtr + i].x = n->Vertices[i].Point.x;
                        m_pVNT[n->iVboPtr + i].y = n->Vertices[i].Point.y;
                        m_pVNT[n->iVboPtr + i].z = n->Vertices[i].Point.z;
                        m_pVNT[n->iVboPtr + i].nx = n->Vertices[i].Normal.x;
                        m_pVNT[n->iVboPtr + i].ny = n->Vertices[i].Normal.y;
                        m_pVNT[n->iVboPtr + i].nz = n->Vertices[i].Normal.z;
                        m_pVNT[n->iVboPtr + i].u = n->Vertices[i].tu;
                        m_pVNT[n->iVboPtr + i].v = n->Vertices[i].tv;
                    }
                    break;
                case GL_LINES:
                case GL_LINE_STRIP:
                case GL_LINE_LOOP:
                    for (i = 0; i < n->iNumPts; ++i)
                    {
                        m_pVNT[n->iVboPtr + i].x = n->Points[i].x;
                        m_pVNT[n->iVboPtr + i].y = n->Points[i].y;
                        m_pVNT[n->iVboPtr + i].z = n->Points[i].z;
                        // miejsce w tablicach normalnych i teksturowania się marnuje...
                    }
                    break;
                case TP_TRACK:
                    if (n->iNumVerts) // bo tory zabezpieczające są niewidoczne
                        n->pTrack->RaArrayFill(m_pVNT + n->iVboPtr, m_pVNT);
                    break;
                case TP_TRACTION:
                    if (n->iNumVerts) // druty mogą być niewidoczne...?
                        n->hvTraction->RaArrayFill(m_pVNT + n->iVboPtr);
                    break;
                }
            n = n->nNext2; // następny z sektora
        }
        BuildVBOs();
    }
    if (Global::bManageNodes)
        ResourceManager::Register(this); // dodanie do automatu zwalniającego pamięć
}

bool TSubRect::StartVBO()
{ // początek rysowania elementów z VBO w sektorze
    SetLastUsage(Timer::GetSimulationTime()); // te z tyłu będą niepotrzebnie zwalniane
    return CMesh::StartVBO();
};

void TSubRect::Release()
{ // wirtualne zwolnienie zasobów przez sprzątacz albo destruktor
    if (Global::bUseVBO)
        CMesh::Clear(); // usuwanie buforów
};

void TSubRect::RenderDL()
{ // renderowanie nieprzezroczystych (DL)
    TGroundNode *node;
    RaAnimate(); // przeliczenia animacji torów w sektorze
    for (node = nRender; node; node = node->nNext3)
        node->RenderDL(); // nieprzezroczyste obiekty (oprócz pojazdów)
    for (node = nRenderMixed; node; node = node->nNext3)
        node->RenderDL(); // nieprzezroczyste z mieszanych modeli
    for (int j = 0; j < iTracks; ++j)
        tTracks[j]->RenderDyn(); // nieprzezroczyste fragmenty pojazdów na torach
};

void TSubRect::RenderAlphaDL()
{ // renderowanie przezroczystych modeli oraz pojazdów (DL)
    TGroundNode *node;
    for (node = nRenderMixed; node; node = node->nNext3)
        node->RenderAlphaDL(); // przezroczyste z mieszanych modeli
    for (node = nRenderAlpha; node; node = node->nNext3)
        node->RenderAlphaDL(); // przezroczyste modele
    // for (node=tmp->nRender;node;node=node->nNext3)
    // if (node->iType==TP_TRACK)
    //  node->pTrack->RenderAlpha(); //przezroczyste fragmenty pojazdów na torach
    for (int j = 0; j < iTracks; ++j)
        tTracks[j]->RenderDynAlpha(); // przezroczyste fragmenty pojazdów na torach
};

void TSubRect::RenderVBO()
{ // renderowanie nieprzezroczystych (VBO)
    TGroundNode *node;
    RaAnimate(); // przeliczenia animacji torów w sektorze
    LoadNodes(); // czemu tutaj?
    if (StartVBO())
    {
        for (node = nRenderRect; node; node = node->nNext3)
            if (node->iVboPtr >= 0)
                node->RenderVBO(); // nieprzezroczyste obiekty terenu
        EndVBO();
    }
    for (node = nRender; node; node = node->nNext3)
        node->RenderVBO(); // nieprzezroczyste obiekty (oprócz pojazdów)
    for (node = nRenderMixed; node; node = node->nNext3)
        node->RenderVBO(); // nieprzezroczyste z mieszanych modeli
    for (int j = 0; j < iTracks; ++j)
        tTracks[j]->RenderDyn(); // nieprzezroczyste fragmenty pojazdów na torach
};

void TSubRect::RenderAlphaVBO()
{ // renderowanie przezroczystych modeli oraz pojazdów (VBO)
    TGroundNode *node;
    for (node = nRenderMixed; node; node = node->nNext3)
        node->RenderAlphaVBO(); // przezroczyste z mieszanych modeli
    for (node = nRenderAlpha; node; node = node->nNext3)
        node->RenderAlphaVBO(); // przezroczyste modele
    // for (node=tmp->nRender;node;node=node->nNext3)
    // if (node->iType==TP_TRACK)
    //  node->pTrack->RenderAlpha(); //przezroczyste fragmenty pojazdów na torach
    for (int j = 0; j < iTracks; ++j)
        tTracks[j]->RenderDynAlpha(); // przezroczyste fragmenty pojazdów na torach
};

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
};
//---------------------------------------------------------------------------
//------------------ Kwadrat kilometrowy ------------------------------------
//---------------------------------------------------------------------------
int TGroundRect::iFrameNumber = 0; // licznik wyświetlanych klatek

TGroundRect::TGroundRect()
{
    pSubRects = NULL;
    nTerrain = NULL;
};

TGroundRect::~TGroundRect()
{
    SafeDeleteArray(pSubRects);
};

void TGroundRect::RenderDL()
{ // renderowanie kwadratu kilometrowego (DL), jeśli jeszcze nie zrobione
    if (iLastDisplay != iFrameNumber)
    { // tylko jezeli dany kwadrat nie był jeszcze renderowany
        // for (TGroundNode* node=pRender;node;node=node->pNext3)
        // node->Render(); //nieprzezroczyste trójkąty kwadratu kilometrowego
        if (nRender)
        { //łączenie trójkątów w jedną listę - trochę wioska
            if (!nRender->DisplayListID || (nRender->iVersion != Global::iReCompile))
            { // jeżeli nie skompilowany, kompilujemy wszystkie trójkąty w jeden
                nRender->fSquareRadius = 5000.0 * 5000.0; // aby agregat nigdy nie znikał
                nRender->DisplayListID = glGenLists(1);
                glNewList(nRender->DisplayListID, GL_COMPILE);
                nRender->iVersion = Global::iReCompile; // aktualna wersja siatek
                for (TGroundNode *node = nRender; node; node = node->nNext3) // następny tej grupy
                    node->Compile(true);
                glEndList();
            }
            nRender->RenderDL(); // nieprzezroczyste trójkąty kwadratu kilometrowego
        }
        if (nRootMesh)
            nRootMesh->RenderDL();
        iLastDisplay = iFrameNumber; // drugi raz nie potrzeba
    }
};

void TGroundRect::RenderVBO()
{ // renderowanie kwadratu kilometrowego (VBO), jeśli jeszcze nie zrobione
    if (iLastDisplay != iFrameNumber)
    { // tylko jezeli dany kwadrat nie był jeszcze renderowany
        LoadNodes(); // ewentualne tworzenie siatek
        if (StartVBO())
        {
            for (TGroundNode *node = nRenderRect; node; node = node->nNext3) // następny tej grupy
                node->RaRenderVBO(); // nieprzezroczyste trójkąty kwadratu kilometrowego
            EndVBO();
            iLastDisplay = iFrameNumber;
        }
        if (nTerrain)
            nTerrain->smTerrain->iVisible = iFrameNumber; // ma się wyświetlić w tej ramce
    }
};

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

void TGround::MoveGroundNode(vector3 pPosition)
{ // Ra: to wymaga gruntownej reformy
    /*
     TGroundNode *Current;
     for (Current=RootNode;Current!=NULL;Current=Current->Next)
      Current->MoveMe(pPosition);

     TGroundRect *Rectx=new TGroundRect; //kwadrat kilometrowy
     for(int i=0;i<iNumRects;i++)
      for(int j=0;j<iNumRects;j++)
       Rects[i][j]=*Rectx; //kopiowanie zawartości do każdego kwadratu
     delete Rectx;
     for (Current=RootNode;Current!=NULL;Current=Current->Next)
     {//rozłożenie obiektów na mapie
      if (Current->iType!=TP_DYNAMIC)
      {//pojazdów to w ogóle nie dotyczy
       if ((Current->iType!=GL_TRIANGLES)&&(Current->iType!=GL_TRIANGLE_STRIP)?true //~czy trójkąt?
        :(Current->iFlags&0x20)?true //~czy teksturę ma nieprzezroczystą?
         //:(Current->iNumVerts!=3)?true //~czy tylko jeden trójkąt?
         :(Current->fSquareMinRadius!=0.0)?true //~czy widoczny z bliska?
          :(Current->fSquareRadius<=90000.0)) //~czy widoczny z daleka?
        GetSubRect(Current->pCenter.x,Current->pCenter.z)->AddNode(Current);
       else //dodajemy do kwadratu kilometrowego
        GetRect(Current->pCenter.x,Current->pCenter.z)->AddNode(Current);
      }
     }
     for (Current=RootDynamic;Current!=NULL;Current=Current->Next)
     {
      Current->pCenter+=pPosition;
      Current->DynamicObject->UpdatePos();
     }
     for (Current=RootDynamic;Current!=NULL;Current=Current->Next)
      Current->DynamicObject->MoverParameters->Physic_ReActivation();
    */
}

TGround::TGround()
{
    // RootNode=NULL;
    nRootDynamic = NULL;
    QueryRootEvent = NULL;
    tmpEvent = NULL;
    tmp2Event = NULL;
    OldQRE = NULL;
    RootEvent = NULL;
    iNumNodes = 0;
    // pTrain=NULL;
    Global::pGround = this;
    bInitDone = false; // Ra: żeby nie robiło dwa razy FirstInit
    for (int i = 0; i < TP_LAST; i++)
        nRootOfType[i] = NULL; // zerowanie tablic wyszukiwania
    bDynamicRemove = false; // na razie nic do usunięcia
    sTracks = new TNames(); // nazwy torów - na razie tak
}

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

void TGround::Free()
{
    TEvent *tmp;
    for (TEvent *Current = RootEvent; Current;)
    {
        tmp = Current;
        Current = Current->evNext2;
        delete tmp;
    }
    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;
    }
    iNumNodes = 0;
    // RootNode=NULL;
    nRootDynamic = NULL;
    delete sTracks;
}

TGroundNode * TGround::DynamicFindAny(AnsiString asNameToFind)
{ // wyszukanie pojazdu o podanej nazwie, szukanie po wszystkich (użyć drzewa!)
    for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
        if ((Current->asName == asNameToFind))
            return Current;
    return NULL;
};

TGroundNode * TGround::DynamicFind(AnsiString asNameToFind)
{ // 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 == asNameToFind))
                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)
            WyslijString(Current->asName, 6); // same nazwy pojazdów
    WyslijString("none", 6); // informacja o końcu listy
};

TGroundNode * TGround::FindGroundNode(AnsiString asNameToFind, TGroundNodeType iNodeType)
{ // wyszukiwanie obiektu o podanej nazwie i konkretnym typie
    if ((iNodeType == TP_TRACK) || (iNodeType == TP_MEMCELL) || (iNodeType == TP_MODEL))
    { // wyszukiwanie w drzewie binarnym
        return (TGroundNode *)sTracks->Find(iNodeType, asNameToFind.c_str());
    }
    // standardowe wyszukiwanie liniowe
    TGroundNode *Current;
    for (Current = nRootOfType[iNodeType]; Current; Current = Current->nNext)
        if (Current->asName == asNameToFind)
            return Current;
    return NULL;
}

double fTrainSetVel = 0;
double fTrainSetDir = 0;
double fTrainSetDist = 0; // odległość składu od punktu 1 w stronę punktu 2
AnsiString asTrainSetTrack = "";
int iTrainSetConnection = 0;
bool bTrainSet = false;
AnsiString asTrainName = "";
int iTrainSetWehicleNumber = 0;
TGroundNode *nTrainSetNode = NULL; // poprzedni pojazd do łączenia
TGroundNode *nTrainSetDriver = NULL; // pojazd, któremu zostanie wysłany rozkład

TGroundVertex TempVerts[10000]; // tu wczytywane są trójkąty
Byte TempConnectionType[200]; // Ra: sprzęgi w składzie; ujemne, gdy odwrotnie

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 * floor(0.001 * node->pCenter.x) - 200.0;
    double x1 = x0 + 1400.0;
    double z0 = 1000.0 * floor(0.001 * node->pCenter.z) - 200.0;
    double z1 = z0 + 1400.0;
    if ((node->Vertices[0].Point.x >= x0) && (node->Vertices[0].Point.x <= x1) &&
        (node->Vertices[0].Point.z >= z0) && (node->Vertices[0].Point.z <= z1) &&
        (node->Vertices[1].Point.x >= x0) && (node->Vertices[1].Point.x <= x1) &&
        (node->Vertices[1].Point.z >= z0) && (node->Vertices[1].Point.z <= z1) &&
        (node->Vertices[2].Point.x >= x0) && (node->Vertices[2].Point.x <= x1) &&
        (node->Vertices[2].Point.z >= z0) && (node->Vertices[2].Point.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->Vertices[0].Point.x - x0) * (node->Vertices[1].Point.x - x0); // AB na wschodzie
    if (mul < min)
        min = mul, divide = 0;
    mul = (node->Vertices[1].Point.x - x0) * (node->Vertices[2].Point.x - x0); // BC na wschodzie
    if (mul < min)
        min = mul, divide = 1;
    mul = (node->Vertices[2].Point.x - x0) * (node->Vertices[0].Point.x - x0); // CA na wschodzie
    if (mul < min)
        min = mul, divide = 2;
    mul = (node->Vertices[0].Point.x - x1) * (node->Vertices[1].Point.x - x1); // AB na zachodzie
    if (mul < min)
        min = mul, divide = 8;
    mul = (node->Vertices[1].Point.x - x1) * (node->Vertices[2].Point.x - x1); // BC na zachodzie
    if (mul < min)
        min = mul, divide = 9;
    mul = (node->Vertices[2].Point.x - x1) * (node->Vertices[0].Point.x - x1); // CA na zachodzie
    if (mul < min)
        min = mul, divide = 10;
    mul = (node->Vertices[0].Point.z - z0) * (node->Vertices[1].Point.z - z0); // AB na południu
    if (mul < min)
        min = mul, divide = 4;
    mul = (node->Vertices[1].Point.z - z0) * (node->Vertices[2].Point.z - z0); // BC na południu
    if (mul < min)
        min = mul, divide = 5;
    mul = (node->Vertices[2].Point.z - z0) * (node->Vertices[0].Point.z - z0); // CA na południu
    if (mul < min)
        min = mul, divide = 6;
    mul = (node->Vertices[0].Point.z - z1) * (node->Vertices[1].Point.z - z1); // AB na północy
    if (mul < min)
        min = mul, divide = 12;
    mul = (node->Vertices[1].Point.z - z1) * (node->Vertices[2].Point.z - z1); // BC na północy
    if (mul < min)
        min = mul, divide = 13;
    mul = (node->Vertices[2].Point.z - z1) * (node->Vertices[0].Point.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(); // a ten jest nowy
    ntri->iType = GL_TRIANGLES; // kopiowanie parametrów, przydałby się konstruktor kopiujący
    ntri->Init(3);
    ntri->TextureID = node->TextureID;
    ntri->iFlags = node->iFlags;
    for (int j = 0; j < 4; ++j)
    {
        ntri->Ambient[j] = node->Ambient[j];
        ntri->Diffuse[j] = node->Diffuse[j];
        ntri->Specular[j] = node->Specular[j];
    }
    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
    iNumNodes++;
    switch (divide & 3)
    { // podzielenie jednego z boków, powstaje wierzchołek D
    case 0: // podział AB (0-1) -> ADC i DBC
        ntri->Vertices[2] = node->Vertices[2]; // wierzchołek C jest wspólny
        ntri->Vertices[1] = node->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->Vertices[1].SetByZ(node->Vertices[0], node->Vertices[1], divide & 8 ? z1 : z0);
        else
            node->Vertices[1].SetByX(node->Vertices[0], node->Vertices[1], divide & 8 ? x1 : x0);
        ntri->Vertices[0] = node->Vertices[1]; // wierzchołek D jest wspólny
        break;
    case 1: // podział BC (1-2) -> ABD i ADC
        ntri->Vertices[0] = node->Vertices[0]; // wierzchołek A jest wspólny
        ntri->Vertices[2] = node->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->Vertices[2].SetByZ(node->Vertices[1], node->Vertices[2], divide & 8 ? z1 : z0);
        else
            node->Vertices[2].SetByX(node->Vertices[1], node->Vertices[2], divide & 8 ? x1 : x0);
        ntri->Vertices[1] = node->Vertices[2]; // wierzchołek D jest wspólny
        break;
    case 2: // podział CA (2-0) -> ABD i DBC
        ntri->Vertices[1] = node->Vertices[1]; // wierzchołek B jest wspólny
        ntri->Vertices[2] = node->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->Vertices[2].SetByZ(node->Vertices[2], node->Vertices[0], divide & 8 ? z1 : z0);
        else
            node->Vertices[2].SetByX(node->Vertices[2], node->Vertices[0], divide & 8 ? x1 : x0);
        ntri->Vertices[0] = node->Vertices[2]; // wierzchołek D jest wspólny
        break;
    }
    // przeliczenie środków ciężkości obu
    node->pCenter =
        (node->Vertices[0].Point + node->Vertices[1].Point + node->Vertices[2].Point) / 3.0;
    ntri->pCenter =
        (ntri->Vertices[0].Point + ntri->Vertices[1].Point + ntri->Vertices[2].Point) / 3.0;
    RaTriangleDivider(node); // rekurencja, bo nawet na TD raz nie wystarczy
    RaTriangleDivider(ntri);
};

TGroundNode * TGround::AddGroundNode(cParser *parser)
{ // wczytanie wpisu typu "node"
    // parser->LoadTraction=Global::bLoadTraction; //Ra: tu nie potrzeba powtarzać
    AnsiString str, str1, str2, str3, str4, Skin, DriverType, asNodeName;
    int nv, ti, i, n;
    double tf, r, rmin, tf1, tf2, tf3, tf4, l, dist, mgn;
    int int1, int2;
    bool bError = false, curve;
    vector3 pt, front, up, left, pos, tv;
    matrix4x4 mat2, mat1, mat;
    GLuint TexID;
    TGroundNode *tmp1;
    TTrack *Track;
    TTextSound *tmpsound;
    std::string token;
    parser->getTokens(2);
    *parser >> r >> rmin;
    parser->getTokens();
    *parser >> token;
    asNodeName = AnsiString(token.c_str());
    parser->getTokens();
    *parser >> token;
    str = AnsiString(token.c_str());
    TGroundNode *tmp, *tmp2;
    tmp = new TGroundNode();
    tmp->asName = (asNodeName == AnsiString("none") ? AnsiString("") : 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(AnsiString("Scene parse error near " + str).c_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.c_str());
        }
        // if (tmp==RootNode) RootNode=NULL;
        delete tmp;
        return NULL;
    }
    switch (tmp->iType)
    {
    case TP_TRACTION:
        tmp->hvTraction = new TTraction();
        parser->getTokens();
        *parser >> token;
        tmp->hvTraction->asPowerSupplyName = AnsiString(token.c_str()); // nazwa zasilacza
        parser->getTokens(3);
        *parser >> tmp->hvTraction->NominalVoltage >> tmp->hvTraction->MaxCurrent >>
            tmp->hvTraction->fResistivity;
        if (tmp->hvTraction->fResistivity == 0.01) // tyle jest w sceneriach [om/km]
            tmp->hvTraction->fResistivity = 0.075; // taka sensowniejsza wartość za
        // http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf
        tmp->hvTraction->fResistivity *= 0.001; // 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 += pOrigin;
        parser->getTokens(3);
        *parser >> tmp->hvTraction->pPoint2.x >> tmp->hvTraction->pPoint2.y >>
            tmp->hvTraction->pPoint2.z;
        tmp->hvTraction->pPoint2 += pOrigin;
        parser->getTokens(3);
        *parser >> tmp->hvTraction->pPoint3.x >> tmp->hvTraction->pPoint3.y >>
            tmp->hvTraction->pPoint3.z;
        tmp->hvTraction->pPoint3 += pOrigin;
        parser->getTokens(3);
        *parser >> tmp->hvTraction->pPoint4.x >> tmp->hvTraction->pPoint4.y >>
            tmp->hvTraction->pPoint4.z;
        tmp->hvTraction->pPoint4 += 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 =
                (tmp->hvTraction->pPoint1 - tmp->hvTraction->pPoint2).Length() / 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 = AnsiString(token.c_str());
            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
        // if (Global::bLoadTraction)
        // tmp->hvTraction->Optimize(); //generowanie DL dla wszystkiego przy wczytywaniu?
        tmp->pCenter = (tmp->hvTraction->pPoint2 + tmp->hvTraction->pPoint1) * 0.5f;
        // if (!Global::bLoadTraction) SafeDelete(tmp); //Ra: tak być nie może, bo NULL to błąd
        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);
        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.IsEmpty()) // jest pusta gdy "none"
        { // dodanie do wyszukiwarki
            if (sTracks->Update(TP_MEMCELL, tmp->asName.c_str(),
                                tmp)) // najpierw sprawdzić, czy już jest
            { // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność
                // wsteczna)
                ErrorLog("Duplicated memcell: " + tmp->asName); // to zgłaszać duplikat
            }
            else
                sTracks->Add(TP_MEMCELL, tmp->asName.c_str(), tmp); // nazwa jest unikalna
        }
        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);
        if (Global::iWriteLogEnabled & 4)
            if (!tmp->asName.IsEmpty())
                WriteLog(tmp->asName.c_str());
        tmp->pTrack->Load(parser, pOrigin,
                          tmp->asName); // w nazwie może być nazwa odcinka izolowanego
        if (!tmp->asName.IsEmpty()) // jest pusta gdy "none"
        { // dodanie do wyszukiwarki
            if (sTracks->Update(TP_TRACK, tmp->asName.c_str(),
                                tmp)) // najpierw sprawdzić, czy już jest
            { // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność
                // wsteczna)
                if (tmp->pTrack->iCategoryFlag & 1) // jeśli jest zdublowany tor kolejowy
                    ErrorLog("Duplicated track: " + tmp->asName); // to zgłaszać duplikat
            }
            else
                sTracks->Add(TP_TRACK, tmp->asName.c_str(), tmp); // nazwa jest unikalna
        }
        tmp->pCenter = (tmp->pTrack->CurrentSegment()->FastGetPoint_0() +
                        tmp->pTrack->CurrentSegment()->FastGetPoint(0.5) +
                        tmp->pTrack->CurrentSegment()->FastGetPoint_1()) /
                       3.0;
        break;
    case TP_SOUND:
        tmp->tsStaticSound = new TTextSound;
        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 = AnsiString(token.c_str());
        tmp->tsStaticSound->Init(str.c_str(), sqrt(tmp->fSquareRadius), tmp->pCenter.x,
                                 tmp->pCenter.y, tmp->pCenter.z, 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

        //            tmp->pDirectSoundBuffer=TSoundsManager::GetFromName(str.c_str());
        //            tmp->iState=(Parser->GetNextSymbol().LowerCase()=="loop"?DSBPLAY_LOOPING:0);
        parser->getTokens();
        *parser >> token;
        break;
    case TP_DYNAMIC:
        tmp->DynamicObject = new TDynamicObject();
        // tmp->DynamicObject->Load(Parser);
        parser->getTokens();
        *parser >> token;
        str1 = AnsiString(token.c_str()); // katalog
        // McZapkie: doszedl parametr ze zmienialna skora
        parser->getTokens();
        *parser >> token;
        Skin = AnsiString(token.c_str()); // tekstura wymienna
        parser->getTokens();
        *parser >> token;
        str3 = AnsiString(token.c_str()); // McZapkie-131102: model w MMD
        if (bTrainSet)
        { // jeśli pojazd jest umieszczony w składzie
            str = asTrainSetTrack;
            parser->getTokens();
            *parser >> tf1; // Ra: -1 oznacza odwrotne wstawienie, normalnie w składzie 0
            parser->getTokens();
            *parser >> token;
            DriverType = AnsiString(token.c_str()); // McZapkie:010303 - w przyszlosci rozne
            // konfiguracje mechanik/pomocnik itp
            tf3 = fTrainSetVel; // prędkość
            parser->getTokens();
            *parser >> token;
            str4 = AnsiString(token.c_str());
            int2 = str4.Pos("."); // yB: wykorzystuje tutaj zmienna, ktora potem bedzie ladunkiem
            if (int2 > 0) // yB: jesli znalazl kropke, to ja przetwarza jako parametry
            {
                int dlugosc = str4.Length();
                int1 = str4.SubString(1, int2 - 1).ToInt(); // niech sprzegiem bedzie do kropki cos
                str4 = str4.SubString(int2 + 1, dlugosc - int2);
            }
            else
            {
                int1 = str4.ToInt();
                str4 = "";
            }
            int2 = 0; // zeruje po wykorzystaniu
            //    *parser >> int1; //yB: nastawy i takie tam TUTAJ!!!!!
            if (int1 < 0)
                int1 = (-int1) |
                       ctrain_depot; // sprzęg zablokowany (pojazdy nierozłączalne przy manewrach)
            if (tf1 != -1.0)
                if (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();
            *parser >> token;
            str = AnsiString(token.c_str()); // track
            parser->getTokens();
            *parser >> tf1; // Ra: -1 oznacza odwrotne wstawienie
            parser->getTokens();
            *parser >> token;
            DriverType = AnsiString(token.c_str()); // McZapkie:010303: obsada
            parser->getTokens();
            *parser >>
                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 >> token;
            str2 = AnsiString(token.c_str()); // LoadType
            if (str2 == AnsiString("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
            // WriteLog("Dynamic shift: "+AnsiString(fTrainSetDist));
            /* //Ra: to jednak robi duże problemy - przesunięcie w dynamic jest przesunięciem do
               tyłu, odwrotnie niż w trainset
                if (!iTrainSetWehicleNumber) //dla pierwszego jest to przesunięcie (ujemne = do
               tyłu)
                 if (tf1!=-1.0) //-1 wyjątkowo oznacza odwrócenie
                  tf1=-tf1; //a dla kolejnych odległość między sprzęgami (ujemne = wbite)
            */
            tf3 = tmp->DynamicObject->Init(asNodeName, str1, Skin, str3, Track,
                                           (tf1 == -1.0 ? fTrainSetDist : fTrainSetDist - tf1),
                                           DriverType, tf3, asTrainName, int2, str2, (tf1 == -1.0),
                                           str4);
            if (tf3 != 0.0) // zero oznacza błąd
            {
                fTrainSetDist -=
                    tf3; // przesunięcie dla kolejnego, minus bo idziemy w stronę punktu 1
                tmp->pCenter = tmp->DynamicObject->GetPosition();
                if (TempConnectionType[iTrainSetWehicleNumber]) // jeśli jest sprzęg
                    if (tmp->DynamicObject->MoverParameters->Couplers[tf1 == -1.0 ? 0 : 1]
                            .AllowedFlag &
                        ctrain_depot) // jesli zablokowany
                        TempConnectionType[iTrainSetWehicleNumber] |= ctrain_depot; // będzie
                // blokada
                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 = AnsiString(token.c_str());
            *parser >> token;
        }
        if (token.compare("enddynamic") != 0)
            Error("enddynamic statement missing");
        break;
    case TP_MODEL:
        if (rmin < 0)
        {
            tmp->iType = TP_TERRAIN;
            tmp->fSquareMinRadius = 0; // to w ogóle potrzebne?
        }
        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(aRotate.y / 180.0 * M_PI);
        // McZapkie-260402: model tez ma wspolrzedne wzgledne
        tmp->pCenter += pOrigin;
        // tmp->fAngle+=aRotate.y; // /180*M_PI
        /*
           if (tmp->iType==TP_MODEL)
           {//jeśli standardowy model
        */
        tmp->Model = new TAnimModel();
        tmp->Model->RaAnglesSet(aRotate.x, tf1 + aRotate.y,
                                aRotate.z); // dostosowanie do pochylania linii
        if (tmp->Model->Load(
                parser, tmp->iType == TP_TERRAIN)) // wczytanie modelu, tekstury i stanu świateł...
            tmp->iFlags =
                tmp->Model->Flags() | 0x200; // ustalenie, czy przezroczysty; flaga usuwania
        else if (tmp->iType != TP_TERRAIN)
        { // model nie wczytał się - ignorowanie node
            delete tmp;
            tmp = NULL; // nie może być tu return
            break; // nie może być tu return?
        }
        /*
           }
           else if (tmp->iType==TP_TERRAIN)
           {//nie potrzeba nakładki animującej submodele
            *parser >> token;
            tmp->pModel3D=TModelsManager::GetModel(token.c_str(),false);
            do //Ra: z tym to trochę bez sensu jest
            {parser->getTokens();
             *parser >> token;
             str=AnsiString(token.c_str());
            } while (str!="endterrains");
           }
        */
        if (tmp->iType == TP_TERRAIN)
        { // jeśli model jest terenem, trzeba utworzyć dodatkowe obiekty
            // po wczytaniu model ma już utworzone DL albo VBO
            Global::pTerrainCompact = tmp->Model; // istnieje co najmniej jeden obiekt terenu
            tmp->iCount = Global::pTerrainCompact->TerrainCount() + 1; // zliczenie submodeli
            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 (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
                // tmp->nNode[i].asName=
            }
        }
        else if (!tmp->asName.IsEmpty()) // jest pusta gdy "none"
        { // dodanie do wyszukiwarki
            if (sTracks->Update(TP_MODEL, tmp->asName.c_str(),
                                tmp)) // najpierw sprawdzić, czy już jest
            { // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność
                // wsteczna)
                ErrorLog("Duplicated model: " + tmp->asName); // to zgłaszać duplikat
            }
            else
                sTracks->Add(TP_MODEL, tmp->asName.c_str(), tmp); // nazwa jest unikalna
        }
        // str=Parser->GetNextSymbol().LowerCase();
        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();
                    *parser >> tmp->Ambient[0];
                    parser->getTokens();
                    *parser >> tmp->Ambient[1];
                    parser->getTokens();
                    *parser >> tmp->Ambient[2];
                }
                else if (token.compare("diffuse:") == 0)
                { // Ra: coś jest nie tak, bo w jednej linijce nie działa
                    parser->getTokens();
                    *parser >> tmp->Diffuse[0];
                    parser->getTokens();
                    *parser >> tmp->Diffuse[1];
                    parser->getTokens();
                    *parser >> tmp->Diffuse[2];
                }
                else if (token.compare("specular:") == 0)
                {
                    parser->getTokens();
                    *parser >> tmp->Specular[0];
                    parser->getTokens();
                    *parser >> tmp->Specular[1];
                    parser->getTokens();
                    *parser >> tmp->Specular[2];
                }
                else
                    Error("Scene material failure!");
                parser->getTokens();
                *parser >> token;
            }
        }
        if (token.compare("endmaterial") == 0)
        {
            parser->getTokens();
            *parser >> token;
        }
        str = AnsiString(token.c_str());
#ifdef _PROBLEND
        // PROBLEND Q: 13122011 - Szociu: 27012012
        PROBLEND = true; // domyslnie uruchomione nowe wyświetlanie
        tmp->PROBLEND = true; // odwolanie do tgroundnode, bo rendering jest w tej klasie
        if (str.Pos("@") > 0) // sprawdza, czy w nazwie tekstury jest znak "@"
        {
            PROBLEND = false; // jeśli jest, wyswietla po staremu
            tmp->PROBLEND = false;
        }
#endif
        tmp->TextureID = TTexturesManager::GetTextureID(szTexturePath, szSceneryPath, str.c_str());
        tmp->iFlags = TTexturesManager::GetAlpha(tmp->TextureID) ? 0x220 : 0x210; // z usuwaniem
        if (((tmp->iType == GL_TRIANGLES) && (tmp->iFlags & 0x10)) ?
                Global::pTerrainCompact->TerrainLoaded() :
                false)
        { // jeśli jest tekstura nieprzezroczysta, a teren załadowany, to pomijamy trójkąty
            do
            { // pomijanie trójkątów
                parser->getTokens();
                *parser >> token;
            } while (token.compare("endtri") != 0);
            // delete tmp; //nie ma co tego trzymać
            // tmp=NULL; //to jest błąd
        }
        else
        {
            i = 0;
            do
            {
                if (i < 9999) // 3333 trójkąty
                { // liczba wierzchołków nie jest nieograniczona
                    parser->getTokens(3);
                    *parser >> TempVerts[i].Point.x >> TempVerts[i].Point.y >> TempVerts[i].Point.z;
                    parser->getTokens(3);
                    *parser >> TempVerts[i].Normal.x >> TempVerts[i].Normal.y >>
                        TempVerts[i].Normal.z;
                    /*
                         str=Parser->GetNextSymbol().LowerCase();
                         if (str==AnsiString("x"))
                             TempVerts[i].tu=(TempVerts[i].Point.x+Parser->GetNextSymbol().ToDouble())/Parser->GetNextSymbol().ToDouble();
                         else
                         if (str==AnsiString("y"))
                             TempVerts[i].tu=(TempVerts[i].Point.y+Parser->GetNextSymbol().ToDouble())/Parser->GetNextSymbol().ToDouble();
                         else
                         if (str==AnsiString("z"))
                             TempVerts[i].tu=(TempVerts[i].Point.z+Parser->GetNextSymbol().ToDouble())/Parser->GetNextSymbol().ToDouble();
                         else
                             TempVerts[i].tu=str.ToDouble();;

                         str=Parser->GetNextSymbol().LowerCase();
                         if (str==AnsiString("x"))
                             TempVerts[i].tv=(TempVerts[i].Point.x+Parser->GetNextSymbol().ToDouble())/Parser->GetNextSymbol().ToDouble();
                         else
                         if (str==AnsiString("y"))
                             TempVerts[i].tv=(TempVerts[i].Point.y+Parser->GetNextSymbol().ToDouble())/Parser->GetNextSymbol().ToDouble();
                         else
                         if (str==AnsiString("z"))
                             TempVerts[i].tv=(TempVerts[i].Point.z+Parser->GetNextSymbol().ToDouble())/Parser->GetNextSymbol().ToDouble();
                         else
                             TempVerts[i].tv=str.ToDouble();;
                    */
                    parser->getTokens(2);
                    *parser >> TempVerts[i].tu >> TempVerts[i].tv;

                    //    tf=Parser->GetNextSymbol().ToDouble();
                    //          TempVerts[i].tu=tf;
                    //        tf=Parser->GetNextSymbol().ToDouble();
                    //      TempVerts[i].tv=tf;

                    TempVerts[i].Point.RotateZ(aRotate.z / 180 * M_PI);
                    TempVerts[i].Point.RotateX(aRotate.x / 180 * M_PI);
                    TempVerts[i].Point.RotateY(aRotate.y / 180 * M_PI);
                    TempVerts[i].Normal.RotateZ(aRotate.z / 180 * M_PI);
                    TempVerts[i].Normal.RotateX(aRotate.x / 180 * M_PI);
                    TempVerts[i].Normal.RotateY(aRotate.y / 180 * M_PI);
                    TempVerts[i].Point += pOrigin;
                    tmp->pCenter += TempVerts[i].Point;
                }
                else if (i == 9999)
                    ErrorLog("Bad triangles: too many verices");
                i++;
                parser->getTokens();
                *parser >> token;

                //   }

            } while (token.compare("endtri") != 0);
            nv = i;
            tmp->Init(nv); // utworzenie tablicy wierzchołków
            tmp->pCenter /= (nv > 0 ? nv : 1);

            //   memcpy(tmp->Vertices,TempVerts,nv*sizeof(TGroundVertex));

            r = 0;
            for (int i = 0; i < nv; i++)
            {
                tmp->Vertices[i] = TempVerts[i];
                tf = SquareMagnitude(tmp->Vertices[i].Point - tmp->pCenter);
                if (tf > r)
                    r = tf;
            }

            //   tmp->fSquareRadius=2000*2000+r;
            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(3);
        *parser >> tmp->Diffuse[0] >> tmp->Diffuse[1] >> tmp->Diffuse[2];
        //   tmp->Diffuse[0]=Parser->GetNextSymbol().ToDouble()/255;
        //   tmp->Diffuse[1]=Parser->GetNextSymbol().ToDouble()/255;
        //   tmp->Diffuse[2]=Parser->GetNextSymbol().ToDouble()/255;
        parser->getTokens();
        *parser >> tmp->fLineThickness;
        i = 0;
        parser->getTokens();
        *parser >> token;
        do
        {
            str = AnsiString(token.c_str());
            TempVerts[i].Point.x = str.ToDouble();
            parser->getTokens(2);
            *parser >> TempVerts[i].Point.y >> TempVerts[i].Point.z;
            TempVerts[i].Point.RotateZ(aRotate.z / 180 * M_PI);
            TempVerts[i].Point.RotateX(aRotate.x / 180 * M_PI);
            TempVerts[i].Point.RotateY(aRotate.y / 180 * M_PI);
            TempVerts[i].Point += pOrigin;
            tmp->pCenter += TempVerts[i].Point;
            i++;
            parser->getTokens();
            *parser >> token;
        } while (token.compare("endline") != 0);
        nv = i;
        //   tmp->Init(nv);
        tmp->Points = new vector3[nv];
        tmp->iNumPts = nv;
        tmp->pCenter /= (nv > 0 ? nv : 1);
        for (int i = 0; i < nv; i++)
            tmp->Points[i] = TempVerts[i].Point;
        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[br][bc].FastGetRect(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[br][bc].SafeGetRect(sc, sr)); // pobranie małego kwadratu
}

TEvent * TGround::FindEvent(const AnsiString &asEventName)
{
    return (TEvent *)sTracks->Find(0, asEventName.c_str()); // wyszukiwanie w drzewie
    /* //powolna wyszukiwarka
     for (TEvent *Current=RootEvent;Current;Current=Current->Next2)
     {
      if (Current->asName==asEventName)
       return Current;
     }
     return NULL;
    */
}

TEvent * TGround::FindEventScan(const AnsiString &asEventName)
{ // wyszukanie eventu z opcją utworzenia niejawnego dla komórek skanowanych
    TEvent *e = (TEvent *)sTracks->Find(0, asEventName.c_str()); // wyszukiwanie w drzewie eventów
    if (e)
        return e; // jak istnieje, to w porządku
    if (asEventName.SubString(asEventName.Length() - 4, 5) ==
        ":scan") // jeszcze może być event niejawny
    { // no to szukamy komórki pamięci o nazwie zawartej w evencie
        AnsiString n = asEventName.SubString(1, asEventName.Length() - 5); // do dwukropka
        if (sTracks->Find(TP_MEMCELL, n.c_str())) // jeśli jest takowa komórka pamięci
            e = new TEvent(n); // utworzenie niejawnego eventu jej odczytu
    }
    return e; // utworzony albo się nie udało
}

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");
    int i, j;
    for (i = 0; i < TP_LAST; ++i)
    {
        for (TGroundNode *Current = nRootOfType[i]; Current; Current = Current->nNext)
        {
            Current->InitNormals();
            if (Current->iType != TP_DYNAMIC)
            { // pojazdów w ogóle nie dotyczy dodawanie do mapy
                if (i == TP_EVLAUNCH ? Current->EvLaunch->IsGlobal() : false)
                    srGlobal.NodeAdd(Current); // dodanie do globalnego obiektu
                else if (i == TP_TERRAIN)
                { // specjalne przetwarzanie terenu wczytanego z pliku E3D
                    AnsiString xxxzzz; // nazwa kwadratu
                    TGroundRect *gr;
                    for (j = 1; j < Current->iCount; ++j)
                    { // od 1 do końca są zestawy trójkątów
                        xxxzzz = AnsiString(Current->nNode[j].smTerrain->pName); // pobranie nazwy
                        gr = GetRect(1000 * (xxxzzz.SubString(1, 3).ToIntDef(0) - 500),
                                     1000 * (xxxzzz.SubString(4, 3).ToIntDef(0) - 500));
                        if (Global::bUseVBO)
                            gr->nTerrain = Current->nNode + j; // zapamiętanie
                        else
                            gr->RaNodeAdd(&Current->nNode[j]);
                    }
                }
                //    else if
                //    ((Current->iType!=GL_TRIANGLES)&&(Current->iType!=GL_TRIANGLE_STRIP)?true
                //    //~czy trójkąt?
                else if ((Current->iType != GL_TRIANGLES) ?
                             true //~czy trójkąt?
                             :
                             (Current->iFlags & 0x20) ?
                             true //~czy teksturę ma nieprzezroczystą?
                             :
                             (Current->fSquareMinRadius != 0.0) ?
                             true //~czy widoczny z bliska?
                             :
                             (Current->fSquareRadius <= 90000.0)) //~czy widoczny z daleka?
                    GetSubRect(Current->pCenter.x, Current->pCenter.z)->NodeAdd(Current);
                else // dodajemy do kwadratu kilometrowego
                    GetRect(Current->pCenter.x, Current->pCenter.z)->NodeAdd(Current);
            }
            // if (Current->iType!=TP_DYNAMIC)
            // GetSubRect(Current->pCenter.x,Current->pCenter.z)->AddNode(Current);
        }
    }
    for (i = 0; i < iNumRects; ++i)
        for (j = 0; j < iNumRects; ++j)
            Rects[i][j].Optimize(); // optymalizacja obiektów w sektorach
    WriteLog("InitNormals OK");
    WriteLog("InitTracks");
    InitTracks(); //łączenie odcinków ze sobą i przyklejanie eventów
    WriteLog("InitTracks OK");
    WriteLog("InitTraction");
    InitTraction(); //łączenie drutów ze sobą
    WriteLog("InitTraction OK");
    WriteLog("InitEvents");
    InitEvents();
    WriteLog("InitEvents OK");
    WriteLog("InitLaunchers");
    InitLaunchers();
    WriteLog("InitLaunchers OK");
    WriteLog("InitGlobalTime");
    // ABu 160205: juz nie TODO :)
    GlobalTime = new TMTableTime(
        hh, mm, srh, srm, ssh,
        ssm); // McZapkie-300302: inicjacja czasu rozkladowego - TODO: czytac z trasy!
    WriteLog("InitGlobalTime OK");
    // jeszcze ustawienie pogody, gdyby nie było w scenerii wpisów
    glClearColor(Global::AtmoColor[0], Global::AtmoColor[1], Global::AtmoColor[2],
                 0.0); // Background Color
    if (Global::fFogEnd > 0)
    {
        glFogi(GL_FOG_MODE, GL_LINEAR);
        glFogfv(GL_FOG_COLOR, Global::FogColor); // set fog color
        glFogf(GL_FOG_START, Global::fFogStart); // fog start depth
        glFogf(GL_FOG_END, Global::fFogEnd); // fog end depth
        glEnable(GL_FOG);
    }
    else
        glDisable(GL_FOG);
    glDisable(GL_LIGHTING);
    glLightfv(GL_LIGHT0, GL_POSITION, Global::lightPos); // daylight position
    glLightfv(GL_LIGHT0, GL_AMBIENT, Global::ambientDayLight); // kolor wszechobceny
    glLightfv(GL_LIGHT0, GL_DIFFUSE, Global::diffuseDayLight); // kolor padający
    glLightfv(GL_LIGHT0, GL_SPECULAR, Global::specularDayLight); // kolor odbity
    // musi być tutaj, bo wcześniej nie mieliśmy wartości światła
    if (Global::fMoveLight >= 0.0) // albo tak, albo niech ustala minimum ciemności w nocy
    {
        Global::fLuminance = // obliczenie luminacji "światła w ciemności"
            +0.150 * Global::ambientDayLight[0] // R
            + 0.295 * Global::ambientDayLight[1] // G
            + 0.055 * Global::ambientDayLight[2]; // B
        if (Global::fLuminance > 0.1) // jeśli miało by być za jasno
            for (int i = 0; i < 3; i++)
                Global::ambientDayLight[i] *=
                    0.1 / Global::fLuminance; // ograniczenie jasności w nocy
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, Global::ambientDayLight);
    }
    else if (Global::bDoubleAmbient) // Ra: wcześniej było ambient dawane na obydwa światła
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, Global::ambientDayLight);
    glEnable(GL_LIGHTING);
    WriteLog("FirstInit is done");
};

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

    /*
        TFileStream *fs;
        fs=new TFileStream(asFile , fmOpenRead	| fmShareCompat	);
        AnsiString str="";
        int size=fs->Size;
        str.SetLength(size);
        fs->Read(str.c_str(),size);
        str+="";
        delete fs;
        TQueryParserComp *Parser;
        Parser=new TQueryParserComp(NULL);
        Parser->TextToParse=str;
    //    Parser->LoadStringToParse(asFile);
        Parser->First();
        AnsiString Token,asFileName;
    */
    const int OriginStackMaxDepth = 100; // rozmiar stosu dla zagnieżdżenia origin
    int OriginStackTop = 0;
    vector3 OriginStack[OriginStackMaxDepth]; // stos zagnieżdżenia origin

    double tf;
    int ParamCount, ParamPos;

    // ABu: Jezeli nie ma definicji w scenerii to ustawiane ponizsze wartosci:
    hh = 10; // godzina startu
    mm = 30; // minuty startu
    srh = 6; // godzina wschodu slonca
    srm = 0; // minuty wschodu slonca
    ssh = 20; // godzina zachodu slonca
    ssm = 0; // minuty zachodu slonca
    TGroundNode *LastNode = NULL; // do użycia w trainset
    iNumNodes = 0;
    token = "";
    parser.getTokens();
    parser >> token;
    int refresh = 0;

    while (token != "") //(!Parser->EndOfFile)
    {
        if (refresh == 50)
        { // SwapBuffers(hDC); //Ra: bez ogranicznika za bardzo spowalnia :( a u niektórych miga
            refresh = 0;
            Global::DoEvents();
        }
        else
            ++refresh;
        str = AnsiString(token.c_str());
        if (str == AnsiString("node"))
        {
            LastNode = AddGroundNode(&parser); // rozpoznanie węzła
            if (LastNode)
            { // jeżeli przetworzony poprawnie
                if (LastNode->iType == GL_TRIANGLES)
                {
                    if (!LastNode->Vertices)
                        SafeDelete(LastNode); // usuwamy nieprzezroczyste trójkąty terenu
                }
                else if (Global::bLoadTraction ? false : LastNode->iType == TP_TRACTION)
                    SafeDelete(LastNode); // usuwamy druty, jeśli wyłączone
                if (LastNode) // dopiero na koniec dopisujemy do tablic
                    if (LastNode->iType != TP_DYNAMIC)
                    { // jeśli nie jest pojazdem
                        LastNode->nNext = nRootOfType[LastNode->iType]; // ostatni dodany dołączamy
                        // na końcu nowego
                        nRootOfType[LastNode->iType] =
                            LastNode; // ustawienie nowego na początku listy
                        iNumNodes++;
                    }
                    else
                    { // jeśli jest pojazdem
                        // if (!bInitDone) FirstInit(); //jeśli nie było w scenerii
                        if (LastNode->DynamicObject->Mechanik) // ale może być pasażer
                            if (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 (bTrainSet && (LastNode?(LastNode->iType==TP_DYNAMIC):false))
                        if (nTrainSetNode) // 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, NULL);
                                nTrainSetDriver =
                                    NULL; // a przy "endtrainset" już wtedy nie potrzeba
                            }
                        }
                    }
            }
            else
            {
                Error("Scene parse error near " + AnsiString(token.c_str()));
                // break;
            }
        }
        else if (str == AnsiString("trainset"))
        {
            iTrainSetWehicleNumber = 0;
            nTrainSetNode = NULL;
            nTrainSetDriver = NULL; // pojazd, któremu zostanie wysłany rozkład
            bTrainSet = true;
            parser.getTokens();
            parser >> token;
            asTrainName = AnsiString(token.c_str()); // McZapkie: rodzaj+nazwa pociagu w SRJP
            parser.getTokens();
            parser >> token;
            asTrainSetTrack = AnsiString(token.c_str()); //ścieżka startowa
            parser.getTokens(2);
            parser >> fTrainSetDist >> fTrainSetVel; // przesunięcie i prędkość
        }
        else if (str == AnsiString("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 ? !TempConnectionType[iTrainSetWehicleNumber - 1] :
                                                 false) // jeśli ostatni pojazd ma sprzęg 0
                        LastNode->DynamicObject->RaLightsSet(-1, 2 + 32 + 64); // to założymy mu
            // końcówki blaszane
            // (jak AI się
            // odpali, to sobie
            // poprawi)
            bTrainSet = false;
            fTrainSetVel = 0;
            // iTrainSetConnection=0;
            nTrainSetNode = nTrainSetDriver = NULL;
            iTrainSetWehicleNumber = 0;
        }
        else if (str == AnsiString("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
                    int i = tmp->asName.Length();
                    if (tmp->asName[1] == '#') // zawsze jeden znak co najmniej jest
                    {
                        delete tmp;
                        tmp = NULL;
                    } // utylizacja duplikatu z krzyżykiem
                    else if (i > 8 ? tmp->asName.SubString(1, 9) == "lineinfo:" :
                                     false) // tymczasowo wyjątki
                    {
                        delete tmp;
                        tmp = NULL;
                    } // tymczasowa utylizacja duplikatów W5
                    else if (i > 8 ? tmp->asName.SubString(i - 7, 8) == "_warning" :
                                     false) // tymczasowo wyjątki
                    {
                        delete tmp;
                        tmp = NULL;
                    } // tymczasowa utylizacja duplikatu z trąbieniem
                    else if (i > 4 ? tmp->asName.SubString(i - 3, 4) == "_shp" :
                                     false) // 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->Type = tp_Ignored; // dezaktywacja pierwotnego - taka proteza na
                            // wsteczną zgodność
                            // SafeDelete(tmp); //bezlitośnie usuwamy wszelkie duplikaty, żeby nie
                            // zaśmiecać drzewka
                        }
                }
                if (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->Type != tp_Ignored)
                            if (RootEvent->asName.Pos(
                                    "onstart")) // event uruchamiany automatycznie po starcie
                                AddToQuery(RootEvent, NULL); // dodanie do kolejki
                        sTracks->Add(0, tmp->asName.c_str(), tmp); // dodanie do wyszukiwarki
                    }
                }
            }
        }
        //     else
        //     if (str==AnsiString("include"))  //Tolaris to zrobil wewnatrz parsera
        //     {
        //         Include(Parser);
        //     }
        else if (str == AnsiString("rotate"))
        {
            // parser.getTokens(3);
            // parser >> aRotate.x >> aRotate.y >> aRotate.z; //Ra: to potrafi dawać błędne
            // rezultaty
            parser.getTokens();
            parser >> aRotate.x;
            parser.getTokens();
            parser >> aRotate.y;
            parser.getTokens();
            parser >> aRotate.z;
            // WriteLog("*** rotate "+AnsiString(aRotate.x)+" "+AnsiString(aRotate.y)+"
            // "+AnsiString(aRotate.z));
        }
        else if (str == AnsiString("origin"))
        {
            //      str=Parser->GetNextSymbol().LowerCase();
            //      if (str=="begin")
            {
                if (OriginStackTop >= OriginStackMaxDepth - 1)
                {
                    MessageBox(0, AnsiString("Origin stack overflow ").c_str(), "Error", MB_OK);
                    break;
                }
                parser.getTokens(3);
                parser >> OriginStack[OriginStackTop].x >> OriginStack[OriginStackTop].y >>
                    OriginStack[OriginStackTop].z;
                pOrigin += OriginStack[OriginStackTop]; // sumowanie całkowitego przesunięcia
                OriginStackTop++; // zwiększenie wskaźnika stosu
            }
        }
        else if (str == AnsiString("endorigin"))
        {
            //      else
            //    if (str=="end")
            {
                if (OriginStackTop <= 0)
                {
                    MessageBox(0, AnsiString("Origin stack underflow ").c_str(), "Error", MB_OK);
                    break;
                }

                OriginStackTop--; // zmniejszenie wskaźnika stosu
                pOrigin -= OriginStack[OriginStackTop];
            }
        }
        else if (str == AnsiString("atmo")) // TODO: uporzadkowac gdzie maja byc parametry mgly!
        { // Ra: ustawienie parametrów OpenGL przeniesione do FirstInit
            WriteLog("Scenery atmo definition");
            parser.getTokens(3);
            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;
            while (token.compare("endatmo") != 0)
            { // a kolejne parametry są pomijane
                parser.getTokens();
                parser >> token;
            }
        }
        else if (str == AnsiString("time"))
        {
            WriteLog("Scenery time definition");
            char temp_in[9];
            char temp_out[9];
            int i, j;
            parser.getTokens();
            parser >> temp_in;
            for (j = 0; j <= 8; j++)
                temp_out[j] = ' ';
            for (i = 0; temp_in[i] != ':'; i++)
                temp_out[i] = temp_in[i];
            hh = atoi(temp_out);
            for (j = 0; j <= 8; j++)
                temp_out[j] = ' ';
            for (j = i + 1; j <= 8; j++)
                temp_out[j - (i + 1)] = temp_in[j];
            mm = atoi(temp_out);

            parser.getTokens();
            parser >> temp_in;
            for (j = 0; j <= 8; j++)
                temp_out[j] = ' ';
            for (i = 0; temp_in[i] != ':'; i++)
                temp_out[i] = temp_in[i];
            srh = atoi(temp_out);
            for (j = 0; j <= 8; j++)
                temp_out[j] = ' ';
            for (j = i + 1; j <= 8; j++)
                temp_out[j - (i + 1)] = temp_in[j];
            srm = atoi(temp_out);

            parser.getTokens();
            parser >> temp_in;
            for (j = 0; j <= 8; j++)
                temp_out[j] = ' ';
            for (i = 0; temp_in[i] != ':'; i++)
                temp_out[i] = temp_in[i];
            ssh = atoi(temp_out);
            for (j = 0; j <= 8; j++)
                temp_out[j] = ' ';
            for (j = i + 1; j <= 8; j++)
                temp_out[j - (i + 1)] = temp_in[j];
            ssm = atoi(temp_out);
            while (token.compare("endtime") != 0)
            {
                parser.getTokens();
                parser >> token;
            }
        }
        else if (str == AnsiString("light"))
        { // Ra: ustawianie światła przeniesione do FirstInit
            WriteLog("Scenery light definition");
            vector3 lp;
            parser.getTokens();
            parser >> lp.x;
            parser.getTokens();
            parser >> lp.y;
            parser.getTokens();
            parser >> lp.z;
            lp = Normalize(lp); // kierunek padania
            Global::lightPos[0] = lp.x; // daylight position
            Global::lightPos[1] = lp.y;
            Global::lightPos[2] = lp.z;
            parser.getTokens();
            parser >> Global::ambientDayLight[0]; // kolor wszechobceny
            parser.getTokens();
            parser >> Global::ambientDayLight[1];
            parser.getTokens();
            parser >> Global::ambientDayLight[2];

            parser.getTokens();
            parser >> Global::diffuseDayLight[0]; // kolor padający
            parser.getTokens();
            parser >> Global::diffuseDayLight[1];
            parser.getTokens();
            parser >> Global::diffuseDayLight[2];

            parser.getTokens();
            parser >> Global::specularDayLight[0]; // kolor odbity
            parser.getTokens();
            parser >> Global::specularDayLight[1];
            parser.getTokens();
            parser >> Global::specularDayLight[2];

            do
            {
                parser.getTokens();
                parser >> token;
            } while (token.compare("endlight") != 0);
        }
        else if (str == AnsiString("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::pFreeCameraInit[into] = xyz;
                abc.x = DegToRad(abc.x);
                abc.y = DegToRad(abc.y);
                abc.z = DegToRad(abc.z);
                Global::pFreeCameraInitAngle[into] = abc;
                Global::iCameraLast = into; // numer ostatniej
            }
        }
        else if (str == AnsiString("sky"))
        { // youBy - niebo z pliku
            WriteLog("Scenery sky definition");
            parser.getTokens();
            parser >> token;
            AnsiString SkyTemp;
            SkyTemp = AnsiString(token.c_str());
            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 == AnsiString("firstinit"))
            FirstInit();
        else if (str == AnsiString("description"))
        {
            do
            {
                parser.getTokens();
                parser >> token;
            } while (token.compare("enddescription") != 0);
        }
        else if (str == AnsiString("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 == AnsiString("config"))
        { // możliwość przedefiniowania parametrów w scenerii
            Global::ConfigParse(NULL, &parser); // parsowanie dodatkowych ustawień
        }
        else if (str != AnsiString(""))
        { // 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(AnsiString("Unrecognized command: " + str));
                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
                Error(AnsiString("Unrecognized command: " + str));
        }
        else if (str == AnsiString(""))
            break;

        // LastNode=NULL;

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

    delete parser;
    sTracks->Sort(TP_TRACK); // finalne sortowanie drzewa torów
    sTracks->Sort(TP_MEMCELL); // finalne sortowanie drzewa komórek pamięci
    sTracks->Sort(TP_MODEL); // finalne sortowanie drzewa modeli
    sTracks->Sort(0); // finalne sortowanie drzewa eventów
    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;
    char buff[255];
    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 \"" + AnsiString(Current->asNodeName) +
                                 "\" does not exists in \"" + Current->asName + "\"");
                }
                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
                         .IsEmpty()) // 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 +
                                 "\" not exists in memcell \"" + tmp->asName + "\"");
                }
                else
                    Current->Params[6].asTrack = NULL;
            }
            else
            { // nie ma komórki, to nie będzie działał poprawnie
                Current->Type = tp_Ignored; // deaktywacja
                ErrorLog("Bad event: \"" + Current->asName + "\" cannot find memcell \"" +
                         Current->asNodeName + "\"");
            }
            break;
        case tp_LogValues: // skojarzenie z memcell
            if (Current->asNodeName.IsEmpty())
            { // 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->Type = tp_Ignored; // deaktywacja
                ErrorLog("Bad 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
                         .IsEmpty()) // 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 +
                                 "\" not exists in memcell \"" + tmp->asName + "\"");
                }
                else
                    Current->Params[6].asTrack = NULL;
            }
            else
                ErrorLog("Bad copyvalues: event \"" + Current->asName +
                         "\" cannot find memcell \"" + Current->asNodeName + "\"");
            strcpy(
                buff,
                Current->Params[9].asText); // skopiowanie nazwy drugiej komórki do bufora roboczego
            SafeDeleteArray(Current->Params[9].asText); // usunięcie nazwy komórki
            tmp = FindGroundNode(buff, TP_MEMCELL); // komórka źódłowa
            if (tmp)
            {
                Current->Params[8].nGroundNode = tmp;
                Current->Params[9].asMemCell = tmp->MemCell; // komórka źródłowa
            }
            else
                ErrorLog("Bad copyvalues: event \"" + Current->asName +
                         "\" cannot find memcell \"" + AnsiString(buff) + "\"");
            break;
        case tp_Animation: // animacja modelu
            tmp = FindGroundNode(Current->asNodeName, TP_MODEL); // egzemplarza modelu do animowania
            if (tmp)
            {
                strcpy(
                    buff,
                    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(buff); // 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 + "." + AnsiString(buff) + ":done"));
                    }
                }
            }
            else
                ErrorLog("Bad 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 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_TRACTION); // może druty?
            if (!tmp)
                tmp = FindGroundNode(Current->asNodeName, TP_TRACK); // albo tory?
            if (tmp)
                Current->Params[9].nGroundNode = tmp;
            else
                ErrorLog("Bad visibility: event \"" + Current->asName + "\" cannot find model \"" +
                         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 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 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.IsEmpty())
            {
                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 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("Event \"" + Current->asName + "\" cannot find dynamic \"" +
                          Current->asNodeName + "\"");
            }
            Current->asNodeName = "";
            break;
        case tp_Multiple:
            if (Current->Params[9].asText != NULL)
            { // przepisanie nazwy do bufora
                strcpy(buff, Current->Params[9].asText);
                SafeDeleteArray(Current->Params[9].asText);
                Current->Params[9].asPointer = NULL; // zerowanie wskaźnika, aby wykryć brak obeiktu
            }
            else
                buff[0] = '\0';
            if (Current->iFlags & (conditional_trackoccupied | conditional_trackfree))
            { // jeśli chodzi o zajetosc toru
                tmp = FindGroundNode(buff, TP_TRACK);
                if (tmp)
                    Current->Params[9].asTrack = tmp->pTrack;
                if (!Current->Params[9].asTrack)
                {
                    ErrorLog(AnsiString("Bad event: Track \"") + AnsiString(buff) +
                             "\" does not exist in \"" + Current->asName + "\"");
                    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(buff, TP_MEMCELL);
                if (tmp)
                    Current->Params[9].asMemCell = tmp->MemCell;
                if (!Current->Params[9].asMemCell)
                {
                    ErrorLog(AnsiString("Bad event: MemCell \"") + AnsiString(buff) +
                             AnsiString("\" does not exist in \"" + Current->asName + "\""));
                    Current->iFlags &=
                        ~(conditional_memstring | conditional_memval1 | conditional_memval2);
                }
            }
            for (i = 0; i < 8; i++)
            {
                if (Current->Params[i].asText != NULL)
                {
                    strcpy(buff, Current->Params[i].asText);
                    SafeDeleteArray(Current->Params[i].asText);
                    Current->Params[i].asEvent = FindEvent(buff);
                    if (!Current->Params[i].asEvent) // Ra: tylko w logu informacja o braku
                        if (AnsiString(Current->Params[i].asText).SubString(1, 5) != "none_")
                        {
                            WriteLog(AnsiString("Event \"") + AnsiString(buff) +
                                     AnsiString("\" does not exist"));
                            ErrorLog("Missed event: " + AnsiString(buff) + " in multiple " +
                                     Current->asName);
                        }
                }
            }
            break;
        case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource)
            if (!Current->asNodeName.IsEmpty())
            {
                tmp = FindGroundNode(Current->asNodeName,
                                     TP_TRACTIONPOWERSOURCE); // podłączenie zasilacza
                if (tmp)
                    Current->Params[9].psPower = tmp->psTractionPowerSource;
                else
                    ErrorLog("Bad 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, state;
    AnsiString name;
    // tracks=tracksfar=0;
    for (Current = nRootOfType[TP_TRACK]; Current; Current = Current->nNext)
    {
        Track = Current->pTrack;
        if (Global::iHiddenEvents & 1)
            if (!Current->asName.IsEmpty())
            { // jeśli podana jest nazwa torów, można szukać eventów skojarzonych przez nazwę
                if (Track->asEvent0Name.IsEmpty())
                    if (FindEvent(Current->asName + ":event0"))
                        Track->asEvent0Name = Current->asName + ":event0";
                if (Track->asEvent1Name.IsEmpty())
                    if (FindEvent(Current->asName + ":event1"))
                        Track->asEvent1Name = Current->asName + ":event1";
                if (Track->asEvent2Name.IsEmpty())
                    if (FindEvent(Current->asName + ":event2"))
                        Track->asEvent2Name = Current->asName + ":event2";

                if (Track->asEventall0Name.IsEmpty())
                    if (FindEvent(Current->asName + ":eventall0"))
                        Track->asEventall0Name = Current->asName + ":eventall0";
                if (Track->asEventall1Name.IsEmpty())
                    if (FindEvent(Current->asName + ":eventall1"))
                        Track->asEventall1Name = Current->asName + ":eventall1";
                if (Track->asEventall2Name.IsEmpty())
                    if (FindEvent(Current->asName + ":eventall2"))
                        Track->asEventall2Name = Current->asName + ":eventall2";
            }
        Track->AssignEvents(
            Track->asEvent0Name.IsEmpty() ? NULL : FindEvent(Track->asEvent0Name),
            Track->asEvent1Name.IsEmpty() ? NULL : FindEventScan(Track->asEvent1Name),
            Track->asEvent2Name.IsEmpty() ? NULL : FindEventScan(Track->asEvent2Name));
        Track->AssignallEvents(
            Track->asEventall0Name.IsEmpty() ? NULL : FindEvent(Track->asEventall0Name),
            Track->asEventall1Name.IsEmpty() ? NULL : FindEvent(Track->asEventall1Name),
            Track->asEventall2Name.IsEmpty() ? NULL :
                                               FindEvent(Track->asEventall2Name)); // MC-280503
        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
                Track->RaAssign(
                    Current, Model ? Model->Model : NULL, FindEvent(Current->asName + ":done"),
                    FindEvent(Current->asName + ":joined")); // wiązanie toru z modelem obrotnicy
                // 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;
        }
        name = Track->IsolatedName(); // pobranie nazwy odcinka izolowanego
        if (!name.IsEmpty()) // jeśli została zwrócona nazwa
            Track->IsolatedEventsAssign(FindEvent(name + ":busy"), FindEvent(name + ":free"));
        if (Current->asName.SubString(1, 1) ==
            "*") // możliwy portal, jeśli nie podłączony od striny 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
            sTracks->Add(TP_MEMCELL, p->asName.c_str(), Current); // dodanie do wyszukiwarki
            Current->nNext =
                nRootOfType[TP_MEMCELL]; // to nie powinno tutaj być, bo robi się śmietnik
            nRootOfType[TP_MEMCELL] = Current;
            iNumNodes++;
            p->pMemCell = Current->MemCell; // wskaźnik komóki przekazany do odcinka izolowanego
        }
        p = p->Next();
    }
    // for (Current=nRootOfType[TP_TRACK];Current;Current=Current->nNext)
    // if (Current->pTrack->eType==tt_Cross)
    //  Current->pTrack->ConnectionsLog(); //zalogowanie informacji o połączeniach
}

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;
    AnsiString 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);
                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
                iNumNodes++;
            }
    }
    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 " +
                                     FloatToStrF(Traction->pPoint1.x, ffFixed, 6, 2) + " " +
                                     FloatToStrF(Traction->pPoint1.y, ffFixed, 6, 2) + " " +
                                     FloatToStrF(Traction->pPoint1.z, ffFixed, 6, 2));
                    }
        }
        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 " +
                                     FloatToStrF(Traction->pPoint2.x, ffFixed, 6, 2) + " " +
                                     FloatToStrF(Traction->pPoint2.y, ffFixed, 6, 2) + " " +
                                     FloatToStrF(Traction->pPoint2.z, ffFixed, 6, 2));
                    }
        }
    }
    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.IsEmpty()) // 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 > 0) // 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;
    int i;
    for (Current = nRootOfType[TP_EVLAUNCH]; Current; Current = Current->nNext)
    {
        EventLauncher = Current->EvLaunch;
        if (EventLauncher->iCheckMask != 0)
            if (EventLauncher->asMemCellName != AnsiString("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 = NULL;
        EventLauncher->Event1 = (EventLauncher->asEvent1Name != AnsiString("none")) ?
                                    FindEvent(EventLauncher->asEvent1Name) :
                                    NULL;
        EventLauncher->Event2 = (EventLauncher->asEvent2Name != AnsiString("none")) ?
                                    FindEvent(EventLauncher->asEvent2Name) :
                                    NULL;
    }
    return true;
}

TTrack * TGround::FindTrack(vector3 Point, int &iConnection, TGroundNode *Exclude)
{ // wyszukiwanie innego toru kończącego się w (Point)
    TTrack *Track;
    TGroundNode *Current;
    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;
    }
#if 0
 //wyszukiwanie czołgowe (po wszystkich jak leci) - nie ma chyba sensu
 for (Current=nRootOfType[TP_TRACK];Current;Current=Current->Next)
 {
  if ((Current->iType==TP_TRACK) && (Current!=Exclude))
  {
   iConnection=Current->pTrack->TestPoint(&Point);
   if (iConnection>=0) return Current->pTrack;
  }
 }
#endif
    return NULL;
}

TTraction * TGround::FindTraction(vector3 *Point, int &iConnection, TGroundNode *Exclude)
{ // wyszukiwanie innego przęsła kończącego się w (Point)
    TTraction *Traction;
    TGroundNode *Current;
    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(vector3 &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 = (DotProduct(
                                                          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 = SquareMagnitude(
                                                    p -
                                                    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]);
        // testowo skrzywienie przęsła tak, aby pokazać skąd ma zasilanie
        // if (dir) //1 gdy ciąg dalszy jest od strony Point2
        // n->hvTraction->pPoint3=0.25*(nBest->pCenter+3*(zg?nBest->hvTraction->pPoint4:nBest->hvTraction->pPoint3));
        // else
        // n->hvTraction->pPoint4=0.25*(nBest->pCenter+3*(zg?nBest->hvTraction->pPoint4:nBest->hvTraction->pPoint3));
    }
    return (nBest ? nBest->hvTraction : NULL);
};

bool TGround::AddToQuery(TEvent *Event, TDynamicObject *Node)
{
    if (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) : false)
            { // 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 (int i = 0; i < Event->Params[6].asTrack->iNumDynamics; ++i)
                            Event->Params[5].asMemCell->PutCommand(
                                Event->Params[6].asTrack->Dynamics[i]->Mechanik,
                                &Event->Params[4].nGroundNode->pCenter);
                        if (DebugModeFlag)
                            WriteLog("EVENT EXECUTED: AddValues & Track command - " +
                                     AnsiString(Event->Params[0].asText) + " " +
                                     AnsiString(Event->Params[1].asdouble) + " " +
                                     AnsiString(Event->Params[2].asdouble));
                    }
                    else if (DebugModeFlag)
                        WriteLog("EVENT EXECUTED: AddValues - " +
                                 AnsiString(Event->Params[0].asText) + " " +
                                 AnsiString(Event->Params[1].asdouble) + " " +
                                 AnsiString(Event->Params[2].asdouble));
                }
                Event =
                    Event
                        ->evJoined; // jeśli jest kolejny o takiej samej nazwie, to idzie do kolejki
            }
            if (Event)
            { // standardowe dodanie do kolejki
                WriteLog("EVENT ADDED TO QUEUE: " + Event->asName +
                         (Node ? AnsiString(" by " + Node->asName) : AnsiString("")));
                Event->fStartTime =
                    fabs(Event->fDelay) + Timer::GetTime(); // czas od uruchomienia scenerii
                if (Event->fRandomDelay > 0.0)
                    Event->fStartTime += Event->fRandomDelay * random(10000) *
                                         0.0001; // doliczenie losowego czasu opóźnienia
                ++Event->iQueued; // zabezpieczenie przed podwójnym dodaniem do kolejki
                if (QueryRootEvent ? Event->fStartTime >= QueryRootEvent->fStartTime : false)
                    QueryRootEvent->AddToQuery(Event); // dodanie gdzieś w środku
                else
                { // dodanie z przodu: albo nic nie ma, albo ma być wykonany szybciej niż pierwszy
                    Event->evNext = QueryRootEvent;
                    QueryRootEvent = Event;
                }
            }
        }
    return true;
}

bool TGround::EventConditon(TEvent *e)
{ // sprawdzenie spelnienia warunków dla eventu
    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 = 1.0 * rand() / RAND_MAX;
        WriteLog("Random integer: " + CurrToStr(rprobability) + "/" +
                 CurrToStr(e->Params[10].asdouble));
        return (e->Params[10].asdouble > rprobability);
    }
    else if (e->iFlags & conditional_memcompare)
    { // porównanie wartości
        if (tmpEvent->Params[9].asMemCell->Compare(e->Params[10].asText, e->Params[11].asdouble,
                                                   e->Params[12].asdouble, e->iFlags))
            return true;
        else if (Global::iWriteLogEnabled && DebugModeFlag)
        { // nie zgadza się, więc sprawdzmy, co
            LogComment = e->Params[9].asMemCell->Text() + AnsiString(" ") +
                         FloatToStrF(e->Params[9].asMemCell->Value1(), ffFixed, 8, 2) + " " +
                         FloatToStrF(tmpEvent->Params[9].asMemCell->Value2(), ffFixed, 8, 2) +
                         " != ";
            if (TestFlag(e->iFlags, conditional_memstring))
                LogComment += AnsiString(tmpEvent->Params[10].asText);
            else
                LogComment += "*";
            if (TestFlag(tmpEvent->iFlags, conditional_memval1))
                LogComment += " " + FloatToStrF(tmpEvent->Params[11].asdouble, ffFixed, 8, 2);
            else
                LogComment += " *";
            if (TestFlag(tmpEvent->iFlags, conditional_memval2))
                LogComment += " " + FloatToStrF(tmpEvent->Params[12].asdouble, ffFixed, 8, 2);
            else
                LogComment += " *";
            WriteLog(LogComment.c_str());
        }
    }
    return false;
};

bool TGround::CheckQuery()
{ // sprawdzenie kolejki eventów oraz wykonanie tych, którym czas minął
    TLocation loc;
    int i;
    /* //Ra: to w ogóle jakiś chory kod jest; wygląda jak wyszukanie eventu z najlepszym czasem
     Double evtime,evlowesttime; //Ra: co to za typ?
     //evlowesttime=1000000;
     if (QueryRootEvent)
     {
      OldQRE=QueryRootEvent;
      tmpEvent=QueryRootEvent;
     }
     if (QueryRootEvent)
     {
      for (i=0;i<90;++i)
      {
       evtime=((tmpEvent->fStartTime)-(Timer::GetTime())); //pobranie wartości zmiennej
       if (evtime<evlowesttime)
       {
        evlowesttime=evtime;
        tmp2Event=tmpEvent;
       }
       if (tmpEvent->Next)
        tmpEvent=tmpEvent->Next;
       else
        i=100;
      }
      if (OldQRE!=tmp2Event)
      {
       QueryRootEvent->AddToQuery(QueryRootEvent);
       QueryRootEvent=tmp2Event;
      }
     }
    */
    /*
     if (QueryRootEvent)
     {//wypisanie kolejki
      tmpEvent=QueryRootEvent;
      WriteLog("--> Event queue:");
      while (tmpEvent)
      {
       WriteLog(tmpEvent->asName+" "+AnsiString(tmpEvent->fStartTime));
       tmpEvent=tmpEvent->Next;
      }
     }
    */
    while (QueryRootEvent ? QueryRootEvent->fStartTime < Timer::GetTime() : false)
    { // 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
            // 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 (tmpEvent->bEnabled)
        { // w zasadzie te wyłączone są skanowane i nie powinny się nigdy w kolejce znaleźć
            WriteLog("EVENT LAUNCHED: " + tmpEvent->asName +
                     (tmpEvent->Activator ? AnsiString(" by " + tmpEvent->Activator->asName) :
                                            AnsiString("")));
            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 (int i = 0; i < tmpEvent->Params[6].asTrack->iNumDynamics; ++i)
                            tmpEvent->Params[5].asMemCell->PutCommand(
                                tmpEvent->Params[6].asTrack->Dynamics[i]->Mechanik,
                                &tmpEvent->Params[4].nGroundNode->pCenter);
                        if (DebugModeFlag)
                            WriteLog("Type: UpdateValues & Track command - " +
                                     AnsiString(tmpEvent->Params[0].asText) + " " +
                                     AnsiString(tmpEvent->Params[1].asdouble) + " " +
                                     AnsiString(tmpEvent->Params[2].asdouble));
                    }
                    else if (DebugModeFlag)
                        WriteLog("Type: UpdateValues - " + AnsiString(tmpEvent->Params[0].asText) +
                                 " " + AnsiString(tmpEvent->Params[1].asdouble) + " " +
                                 AnsiString(tmpEvent->Params[2].asdouble));
                }
                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)
                        WyslijEvent(tmpEvent->asName, tmpEvent->Activator->GetName());
                    // 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)
                {
                    loc.X =
                        -tmpEvent->Params[3].asdouble; // zamiana, bo fizyka ma inaczej niż sceneria
                    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");
                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
                    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");
                    // WriteLog("Vel: ",tmpEvent->Params[0].asdouble);
                    tmpEvent->Params[9].asTrack->VelocitySet(tmpEvent->Params[0].asdouble);
                    if (DebugModeFlag) // wyświetlana jest ta faktycznie ustawiona
                        WriteLog("vel: ", 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("Multiple passed");
                    for (i = 0; i < 8; ++i)
                    { // dodawane do kolejki w kolejności zapisania
                        if (tmpEvent->Params[i].asEvent)
                            if (bCondition != bool(tmpEvent->iFlags & (conditional_else << i)))
                            {
                                if (tmpEvent->Params[i].asEvent != tmpEvent)
                                    AddToQuery(tmpEvent->Params[i].asEvent,
                                               tmpEvent->Activator); // normalnie dodać
                                else // jeśli ma być rekurencja
                                    if (tmpEvent->fDelay >=
                                        5.0) // to musi mieć sensowny okres powtarzania
                                    if (tmpEvent->iQueued < 2)
                                    { // trzeba zrobić wyjątek, aby event mógł się sam dodać do
                                        // kolejki, raz już jest, ale będzie usunięty
                                        // pętla eventowa może być uruchomiona wiele razy, ale tylko
                                        // pierwsze uruchomienie zadziała
                                        tmpEvent->iQueued =
                                            0; // tymczasowo, aby był ponownie dodany do kolejki
                                        AddToQuery(tmpEvent, tmpEvent->Activator);
                                        tmpEvent->iQueued =
                                            2; // kolejny raz już absolutnie nie dodawać
                                    }
                            }
                    }
                    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)
                                WyslijEvent(tmpEvent->asName, tmpEvent->Activator->GetName());
                            else
                                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
                        tmpEvent->Params[9].asMemCell->UpdateValues(
                            tmpEvent->Activator->MoverParameters->TypeName.c_str(), // typ pojazdu
                            0, // na razie nic
                            0, // na razie nic
                            tmpEvent->iFlags &
                                (update_memstring | update_memval1 | update_memval2));
                    else // jeśli parametry ładunku
                        tmpEvent->Params[9].asMemCell->UpdateValues(
                            tmpEvent->Activator->MoverParameters->LoadType != "" ?
                                tmpEvent->Activator->MoverParameters->LoadType.c_str() :
                                "none", // nazwa ładunku
                            tmpEvent->Activator->MoverParameters->Load, // aktualna ilość
                            tmpEvent->Activator->MoverParameters->MaxLoad, // maksymalna ilość
                            tmpEvent->iFlags &
                                (update_memstring | update_memval1 | update_memval2));
                }
                else if (tmpEvent->iFlags & update_memadd)
                { // jeśli miejsce docelowe pojazdu
                    tmpEvent->Params[9].asMemCell->UpdateValues(
                        tmpEvent->Activator->asDestination.c_str(), // 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));
                }
                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().c_str(),
                            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() + " " +
                             tmpEvent->Params[9].asMemCell->Value1() + " " +
                             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() + " " + Current->MemCell->Value1() + " " +
                                 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)
        --tmpEvent->iQueued; // teraz moze być ponownie dodany do kolejki
        /*
          if (QueryRootEvent->eJoined) //jeśli jest kolejny o takiej samej nazwie
          {//to teraz jego dajemy do wykonania
           QueryRootEvent->eJoined->Next=QueryRootEvent->Next; //pamiętając o następnym z kolejki
           QueryRootEvent->eJoined->fStartTime=QueryRootEvent->fStartTime; //czas musi być ten sam,
          bo nie jest aktualizowany
           //QueryRootEvent->fStartTime=0;
           QueryRootEvent=QueryRootEvent->eJoined; //a wykonać ten doczepiony
          }
          else
          {//a jak nazwa jest unikalna, to kolejka idzie dalej
           //QueryRootEvent->fStartTime=0;
           QueryRootEvent=QueryRootEvent->Next; //NULL w skrajnym przypadku
          }
        */
    } // while
    return true;
}

void TGround::OpenGLUpdate(HDC hDC)
{
    SwapBuffers(hDC); // swap buffers (double buffering)
};

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 (bDynamicRemove)
    { // jeśli jest coś do usunięcia z listy, to trzeba na końcu
        for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
            if (!Current->DynamicObject->bEnabled)
            {
                DynamicRemove(Current->DynamicObject); // usunięcie tego i podłączonych
                Current = nRootDynamic; // sprawdzanie listy od początku
            }
        bDynamicRemove = false; // na razie koniec
    }
    return true;
};

// 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
    vector3 vLeft, vUp, vFront, dwys;
    vector3 pant0;
    vector3 vParam; // współczynniki równania parametrycznego drutu
    vector3 vStyk; // punkt przebicia drutu przez płaszczyznę ruchu pantografu
    vector3 vGdzie; // wektor położenia drutu względem pojazdu
    vFront = model->VectorFront(); // wektor normalny dla płaszczyzny ruchu pantografu
    vUp = model->VectorUp(); // wektor pionu pudła (pochylony od pionu na przechyłce)
    vLeft = model->VectorLeft(); // wektor odległości w bok (odchylony od poziomu na przechyłce)
    dwys = model->GetPosition(); // współrzędne środka pojazdu
    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 = -DotProduct(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 = -(DotProduct(p->hvPowerWire->pPoint1, vFront) + fRaParam) /
                               DotProduct(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 = DotProduct(
                            vGdzie, vUp); // musi się mieścić w przedziale ruchu pantografu
                        // odległość w bok powinna być mniejsza niż pół szerokości pantografu
                        fHorizontal = fabs(DotProduct(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
                                {
                                    vParam =
                                        node->hvTraction
                                            ->vParametric; // współczynniki równania parametrycznego
                                    fRaParam = -DotProduct(pant0, vFront);
                                    fRaParam = -(DotProduct(node->hvTraction->pPoint1, vFront) +
                                                 fRaParam) /
                                               DotProduct(vParam, vFront);
                                    if ((fRaParam >= -0.001) ? (fRaParam <= 1.001) : false)
                                    { // jeśli tylko jest w przedziale, wyznaczyć odległość wzdłuż
                                        // wektorów vUp i vLeft
                                        vStyk = node->hvTraction->pPoint1 +
                                                fRaParam * vParam; // punkt styku płaszczyzny z
                                        // drutem (dla generatora łuku
                                        // el.)
                                        vGdzie = vStyk - pant0; // wektor
                                        fVertical = DotProduct(
                                            vGdzie,
                                            vUp); // musi się mieścić w przedziale ruchu pantografu
                                        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 = fabs(DotProduct(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 " +
                                                            FloatToStrF(pant0.x, ffFixed, 7, 2) +
                                                            " " +
                                                            FloatToStrF(pant0.y, ffFixed, 7, 2) +
                                                            " " +
                                                            FloatToStrF(pant0.z, ffFixed, 7, 2));
                                                }
                                            }
                                            else if (fVertical < p->PantTraction) // ale niżej, niż
                                            // poprzednio
                                            // znaleziony
                                            {
                                                fHorizontal =
                                                    fabs(DotProduct(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
    }
    // if (model->fWahaczeAmp<model->MoverParameters->DistCounter)
    //{//nieużywana normalnie zmienna ogranicza powtórzone logowania
    // model->fWahaczeAmp=model->MoverParameters->DistCounter;
    // ErrorLog(FloatToStrF(1000.0*model->MoverParameters->DistCounter,ffFixed,7,3)+","+FloatToStrF(p->PantTraction,ffFixed,7,3)+","+FloatToStrF(p->fHorizontal,ffFixed,7,3)+","+FloatToStrF(p->PantWys,ffFixed,7,3)+","+AnsiString(p->hvPowerWire?1:0));
    // //
    // if (p->fHorizontal>1.0)
    //{
    // //Global::iPause|=1; //zapauzowanie symulacji
    // Global::fTimeSpeed=1; //spowolnienie czasu do obejrzenia pantografu
    // return true; //łapacz
    //}
    //}
    return true;
};

bool TGround::RenderDL(vector3 pPosition)
{ // renderowanie scenerii z Display List - faza nieprzezroczystych
    glDisable(GL_BLEND);
    glAlphaFunc(GL_GREATER, 0.45); // im mniejsza wartość, tym większa ramka, domyślnie 0.1f
    ++TGroundRect::iFrameNumber; // zwięszenie licznika ramek (do usuwniania nadanimacji)
    CameraDirection.x = sin(Global::pCameraRotation); // wektor kierunkowy
    CameraDirection.z = cos(Global::pCameraRotation);
    int tr, tc;
    TGroundNode *node;
    glColor3f(1.0f, 1.0f, 1.0f);
    glEnable(GL_LIGHTING);
    int n = 2 * iNumSubRects; //(2*==2km) promień wyświetlanej mapy w sektorach
    int c = GetColFromX(pPosition.x);
    int r = GetRowFromZ(pPosition.z);
    TSubRect *tmp;
    for (node = srGlobal.nRenderHidden; node; node = node->nNext3)
        node->RenderHidden(); // rednerowanie globalnych (nie za często?)
    int i, j, k;
    // 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)
            {
                tmp->LoadNodes(); // oznaczanie aktywnych sektorów
                for (node = tmp->nRenderHidden; node; node = node->nNext3)
                    node->RenderHidden();
                tmp->RenderSounds(); // jeszcze dźwięki pojazdów by się przydały, również
                // niewidocznych
            }
    // renderowanie progresywne - zależne od FPS oraz kierunku patrzenia
    iRendered = 0; // ilość renderowanych sektorów
    vector3 direction;
    for (k = 0; k < Global::iSegmentsRendered; ++k) // sektory w kolejności odległości
    { // przerobione na użycie SectorOrder
        i = SectorOrder[k].x; // na starcie oba >=0
        j = SectorOrder[k].y;
        do
        {
            if (j <= 0)
                i = -i; // pierwszy przebieg: j<=0, i>=0; drugi: j>=0, i<=0; trzeci: j<=0, i<=0
            // czwarty: j>=0, i>=0;
            j = -j; // i oraz j musi być zmienione wcześniej, żeby continue działało
            direction = vector3(i, 0, j); // wektor od kamery do danego sektora
            if (LengthSquared3(direction) > 5) // te blisko są zawsze wyświetlane
            {
                direction = SafeNormalize(direction); // normalizacja
                if (CameraDirection.x * direction.x + CameraDirection.z * direction.z < 0.55)
                    continue; // pomijanie sektorów poza kątem patrzenia
            }
            Rects[(i + c) / iNumSubRects][(j + r) / iNumSubRects]
                .RenderDL(); // kwadrat kilometrowy nie zawsze, bo szkoda FPS
            if ((tmp = FastGetSubRect(i + c, j + r)) != NULL)
                if (tmp->iNodeCount) // o ile są jakieś obiekty, bo po co puste sektory przelatywać
                    pRendered[iRendered++] = tmp; // tworzenie listy sektorów do renderowania
        } while ((i < 0) || (j < 0)); // są 4 przypadki, oprócz i=j=0
    }
    for (i = 0; i < iRendered; i++)
        pRendered[i]->RenderDL(); // renderowanie nieprzezroczystych
    return true;
}

bool TGround::RenderAlphaDL(vector3 pPosition)
{ // renderowanie scenerii z Display List - faza przezroczystych
    glEnable(GL_BLEND);
    glAlphaFunc(GL_GREATER, 0.04); // im mniejsza wartość, tym większa ramka, domyślnie 0.1f
    TGroundNode *node;
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    TSubRect *tmp;
    // Ra: renderowanie progresywne - zależne od FPS oraz kierunku patrzenia
    int i;
    for (i = iRendered - 1; i >= 0; --i) // od najdalszych
    { // przezroczyste trójkąty w oddzielnym cyklu przed modelami
        tmp = pRendered[i];
        for (node = tmp->nRenderRectAlpha; node; node = node->nNext3)
            node->RenderAlphaDL(); // przezroczyste modele
    }
    for (i = iRendered - 1; i >= 0; --i) // od najdalszych
    { // renderowanie przezroczystych modeli oraz pojazdów
        pRendered[i]->RenderAlphaDL();
    }
    glDisable(GL_LIGHTING); // linie nie powinny świecić
    for (i = iRendered - 1; i >= 0; --i) // od najdalszych
    { // druty na końcu, żeby się nie robiły białe plamy na tle lasu
        tmp = pRendered[i];
        for (node = tmp->nRenderWires; node; node = node->nNext3)
            node->RenderAlphaDL(); // druty
    }
    return true;
}

bool TGround::RenderVBO(vector3 pPosition)
{ // renderowanie scenerii z VBO - faza nieprzezroczystych
    glDisable(GL_BLEND);
    glAlphaFunc(GL_GREATER, 0.45); // im mniejsza wartość, tym większa ramka, domyślnie 0.1f
    ++TGroundRect::iFrameNumber; // zwięszenie licznika ramek
    CameraDirection.x = sin(Global::pCameraRotation); // wektor kierunkowy
    CameraDirection.z = cos(Global::pCameraRotation);
    int tr, tc;
    TGroundNode *node;
    glColor3f(1.0f, 1.0f, 1.0f);
    glEnable(GL_LIGHTING);
    int n = 2 * iNumSubRects; //(2*==2km) promień wyświetlanej mapy w sektorach
    int c = GetColFromX(pPosition.x);
    int r = GetRowFromZ(pPosition.z);
    TSubRect *tmp;
    for (node = srGlobal.nRenderHidden; node; node = node->nNext3)
        node->RenderHidden(); // rednerowanie globalnych (nie za często?)
    int i, j, k;
    // 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)
            {
                for (node = tmp->nRenderHidden; node; node = node->nNext3)
                    node->RenderHidden();
                tmp->RenderSounds(); // jeszcze dźwięki pojazdów by się przydały, również
                // niewidocznych
            }
        }
    // renderowanie progresywne - zależne od FPS oraz kierunku patrzenia
    iRendered = 0; // ilość renderowanych sektorów
    vector3 direction;
    for (k = 0; k < Global::iSegmentsRendered; ++k) // sektory w kolejności odległości
    { // przerobione na użycie SectorOrder
        i = SectorOrder[k].x; // na starcie oba >=0
        j = SectorOrder[k].y;
        do
        {
            if (j <= 0)
                i = -i; // pierwszy przebieg: j<=0, i>=0; drugi: j>=0, i<=0; trzeci: j<=0, i<=0
            // czwarty: j>=0, i>=0;
            j = -j; // i oraz j musi być zmienione wcześniej, żeby continue działało
            direction = vector3(i, 0, j); // wektor od kamery do danego sektora
            if (LengthSquared3(direction) > 5) // te blisko są zawsze wyświetlane
            {
                direction = SafeNormalize(direction); // normalizacja
                if (CameraDirection.x * direction.x + CameraDirection.z * direction.z < 0.55)
                    continue; // pomijanie sektorów poza kątem patrzenia
            }
            Rects[(i + c) / iNumSubRects][(j + r) / iNumSubRects]
                .RenderVBO(); // kwadrat kilometrowy nie zawsze, bo szkoda FPS
            if ((tmp = FastGetSubRect(i + c, j + r)) != NULL)
                if (tmp->iNodeCount) // jeżeli są jakieś obiekty, bo po co puste sektory przelatywać
                    pRendered[iRendered++] = tmp; // tworzenie listy sektorów do renderowania
        } while ((i < 0) || (j < 0)); // są 4 przypadki, oprócz i=j=0
    }
    // dodać rednerowanie terenu z E3D - jedno VBO jest używane dla całego modelu, chyba że jest ich
    // więcej
    if (Global::pTerrainCompact)
        Global::pTerrainCompact->TerrainRenderVBO(TGroundRect::iFrameNumber);
    for (i = 0; i < iRendered; i++)
    { // renderowanie nieprzezroczystych
        pRendered[i]->RenderVBO();
    }
    return true;
}

bool TGround::RenderAlphaVBO(vector3 pPosition)
{ // renderowanie scenerii z VBO - faza przezroczystych
    glEnable(GL_BLEND);
    glAlphaFunc(GL_GREATER, 0.04); // im mniejsza wartość, tym większa ramka, domyślnie 0.1f
    TGroundNode *node;
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    TSubRect *tmp;
    int i;
    for (i = iRendered - 1; i >= 0; --i) // od najdalszych
    { // renderowanie przezroczystych trójkątów sektora
        tmp = pRendered[i];
        tmp->LoadNodes(); // ewentualne tworzenie siatek
        if (tmp->StartVBO())
        {
            for (node = tmp->nRenderRectAlpha; node; node = node->nNext3)
                if (node->iVboPtr >= 0)
                    node->RenderAlphaVBO(); // nieprzezroczyste obiekty terenu
            tmp->EndVBO();
        }
    }
    for (i = iRendered - 1; i >= 0; --i) // od najdalszych
        pRendered[i]->RenderAlphaVBO(); // przezroczyste modeli oraz pojazdy
    glDisable(GL_LIGHTING); // linie nie powinny świecić
    for (i = iRendered - 1; i >= 0; --i) // od najdalszych
    { // druty na końcu, żeby się nie robiły białe plamy na tle lasu
        tmp = pRendered[i];
        if (tmp->StartVBO())
        {
            for (node = tmp->nRenderWires; node; node = node->nNext3)
                node->RenderAlphaVBO(); // przezroczyste modele
            tmp->EndVBO();
        }
    }
    return true;
};

//---------------------------------------------------------------------------
void TGround::Navigate(String ClassName, UINT Msg, WPARAM wParam, LPARAM lParam)
{ // wysłanie komunikatu do sterującego
    HWND h = FindWindow(ClassName.c_str(), 0); // można by to zapamiętać
    if (h == 0)
        h = FindWindow(0, ClassName.c_str()); // można by to zapamiętać
    SendMessage(h, Msg, wParam, lParam);
};
//--------------------------------
void TGround::WyslijEvent(const AnsiString &e, const AnsiString &d)
{ // Ra: jeszcze do wyczyszczenia
    DaneRozkaz r;
    r.iSygn = 'EU07';
    r.iComm = 2; // 2 - event
    int i = e.Length(), j = d.Length();
    r.cString[0] = char(i);
    strcpy(r.cString + 1, e.c_str()); // zakończony zerem
    r.cString[i + 2] = char(j); // licznik po zerze kończącym
    strcpy(r.cString + 3 + i, d.c_str()); // zakończony zerem
    COPYDATASTRUCT cData;
    cData.dwData = 'EU07'; // sygnatura
    cData.cbData = 12 + i + j; // 8+dwa liczniki i dwa zera kończące
    cData.lpData = &r;
    Navigate("TEU07SRK", WM_COPYDATA, (WPARAM)Global::hWnd, (LPARAM)&cData);
	CommLog(AnsiString(Now()) + " " + IntToStr(r.iComm) + " " + e + " sent");
};
//---------------------------------------------------------------------------
void TGround::WyslijUszkodzenia(const AnsiString &t, char fl)
{ // wysłanie informacji w postaci pojedynczego tekstu
	DaneRozkaz r;
	r.iSygn = 'EU07';
	r.iComm = 13; // numer komunikatu
	int i = t.Length();
	r.cString[0] = char(fl);
	r.cString[1] = char(i);
	strcpy(r.cString + 2, t.c_str()); // z zerem kończącym
	COPYDATASTRUCT cData;
	cData.dwData = 'EU07'; // sygnatura
	cData.cbData = 11 + i; // 8+licznik i zero kończące
	cData.lpData = &r;
	Navigate("TEU07SRK", WM_COPYDATA, (WPARAM)Global::hWnd, (LPARAM)&cData);
	CommLog(AnsiString(Now()) + " " + IntToStr(r.iComm) + " " + t + " sent");
};
//---------------------------------------------------------------------------
void TGround::WyslijString(const AnsiString &t, int n)
{ // wysłanie informacji w postaci pojedynczego tekstu
    DaneRozkaz r;
    r.iSygn = 'EU07';
    r.iComm = n; // numer komunikatu
    int i = t.Length();
    r.cString[0] = char(i);
    strcpy(r.cString + 1, t.c_str()); // z zerem kończącym
    COPYDATASTRUCT cData;
    cData.dwData = 'EU07'; // sygnatura
    cData.cbData = 10 + i; // 8+licznik i zero kończące
    cData.lpData = &r;
    Navigate("TEU07SRK", WM_COPYDATA, (WPARAM)Global::hWnd, (LPARAM)&cData);
	CommLog(AnsiString(Now()) + " " + IntToStr(r.iComm) + " " + t + " sent");
};
//---------------------------------------------------------------------------
void TGround::WyslijWolny(const AnsiString &t)
{ // Ra: jeszcze do wyczyszczenia
    WyslijString(t, 4); // tor wolny
};
//--------------------------------
void TGround::WyslijNamiary(TGroundNode *t)
{ // wysłanie informacji o pojeździe - (float), długość ramki będzie zwiększana w miarę potrzeby
    // WriteLog("Wysylam pojazd");
    DaneRozkaz r;
    r.iSygn = 'EU07';
    r.iComm = 7; // 7 - dane pojazdu
    int i = 32, j = t->asName.Length();
    r.iPar[0] = i; // ilość danych liczbowych
    r.fPar[1] = Global::fTimeAngleDeg / 360.0; // aktualny czas (1.0=doba)
    r.fPar[2] = t->DynamicObject->MoverParameters->Loc.X; // pozycja X
    r.fPar[3] = t->DynamicObject->MoverParameters->Loc.Y; // pozycja Y
    r.fPar[4] = t->DynamicObject->MoverParameters->Loc.Z; // pozycja Z
    r.fPar[5] = t->DynamicObject->MoverParameters->V; // prędkość ruchu X
    r.fPar[6] = t->DynamicObject->MoverParameters->nrot * M_PI *
                t->DynamicObject->MoverParameters->WheelDiameter; // prędkość obrotowa kóŁ
    r.fPar[7] = 0; // prędkość ruchu Z
    r.fPar[8] = t->DynamicObject->MoverParameters->AccS; // przyspieszenie X
    r.fPar[9] = t->DynamicObject->MoverParameters->AccN; // przyspieszenie Y //na razie nie
    r.fPar[10] = t->DynamicObject->MoverParameters->AccV; // przyspieszenie Z
    r.fPar[11] = t->DynamicObject->MoverParameters->DistCounter; // przejechana odległość w km
    r.fPar[12] = t->DynamicObject->MoverParameters->PipePress; // ciśnienie w PG
    r.fPar[13] = t->DynamicObject->MoverParameters->ScndPipePress; // ciśnienie w PZ
    r.fPar[14] = t->DynamicObject->MoverParameters->BrakePress; // ciśnienie w CH
    r.fPar[15] = t->DynamicObject->MoverParameters->Compressor; // ciśnienie w ZG
    r.fPar[16] = t->DynamicObject->MoverParameters->Itot; // Prąd całkowity
    r.iPar[17] = t->DynamicObject->MoverParameters->MainCtrlPos; // Pozycja NJ
    r.iPar[18] = t->DynamicObject->MoverParameters->ScndCtrlPos; // Pozycja NB
    r.iPar[19] = t->DynamicObject->MoverParameters->MainCtrlActualPos; // Pozycja jezdna
    r.iPar[20] = t->DynamicObject->MoverParameters->ScndCtrlActualPos; // Pozycja bocznikowania
    r.iPar[21] = t->DynamicObject->MoverParameters->ScndCtrlActualPos; // Pozycja bocznikowania
    r.iPar[22] = t->DynamicObject->MoverParameters->ResistorsFlag * 1 +
                 t->DynamicObject->MoverParameters->ConverterFlag * 2 +
                 +t->DynamicObject->MoverParameters->CompressorFlag * 4 +
                 t->DynamicObject->MoverParameters->Mains * 8 +
                 +t->DynamicObject->MoverParameters->DoorLeftOpened * 16 +
                 t->DynamicObject->MoverParameters->DoorRightOpened * 32 +
                 +t->DynamicObject->MoverParameters->FuseFlag * 64 +
                 t->DynamicObject->MoverParameters->DepartureSignal * 128;
    // WriteLog("Zapisalem stare");
    // WriteLog("Mam patykow "+IntToStr(t->DynamicObject->iAnimType[ANIM_PANTS]));
    for (int p = 0; p < 4; p++)
    {
        //   WriteLog("Probuje pant "+IntToStr(p));
        if (p < t->DynamicObject->iAnimType[ANIM_PANTS])
        {
            r.fPar[23 + p] = t->DynamicObject->pants[p].fParamPants->PantWys; // stan pantografów 4
            //     WriteLog("Zapisalem pant "+IntToStr(p));
        }
        else
        {
            r.fPar[23 + p] = -2;
            //     WriteLog("Nie mam pant "+IntToStr(p));
        }
    }
    // WriteLog("Zapisalem pantografy");
    for (int p = 0; p < 3; p++)
        r.fPar[27 + p] =
            t->DynamicObject->MoverParameters->ShowCurrent(p + 1); // amperomierze kolejnych grup
    // WriteLog("zapisalem prady");
    r.iPar[30] = t->DynamicObject->MoverParameters->WarningSignal; // trabienie
    r.fPar[31] = t->DynamicObject->MoverParameters->RunningTraction.TractionVoltage; // napiecie WN
    // WriteLog("Parametry gotowe");
    i <<= 2; // ilość bajtów
    r.cString[i] = char(j); // na końcu nazwa, żeby jakoś zidentyfikować
    strcpy(r.cString + i + 1, t->asName.c_str()); // zakończony zerem
    COPYDATASTRUCT cData;
    cData.dwData = 'EU07'; // sygnatura
    cData.cbData = 10 + i + j; // 8+licznik i zero kończące
    cData.lpData = &r;
    // WriteLog("Ramka gotowa");
    Navigate("TEU07SRK", WM_COPYDATA, (WPARAM)Global::hWnd, (LPARAM)&cData);
    // WriteLog("Ramka poszla!");
	CommLog(AnsiString(Now()) + " " + IntToStr(r.iComm) + " " + t->asName + " sent");
};
//
void TGround::WyslijObsadzone()
{   // wysłanie informacji o pojeździe
	DaneRozkaz2 r;
	r.iSygn = 'EU07';
	r.iComm = 12;   // kod 12
	for (int i; i<1984; i++) r.cString[i] = 0;

	int i = 0;
	for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext)
		if (Current->DynamicObject->Mechanik)
		{
			strcpy(r.cString + 64 * i, Current->DynamicObject->asName.c_str());
			r.fPar[16 * i + 4] = Current->DynamicObject->GetPosition().x;
			r.fPar[16 * i + 5] = Current->DynamicObject->GetPosition().y;
			r.fPar[16 * i + 6] = Current->DynamicObject->GetPosition().z;
			r.iPar[16 * i + 7] = Current->DynamicObject->Mechanik->GetAction();
			strcpy(r.cString + 64 * i + 32, Current->DynamicObject->GetTrack()->IsolatedName().c_str());
			strcpy(r.cString + 64 * i + 48, Current->DynamicObject->Mechanik->Timetable()->TrainName.c_str());
			i++;
			if (i>30) break;
		}
	while (i <= 30)
	{
		strcpy(r.cString + 64 * i, AnsiString("none").c_str());
		r.fPar[16 * i + 4] = 1;
		r.fPar[16 * i + 5] = 2;
		r.fPar[16 * i + 6] = 3;
		r.iPar[16 * i + 7] = 0;
		strcpy(r.cString + 64 * i + 32, AnsiString("none").c_str());
		strcpy(r.cString + 64 * i + 48, AnsiString("none").c_str());
		i++;
	}

	COPYDATASTRUCT cData;
	cData.dwData = 'EU07';     // sygnatura
	cData.cbData = 8 + 1984; // 8+licznik i zero kończące
	cData.lpData = &r;
	// WriteLog("Ramka gotowa");
	Navigate("TEU07SRK", WM_COPYDATA, (WPARAM)Global::hWnd, (LPARAM)&cData);
	CommLog(AnsiString(Now()) + " " + IntToStr(r.iComm) + " obsadzone" + " sent");
}

//--------------------------------
void TGround::WyslijParam(int nr, int fl)
{ // wysłanie parametrów symulacji w ramce (nr) z flagami (fl)
    DaneRozkaz r;
    r.iSygn = 'EU07';
    r.iComm = nr; // zwykle 5
    r.iPar[0] = fl; // flagi istotności kolejnych parametrów
    int i = 0; // domyślnie brak danych
    switch (nr)
    { // można tym przesyłać różne zestawy parametrów
    case 5: // czas i pauza
        r.fPar[1] = Global::fTimeAngleDeg / 360.0; // aktualny czas (1.0=doba)
        r.iPar[2] = Global::iPause; // stan zapauzowania
        i = 8; // dwa parametry po 4 bajty każdy
        break;
    }
    COPYDATASTRUCT cData;
    cData.dwData = 'EU07'; // sygnatura
    cData.cbData = 12 + i; // 12+rozmiar danych
    cData.lpData = &r;
    Navigate("TEU07SRK", WM_COPYDATA, (WPARAM)Global::hWnd, (LPARAM)&cData);
};
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
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, k;
    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 (k = 0; k < node->pTrack->iNumDynamics; k++)
                            if (mech ? (node->pTrack->Dynamics[k]->Mechanik != NULL) :
                                       true) // czy ma mieć obsadę
                                if ((sqd = SquareMagnitude(
                                         node->pTrack->Dynamics[k]->GetPosition() - pPosition)) <
                                    sqm)
                                {
                                    sqm = sqd; // nowa odległość
                                    dyn = node->pTrack->Dynamics[k]; // 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, k;
    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 (k = 0; k < node->pTrack->iNumDynamics; k++)
                            if (mech ? (node->pTrack->Dynamics[k]->Mechanik != NULL) :
                                       true) // czy ma mieć obsadę
                            {
                                if ((sqd = SquareMagnitude(
                                         node->pTrack->Dynamics[k]->HeadPosition() - pPosition)) <
                                    sqm)
                                {
                                    sqm = sqd; // nowa odległość
                                    dyn = node->pTrack->Dynamics[k]; // nowy lider
                                }
                                if ((sqd = SquareMagnitude(
                                         node->pTrack->Dynamics[k]->RearPosition() - pPosition)) <
                                    sqm)
                                {
                                    sqm = sqd; // nowa odległość
                                    dyn = node->pTrack->Dynamics[k]; // nowy lider
                                }
                            }
    return dyn;
};
//---------------------------------------------------------------------------
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);
                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(const AnsiString &f){
    // Ra: wczytanie trójkątów terenu z pliku E3D
};

//---------------------------------------------------------------------------
void TGround::TerrainWrite()
{ // Ra: zapisywanie trójkątów terenu do pliku E3D
    if (Global::pTerrainCompact->TerrainLoaded())
        return; // jeśli zostało wczytane, to nie ma co dalej robić
    if (Global::asTerrainModel.IsEmpty())
        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
    TSubModel *st; // wskaźnik roboczy na submodel tekstury
    // 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;
    float8 *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(AnsiString(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:
                            // ver=sk->TrianglePtr(TTexturesManager::GetName(Current->TextureID).c_str(),Current->iNumVerts);
                            // //wskaźnik na początek
                            ver = sk->TrianglePtr(Current->TextureID, Current->iVboPtr,
                                                  Current->Ambient, Current->Diffuse,
                                                  Current->Specular); // wskaźnik na początek
                            // WriteLog("Zapis "+AnsiString(Current->iNumVerts)+" trójkątów w
                            // ("+AnsiString(i)+","+AnsiString(j)+") od
                            // "+AnsiString(Current->iVboPtr)+" dla
                            // "+AnsiString(Current->TextureID));
                            Current->iVboPtr = -1; // bo to było tymczasowo używane
                            for (k = 0; k < Current->iNumVerts; ++k)
                            { // przepisanie współrzędnych
                                ver[k].Point.x = Current->Vertices[k].Point.x;
                                ver[k].Point.y = Current->Vertices[k].Point.y;
                                ver[k].Point.z = Current->Vertices[k].Point.z;
                                ver[k].Normal.x = Current->Vertices[k].Normal.x;
                                ver[k].Normal.y = Current->Vertices[k].Normal.y;
                                ver[k].Normal.z = Current->Vertices[k].Normal.z;
                                ver[k].tu = Current->Vertices[k].tu;
                                ver[k].tv = Current->Vertices[k].tv;
                            }
                            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(AnsiString("models\\" + Global::asTerrainModel).c_str());
};
//---------------------------------------------------------------------------

void TGround::TrackBusyList()
{ // wysłanie informacji o wszystkich zajętych odcinkach
    TGroundNode *Current;
    TTrack *Track;
    AnsiString name;
    for (Current = nRootOfType[TP_TRACK]; Current; Current = Current->nNext)
        if (!Current->asName.IsEmpty()) // musi być nazwa
            if (Current->pTrack->iNumDynamics) // osi to chyba nie ma jak policzyć
                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ść
            WyslijString(Current->asName, 11); // zajęty
        else
            WyslijString(Current->asName, 10); // wolny
    WyslijString("none", 10); // informacja o końcu listy
};
//---------------------------------------------------------------------------

void TGround::IsolatedBusy(const AnsiString 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ść
            {
                WyslijString(Current->asName, 11); // zajęty
                return; // nie sprawdzaj dalszych
            }
    WyslijString(t, 10); // wolny
};
//---------------------------------------------------------------------------

void TGround::Silence(vector3 gdzie)
{ // wyciszenie wszystkiego w sektorach przed przeniesieniem kamery z (gdzie)
    int tr, tc;
    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, k;
    // 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ć
            }
};
//---------------------------------------------------------------------------
