diff --git a/.clang-format b/.clang-format index 9c9c0863..be220828 100644 --- a/.clang-format +++ b/.clang-format @@ -10,6 +10,8 @@ BreakBeforeTernaryOperators: false AllowShortBlocksOnASingleLine: false AllowShortIfStatementsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty -ColumnLimit: 100 +ColumnLimit: 200 SortIncludes: false +UseTab: ForIndentation +TabWidth: 4 --- diff --git a/.gitignore b/.gitignore index 6b5c69ab..3cf0aaaf 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,8 @@ builds/build_* builds/*.zip builds/deps_* +doc/ + CMakeLists.txt.user *.vcxproj diff --git a/AirCoupler.cpp b/AirCoupler.cpp index 5c6c0b09..32f814fd 100644 --- a/AirCoupler.cpp +++ b/AirCoupler.cpp @@ -10,69 +10,77 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "AirCoupler.h" -TAirCoupler::TAirCoupler() +AirCoupler::AirCoupler() { Clear(); } -TAirCoupler::~TAirCoupler() +AirCoupler::~AirCoupler() { } -int TAirCoupler::GetStatus() -{ // zwraca 1, jeśli istnieje model prosty, 2 gdy skośny - int x = 0; - if (pModelOn) - x = 1; - if (pModelxOn) - x = 2; - return x; +/** + * \return 1 when \(straight\) TModel3d \c ModelOn exists + * \return 2 when \(slanted\) TModel3d \c ModelxOn exists + * \return 0 when neither of them exist + */ +int AirCoupler::GetStatus() +{ + if (ModelOn) return 1; + if (ModelxOn) return 2; + return 0; } -void TAirCoupler::Clear() -{ // zerowanie wskaźników - pModelOn = NULL; - pModelOff = NULL; - pModelxOn = NULL; - bOn = false; - bxOn = false; +/** + * Reset pointers and variables. + */ +void AirCoupler::Clear() +{ + ModelOn = NULL; + ModelOff = NULL; + ModelxOn = NULL; + On = false; + xOn = false; } -void TAirCoupler::Init(std::string const &asName, TModel3d *pModel) -{ // wyszukanie submodeli - if (!pModel) - return; // nie ma w czym szukać - pModelOn = pModel->GetFromName( asName + "_on" ); // połączony na wprost - pModelOff = pModel->GetFromName( asName + "_off" ); // odwieszony - pModelxOn = pModel->GetFromName( asName + "_xon" ); // połączony na skos +/** + * Looks for submodels in the model and updates pointers. + */ +void AirCoupler::Init(std::string const &asName, TModel3d *Model) +{ + if (!Model) + return; + ModelOn = Model->GetFromName(asName + "_on"); // Straight connect. + ModelOff = Model->GetFromName(asName + "_off"); // Not connected. Hung up. + ModelxOn = Model->GetFromName(asName + "_xon"); // Slanted connect. } - -void TAirCoupler::Load(cParser *Parser, TModel3d *pModel) +/** + * Gets name of submodel \(from cParser \b *Parser\), + * looks for it in the TModel3d \b *Model and update pointers. + * If submodel is not found, reset pointers. +*/ +void AirCoupler::Load(cParser *Parser, TModel3d *Model) { std::string name = Parser->getToken(); - if( pModel ) { - - Init( name, pModel ); + if(Model) + { + Init(name, Model); } else { - pModelOn = NULL; - pModelxOn = NULL; - pModelOff = NULL; + ModelOn = NULL; + ModelxOn = NULL; + ModelOff = NULL; } } -void TAirCoupler::Update() +// Update submodels visibility. +void AirCoupler::Update() { - // if ((pModelOn!=NULL) && (pModelOn!=NULL)) - { - if (pModelOn) - pModelOn->iVisible = bOn; - if (pModelOff) - pModelOff->iVisible = !(bOn || bxOn); - if (pModelxOn) - pModelxOn->iVisible = bxOn; - } + if (ModelOn) + ModelOn->iVisible = On; + if (ModelOff) + ModelOff->iVisible = !(On || xOn); + if (ModelxOn) + ModelxOn->iVisible = xOn; } - -//--------------------------------------------------------------------------- diff --git a/AirCoupler.h b/AirCoupler.h index 8cdebaea..fbb2bd1c 100644 --- a/AirCoupler.h +++ b/AirCoupler.h @@ -12,42 +12,40 @@ http://mozilla.org/MPL/2.0/. #include "Model3d.h" #include "parser.h" -class TAirCoupler +class AirCoupler { - private: - // TButtonType eType; - TSubModel *pModelOn, *pModelOff, *pModelxOn; - bool bOn; - bool bxOn; +private: + TSubModel *ModelOn, *ModelOff, *ModelxOn; + bool On; + bool xOn; void Update(); - public: - TAirCoupler(); - ~TAirCoupler(); +public: + AirCoupler(); + ~AirCoupler(); + ///Reset members. void Clear(); - inline void TurnOn() - { - bOn = true; - bxOn = false; - Update(); - }; - inline void TurnOff() - { - bOn = false; - bxOn = false; - Update(); - }; - inline void TurnxOn() - { - bOn = false; - bxOn = true; - Update(); - }; - // inline bool Active() { if ((pModelOn)||(pModelOff)) return true; return false;}; + ///Looks for submodels. + void Init(std::string const &asName, TModel3d *Model); + ///Loads info about coupler. + void Load(cParser *Parser, TModel3d *Model); int GetStatus(); - void Init(std::string const &asName, TModel3d *pModel); - void Load(cParser *Parser, TModel3d *pModel); - // bool Render(); + inline void TurnOn() ///Turns on straight coupler. + { + On = true; + xOn = false; + Update(); + }; + inline void TurnOff() ///Turns on disconnected coupler. + { + On = false; + xOn = false; + Update(); + }; + inline void TurnxOn() ///Turns on slanted coupler. + { + On = false; + xOn = true; + Update(); + }; }; - -//--------------------------------------------------------------------------- diff --git a/AnimModel.cpp b/AnimModel.cpp index b956331a..77429ef0 100644 --- a/AnimModel.cpp +++ b/AnimModel.cpp @@ -15,15 +15,12 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "AnimModel.h" -#include "Globals.h" -#include "Logs.h" -#include "usefull.h" -#include "McZapkie/mctools.h" -#include "Timer.h" #include "MdlMngr.h" -// McZapkie: -#include "renderer.h" -//--------------------------------------------------------------------------- +#include "simulation.h" +#include "Globals.h" +#include "Timer.h" +#include "Logs.h" + TAnimContainer *TAnimModel::acAnimList = NULL; TAnimAdvanced::TAnimAdvanced(){}; @@ -232,9 +229,10 @@ void TAnimContainer::UpdateModel() { fTranslateSpeed = 0.0; // wyłączenie przeliczania wektora if (LengthSquared3(vTranslation) <= 0.0001) // jeśli jest w punkcie początkowym iAnim &= ~2; // wyłączyć zmianę pozycji submodelu - if (evDone) - Global::AddToQuery(evDone, NULL); // wykonanie eventu informującego o - // zakończeniu + if( evDone ) { + // wykonanie eventu informującego o zakończeniu + simulation::Events.AddToQuery( evDone, nullptr ); + } } } if (fRotateSpeed != 0.0) @@ -299,9 +297,10 @@ void TAnimContainer::UpdateModel() { if (!anim) { // nie potrzeba przeliczać już fRotateSpeed = 0.0; - if (evDone) - Global::AddToQuery(evDone, NULL); // wykonanie eventu informującego o - // zakończeniu + if( evDone ) { + // wykonanie eventu informującego o zakończeniu + simulation::Events.AddToQuery( evDone, nullptr ); + } } } if( fAngleSpeed != 0.f ) { @@ -330,7 +329,7 @@ void TAnimContainer::PrepareModel() fAngleSpeed = 0.0; // wyłączenie przeliczania wektora if( evDone ) { // wykonanie eventu informującego o zakończeniu - Global::AddToQuery( evDone, NULL ); + simulation::Events.AddToQuery( evDone, nullptr ); } } else @@ -406,23 +405,12 @@ void TAnimContainer::EventAssign(TEvent *ev) //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ -TAnimModel::TAnimModel() -{ - pRoot = NULL; - pModel = NULL; - iNumLights = 0; - fBlinkTimer = 0; - - for (int i = 0; i < iMaxNumLights; ++i) - { - LightsOn[i] = LightsOff[i] = nullptr; // normalnie nie ma - lsLights[i] = ls_Off; // a jeśli są, to wyłączone +TAnimModel::TAnimModel( scene::node_data const &Nodedata ) : basic_node( Nodedata ) { + // TODO: wrap these in a tuple and move to underlying model + for( int index = 0; index < iMaxNumLights; ++index ) { + LightsOn[ index ] = LightsOff[ index ] = nullptr; // normalnie nie ma + lsLights[ index ] = ls_Off; // a jeśli są, to wyłączone } - vAngle.x = vAngle.y = vAngle.z = 0.0; // zerowanie obrotów egzemplarza - pAdvanced = NULL; // nie ma zaawansowanej animacji - fDark = 0.25f; // standardowy próg zaplania - fOnTime = 0.66f; - fOffTime = fOnTime + 0.66f; } TAnimModel::~TAnimModel() @@ -572,6 +560,15 @@ void TAnimModel::RaAnimate( unsigned int const Framestamp ) { m_framestamp = Framestamp; }; +// calculates piece's bounding radius +void +TAnimModel::radius_() { + + if( pModel != nullptr ) { + m_area.radius = pModel->bounding_radius(); + } +} + void TAnimModel::RaPrepare() { // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza bool state; // stan światła @@ -762,21 +759,27 @@ void TAnimModel::LightSet(int n, float v) { // ustawienie światła (n) na wartość (v) if (n >= iMaxNumLights) return; // przekroczony zakres - lsLights[n] = TLightState(int(v)); - switch (lsLights[n]) - { // interpretacja ułamka zależnie od typu - case 0: // ustalenie czasu migotania, t<1s (f>1Hz), np. 0.1 => t=0.1 (f=10Hz) - break; - case 1: // ustalenie wypełnienia ułamkiem, np. 1.25 => zapalony przez 1/4 okresu - break; - case 2: // ustalenie częstotliwości migotania, f<1Hz (t>1s), np. 2.2 => f=0.2Hz (t=5s) - break; - case 3: // zapalenie świateł zależne od oświetlenia scenerii - if (v > 3.0) - fDark = v - 3.0; // ustawienie indywidualnego progu zapalania - else - fDark = 0.25; // standardowy próg zaplania - break; + lsLights[ n ] = TLightState( static_cast( v ) ); + switch( lsLights[ n ] ) { + // interpretacja ułamka zależnie od typu + case ls_Off: { + // ustalenie czasu migotania, t<1s (f>1Hz), np. 0.1 => t=0.1 (f=10Hz) + break; + } + case ls_On: { + // ustalenie wypełnienia ułamkiem, np. 1.25 => zapalony przez 1/4 okresu + break; + } + case ls_Blink: { + // ustalenie częstotliwości migotania, f<1Hz (t>1s), np. 2.2 => f=0.2Hz (t=5s) + break; + } + case ls_Dark: { + // zapalenie świateł zależne od oświetlenia scenerii + if( v > 3.0 ) { fDark = v - 3.0; } // ustawienie indywidualnego progu zapalania + else { fDark = DefaultDarkThresholdLevel; } // standardowy próg zaplania + break; + } } }; //--------------------------------------------------------------------------- diff --git a/AnimModel.h b/AnimModel.h index d0510485..adcd8d9d 100644 --- a/AnimModel.h +++ b/AnimModel.h @@ -19,15 +19,16 @@ http://mozilla.org/MPL/2.0/. #include "DynObj.h" const int iMaxNumLights = 8; +float const DefaultDarkThresholdLevel { 0.325f }; // typy stanu świateł -typedef enum +enum TLightState { ls_Off = 0, // zgaszone ls_On = 1, // zapalone ls_Blink = 2, // migające ls_Dark = 3 // Ra: zapalajce się automatycznie, gdy zrobi się ciemno -} TLightState; +}; class TAnimVocaloidFrame { // ramka animacji typu Vocaloid Motion Data z programu MikuMikuDance @@ -77,16 +78,9 @@ class TAnimContainer TAnimContainer(); ~TAnimContainer(); bool Init(TSubModel *pNewSubModel); - // std::string inline GetName() { return - // std::string(pSubModel?pSubModel->asName.c_str():""); }; - // std::string inline GetName() { return std::string(pSubModel?pSubModel->pName:""); - // }; - std::string NameGet() - { - return (pSubModel ? pSubModel->pName : ""); - }; - // void SetRotateAnim(vector3 vNewRotateAxis, double fNewDesiredAngle, double - // fNewRotateSpeed, bool bResetAngle=false); + inline + std::string NameGet() { + return (pSubModel ? pSubModel->pName : ""); }; void SetRotateAnim(vector3 vNewRotateAngles, double fNewRotateSpeed); void SetTranslateAnim(vector3 vNewTranslate, double fNewSpeed); void AnimSetVMD(double fNewSpeed); @@ -94,24 +88,20 @@ class TAnimContainer void UpdateModel(); void UpdateModelIK(); bool InMovement(); // czy w trakcie animacji? - double AngleGet() - { - return vRotateAngles.z; - }; // jednak ostatnia, T3D ma inny układ - vector3 TransGet() - { - return vector3(-vTranslation.x, vTranslation.z, vTranslation.y); - }; // zmiana, bo T3D ma inny układ - void WillBeAnimated() - { + inline + double AngleGet() { + return vRotateAngles.z; }; // jednak ostatnia, T3D ma inny układ + inline + vector3 TransGet() { + return vector3(-vTranslation.x, vTranslation.z, vTranslation.y); }; // zmiana, bo T3D ma inny układ + inline + void WillBeAnimated() { if (pSubModel) - pSubModel->WillBeAnimated(); - }; + pSubModel->WillBeAnimated(); }; void EventAssign(TEvent *ev); - TEvent * Event() - { - return evDone; - }; + inline + TEvent * Event() { + return evDone; }; }; class TAnimAdvanced @@ -129,55 +119,76 @@ class TAnimAdvanced }; // opakowanie modelu, określające stan egzemplarza -class TAnimModel { +class TAnimModel : public editor::basic_node { friend class opengl_renderer; - private: - TAnimContainer *pRoot; // pojemniki sterujące, tylko dla aniomowanych submodeli - TModel3d *pModel; - double fBlinkTimer; - int iNumLights; - TSubModel *LightsOn[iMaxNumLights]; // Ra: te wskaźniki powinny być w ramach TModel3d - TSubModel *LightsOff[iMaxNumLights]; - vector3 vAngle; // bazowe obroty egzemplarza względem osi - material_data m_materialdata; - - std::string asText; // tekst dla wyświetlacza znakowego - TAnimAdvanced *pAdvanced { nullptr }; - void Advanced(); - TLightState lsLights[iMaxNumLights]; - float fDark; // poziom zapalanie światła (powinno być chyba powiązane z danym światłem?) - float fOnTime, fOffTime; // były stałymi, teraz mogą być zmienne dla każdego egzemplarza - unsigned int m_framestamp { 0 }; // id of last rendered gfx frame -private: - void RaAnimate( unsigned int const Framestamp ); // przeliczenie animacji egzemplarza - void RaPrepare(); // ustawienie animacji egzemplarza na wzorcu - public: - static TAnimContainer *acAnimList; // lista animacji z eventem, które muszą być przeliczane również bez wyświetlania - inline - material_data const *Material() const { return &m_materialdata; } - - TAnimModel(); +public: +// constructors + TAnimModel( scene::node_data const &Nodedata ); +// destructor ~TAnimModel(); +// methods + static void AnimUpdate( double dt ); bool Init(TModel3d *pNewModel); bool Init(std::string const &asName, std::string const &asReplacableTexture); bool Load(cParser *parser, bool ter = false); TAnimContainer * AddContainer(std::string const &Name); TAnimContainer * GetContainer(std::string const &Name = ""); - int Flags(); - void RaAnglesSet(double a, double b, double c) - { - vAngle.x = a; - vAngle.y = b; - vAngle.z = c; - }; + void RaAnglesSet( glm::vec3 Angles ) { + vAngle.x = Angles.x; + vAngle.y = Angles.y; + vAngle.z = Angles.z; }; + void LightSet( int n, float v ); + void AnimationVND( void *pData, double a, double b, double c, double d ); bool TerrainLoaded(); int TerrainCount(); TSubModel * TerrainSquare(int n); - void AnimationVND(void *pData, double a, double b, double c, double d); - void LightSet(int n, float v); - static void AnimUpdate(double dt); + int Flags(); + inline + material_data const * + Material() const { + return &m_materialdata; } + inline + TModel3d * + Model() { + return pModel; } +// members + static TAnimContainer *acAnimList; // lista animacji z eventem, które muszą być przeliczane również bez wyświetlania + +protected: + // calculates piece's bounding radius + void + radius_(); + +private: +// methods + void RaPrepare(); // ustawienie animacji egzemplarza na wzorcu + void RaAnimate( unsigned int const Framestamp ); // przeliczenie animacji egzemplarza + void Advanced(); +// members + TAnimContainer *pRoot { nullptr }; // pojemniki sterujące, tylko dla aniomowanych submodeli + TModel3d *pModel { nullptr }; + double fBlinkTimer { 0.0 }; + int iNumLights { 0 }; + TSubModel *LightsOn[ iMaxNumLights ]; // Ra: te wskaźniki powinny być w ramach TModel3d + TSubModel *LightsOff[ iMaxNumLights ]; + vector3 vAngle; // bazowe obroty egzemplarza względem osi + material_data m_materialdata; + + std::string asText; // tekst dla wyświetlacza znakowego + TAnimAdvanced *pAdvanced { nullptr }; + TLightState lsLights[ iMaxNumLights ]; + float fDark { DefaultDarkThresholdLevel }; // poziom zapalanie światła (powinno być chyba powiązane z danym światłem?) + float fOnTime { 0.66f }; + float fOffTime { 0.66f + 0.66f }; // były stałymi, teraz mogą być zmienne dla każdego egzemplarza + unsigned int m_framestamp { 0 }; // id of last rendered gfx frame +}; + + + +class instance_table : public basic_table { + }; //--------------------------------------------------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 3762da85..d4c043a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,6 @@ set(SOURCES "Float3d.cpp" "Gauge.cpp" "Globals.cpp" -"Ground.cpp" "Logs.cpp" "McZapkie/friction.cpp" "McZapkie/hamulce.cpp" @@ -72,6 +71,11 @@ set(SOURCES "lua.cpp" "stdafx.cpp" "uart.cpp" +"messaging.cpp" +"scene.cpp" +"scenenode.cpp" +"simulation.cpp" +"vertex.cpp" ) set (ARCH "x86") @@ -183,9 +187,3 @@ target_link_libraries(${PROJECT_NAME} ${LUAJIT_LIBRARIES}) find_package(libserialport REQUIRED) include_directories(${libserialport_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} ${libserialport_LIBRARY}) - -if (UNIX) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -g") -endif() - -#cotire(${PROJECT_NAME}) diff --git a/Camera.cpp b/Camera.cpp index aec7b911..5fb87cba 100644 --- a/Camera.cpp +++ b/Camera.cpp @@ -18,9 +18,6 @@ http://mozilla.org/MPL/2.0/. //--------------------------------------------------------------------------- -// TViewPyramid TCamera::OrgViewPyramid; -//={vector3(-1,1,1),vector3(1,1,1),vector3(-1,-1,1),vector3(1,-1,1),vector3(0,0,0)}; - void TCamera::Init(vector3 NPos, vector3 NAngle) { @@ -37,8 +34,8 @@ void TCamera::Init(vector3 NPos, vector3 NAngle) void TCamera::OnCursorMove(double x, double y) { // McZapkie-170402: zeby mysz dzialala zawsze if (Type==tp_Follow) - Pitch += y; - Yaw += -x; + Yaw -= x; + Pitch -= y; if (Yaw > M_PI) Yaw -= 2 * M_PI; else if (Yaw < -M_PI) @@ -62,7 +59,7 @@ TCamera::OnCommand( command_data const &Command ) { OnCursorMove( reinterpret_cast( Command.param1 ) * 0.005 * Global::fMouseXScale / Global::ZoomFactor, - reinterpret_cast( Command.param2 ) * -0.01 * Global::fMouseYScale / Global::ZoomFactor ); + reinterpret_cast( Command.param2 ) * 0.01 * Global::fMouseYScale / Global::ZoomFactor ); break; } diff --git a/Classes.h b/Classes.h index 504422d8..0bb92279 100644 --- a/Classes.h +++ b/Classes.h @@ -37,7 +37,7 @@ class TMtableTime; // czas dla danego posterunku class TController; // obiekt sterujący pociągiem (AI) -typedef enum +enum TCommandType { // binarne odpowiedniki komend w komórce pamięci cm_Unknown, // ciąg nierozpoznany (nie jest komendą) cm_Ready, // W4 zezwala na odjazd, ale semafor może zatrzymać @@ -51,6 +51,6 @@ typedef enum cm_OutsideStation, cm_Shunt, cm_Command // komenda pobierana z komórki -} TCommandType; +}; #endif diff --git a/Driver.cpp b/Driver.cpp index 1e9533c0..afff1cf0 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -20,7 +20,6 @@ http://mozilla.org/MPL/2.0/. #include "mtable.h" #include "DynObj.h" #include "Event.h" -#include "Ground.h" #include "MemCell.h" #include "World.h" #include "McZapkie/mctools.h" @@ -73,7 +72,17 @@ double GetDistanceToEvent(TTrack const *track, TEvent const *event, double scan_ { // przejście na inny tor track = track->Connected(int(sd), sd); start_dist += (1 == krok) ? 0 : back ? -segment->GetLength() : segment->GetLength(); - return GetDistanceToEvent(track, event, sd, start_dist, ++iter, 1 == krok ? true : false); + if( ( track != nullptr ) + && ( track->eType == tt_Cross ) ) { + // NOTE: tracing through crossroads currently poses risk of tracing through wrong segment + // as it's possible to be performerd before setting a route through the crossroads + // as a stop-gap measure we don't trace through crossroads which should be reasonable in most cases + // TODO: establish route before the scan, or a way for the function to know which route to pick + return start_dist; + } + else { + return GetDistanceToEvent( track, event, sd, start_dist, ++iter, 1 == krok ? true : false ); + } } else { // obliczenie mojego toru @@ -303,7 +312,7 @@ bool TSpeedPos::Update() std::string TSpeedPos::GetName() { if (iFlags & spTrack) // jeśli tor - return trTrack->NameGet(); + return trTrack->name(); else if( iFlags & spEvent ) // jeśli event return evEvent->asName; else @@ -517,7 +526,7 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle) if (pTrack != tLast) // ostatni zapisany w tabelce nie był jeszcze sprawdzony { // jeśli tor nie był jeszcze sprawdzany if( Global::iWriteLogEnabled & 8 ) { - WriteLog( "Speed table for " + OwnerName() + " tracing through track " + pTrack->NameGet() ); + WriteLog( "Speed table for " + OwnerName() + " tracing through track " + pTrack->name() ); } if( ( pEvent = CheckTrackEvent( pTrack, fLastDir ) ) != nullptr ) // jeśli jest semafor na tym torze @@ -580,11 +589,15 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle) // na skrzyżowaniach trzeba wybrać segment, po którym pojedzie pojazd // dopiero tutaj jest ustalany kierunek segmentu na skrzyżowaniu sSpeedTable[iLast].iFlags |= - ((pTrack->CrossSegment( - (fLastDir < 0 ? - tLast->iPrevDirection : - tLast->iNextDirection), - iRouteWanted) & 0xf) << 28); // ostatnie 4 bity pola flag + ( ( pTrack->CrossSegment( + (fLastDir < 0 ? + tLast->iPrevDirection : + tLast->iNextDirection), +/* + iRouteWanted ) +*/ + 1 + std::floor( Random( static_cast(pTrack->RouteCount()) - 0.001 ) ) ) + & 0xf ) << 28 ); // ostatnie 4 bity pola flag sSpeedTable[iLast].iFlags &= ~spReverse; // usunięcie flagi kierunku, bo może być błędna if (sSpeedTable[iLast].iFlags < 0) { sSpeedTable[iLast].iFlags |= spReverse; // ustawienie flagi kierunku na podstawie wybranego segmentu @@ -592,10 +605,12 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle) if (int(fLastDir) * sSpeedTable[iLast].iFlags < 0) { fLastDir = -fLastDir; } +/* if (AIControllFlag) { // dla AI na razie losujemy kierunek na kolejnym skrzyżowaniu iRouteWanted = 1 + Random(3); } +*/ } } else if ( ( pTrack->fRadius != 0.0 ) // odległość na łuku lepiej aproksymować cięciwami @@ -708,7 +723,7 @@ void TController::TableCheck(double fDistance) if (sSpeedTable[i].Update()) { if( Global::iWriteLogEnabled & 8 ) { - WriteLog( "Speed table for " + OwnerName() + " detected switch change at " + sSpeedTable[ i ].trTrack->NameGet() + " (generating fresh trace)" ); + WriteLog( "Speed table for " + OwnerName() + " detected switch change at " + sSpeedTable[ i ].trTrack->name() + " (generating fresh trace)" ); } // usuwamy wszystko za tym torem while (sSpeedTable.back().trTrack != sSpeedTable[i].trTrack) @@ -812,7 +827,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN WriteLog( pVehicle->asName + " as " + TrainParams->TrainName + ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute) - + " skipped " + asNextStop); // informacja + + " passed " + asNextStop); // informacja #endif // przy jakim dystansie (stanie licznika) ma przesunąć na następny postój fLastStopExpDist = mvOccupied->DistCounter + 0.250 + 0.001 * fLength; @@ -1252,7 +1267,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // jeśli ma stać, dostaje komendę od razu go = cm_Command; // komenda z komórki, do wykonania po zatrzymaniu } - else if( sSpeedTable[ i ].fDist <= 20.0 ) { + else if( sSpeedTable[ i ].fDist <= fMaxProximityDist ) { // jeśli ma dociągnąć, to niech dociąga // (moveStopCloser dotyczy dociągania do W4, nie semafora) go = cm_Command; // komenda z komórki, do wykonania po zatrzymaniu @@ -1782,7 +1797,7 @@ void TController::AutoRewident() } if (mvOccupied->TrainType == dt_EZT) { - fAccThreshold = -fBrake_a0[BrakeAccTableSize] - 8 * fBrake_a1[BrakeAccTableSize]; + fAccThreshold = std::max(-fBrake_a0[BrakeAccTableSize] - 8 * fBrake_a1[BrakeAccTableSize], -0.75); fBrakeReaction = 0.25; } else if (ustaw > 16) @@ -2400,7 +2415,7 @@ bool TController::IncBrake() } case Pneumatic: { // NOTE: can't perform just test whether connected vehicle == nullptr, due to virtual couplers formed with nearby vehicles - bool standalone{ true }; + bool standalone { true }; if( ( mvOccupied->TrainType == dt_ET41 ) || ( mvOccupied->TrainType == dt_ET42 ) ) { // NOTE: we're doing simplified checks full of presuptions here. @@ -2415,9 +2430,24 @@ bool TController::IncBrake() } } else { +/* standalone = ( ( mvOccupied->Couplers[ 0 ].CouplingFlag == 0 ) && ( mvOccupied->Couplers[ 1 ].CouplingFlag == 0 ) ); +*/ + if( pVehicles[ 0 ] != pVehicles[ 1 ] ) { + // more detailed version, will use manual braking also for coupled sets of controlled vehicles + auto *vehicle = pVehicles[ 0 ]; // start from first + while( ( true == standalone ) + && ( vehicle != nullptr ) ) { + // NOTE: we could simplify this by doing only check of the rear coupler, but this can be quite tricky in itself + // TODO: add easier ways to access front/rear coupler taking into account vehicle's direction + standalone = + ( ( ( vehicle->MoverParameters->Couplers[ 0 ].CouplingFlag == 0 ) || ( vehicle->MoverParameters->Couplers[ 0 ].CouplingFlag & coupling::control ) ) + && ( ( vehicle->MoverParameters->Couplers[ 1 ].CouplingFlag == 0 ) || ( vehicle->MoverParameters->Couplers[ 1 ].CouplingFlag & coupling::control ) ) ); + vehicle = vehicle->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła) + } + } } if( true == standalone ) { OK = mvOccupied->IncLocalBrakeLevel( @@ -2973,20 +3003,15 @@ void TController::RecognizeCommand() c->Command = ""; // usunięcie obsłużonej komendy } -void TController::PutCommand(std::string NewCommand, double NewValue1, double NewValue2, - const TLocation &NewLocation, TStopReason reason) -{ // wysłanie komendy przez event PutValues, jak pojazd ma obsadę, to wysyła tutaj, a nie do pojazdu - // bezpośrednio - vector3 sl; - sl.x = -NewLocation.X; // zamiana na współrzędne scenerii - sl.z = NewLocation.Y; - sl.y = NewLocation.Z; +void TController::PutCommand(std::string NewCommand, double NewValue1, double NewValue2, const TLocation &NewLocation, TStopReason reason) +{ // wysłanie komendy przez event PutValues, jak pojazd ma obsadę, to wysyła tutaj, a nie do pojazdu bezpośrednio + // zamiana na współrzędne scenerii + glm::dvec3 sl { -NewLocation.X, NewLocation.Z, NewLocation.Y }; if (!PutCommand(NewCommand, NewValue1, NewValue2, &sl, reason)) mvOccupied->PutCommand(NewCommand, NewValue1, NewValue2, NewLocation); } -bool TController::PutCommand(std::string NewCommand, double NewValue1, double NewValue2, - const vector3 *NewLocation, TStopReason reason) +bool TController::PutCommand( std::string NewCommand, double NewValue1, double NewValue2, glm::dvec3 const *NewLocation, TStopReason reason ) { // analiza komendy if (NewCommand == "CabSignal") { // SHP wyzwalane jest przez człon z obsadą, ale obsługiwane przez silnikowy @@ -3639,6 +3664,14 @@ TController::UpdateSituation(double dt) { // dla nastawienia G koniecznie należy wydłużyć drogę na czas reakcji fBrakeDist += 2 * mvOccupied->Vel; } +/* + // take into account effect of gravity (but to stay on safe side of calculations, only downhill) + if( fAccGravity > 0.025 ) { + fBrakeDist *= ( 1.0 + fAccGravity ); + // TBD: use version which shortens route going uphill, too + //fBrakeDist = std::max( fBrakeDist, fBrakeDist * ( 1.0 + fAccGravity ) ); + } +*/ // route scan double routescanrange = ( mvOccupied->Vel > 5.0 ? @@ -3812,8 +3845,11 @@ TController::UpdateSituation(double dt) { CheckVehicles(); // sprawdzić światła nowego składu JumpToNextOrder(); // wykonanie następnej komendy } +/* + // NOTE: disabled as speed limit is decided in another place based on distance to potential target else SetVelocity(2.0, 0.0); // jazda w ustawionym kierunku z prędkością 2 (18s) +*/ } // if (AIControllFlag) //koniec zblokowania, bo była zmienna lokalna } else { @@ -4208,13 +4244,12 @@ TController::UpdateSituation(double dt) { ~(Obey_train | Shunt))) // jedzie w dowolnym trybie albo Wait_for_orders if (fabs(VelSignal) >= 1.0) // 0.1 nie wysyła się do samochodow, bo potem nie ruszą - PutCommand("SetVelocity", VelSignal, VelNext, - NULL); // komenda robi dodatkowe operacje + PutCommand("SetVelocity", VelSignal, VelNext, nullptr); // komenda robi dodatkowe operacje break; case cm_ShuntVelocity: // od wersji 357 Tm nie budzi wyłączonej lokomotywy if (!(OrderList[OrderPos] & ~(Obey_train | Shunt))) // jedzie w dowolnym trybie albo Wait_for_orders - PutCommand("ShuntVelocity", VelSignal, VelNext, NULL); + PutCommand("ShuntVelocity", VelSignal, VelNext, nullptr); else if (iCoupler) // jeśli jedzie w celu połączenia SetVelocity(VelSignal, VelNext); break; @@ -4291,18 +4326,23 @@ TController::UpdateSituation(double dt) { vehicle->fTrackBlock ); double k = coupler->Connected->Vel; // prędkość pojazdu z przodu (zakładając, // że jedzie w tę samą stronę!!!) - if( k < vel + 10 ) { + if( k - vel < 10 ) { // porównanie modułów prędkości [km/h] // zatroszczyć się trzeba, jeśli tamten nie jedzie znacząco szybciej double const distance = vehicle->fTrackBlock - fMaxProximityDist - ( fBrakeDist * 1.15 ); // odległość bezpieczna zależy od prędkości - if( distance < 0 ) { + if( distance < 0.0 ) { // jeśli odległość jest zbyt mała if( k < 10.0 ) // k - prędkość tego z przodu { // jeśli tamten porusza się z niewielką prędkością albo stoi if( OrderCurrentGet() & Connect ) { // jeśli spinanie, to jechać dalej AccPreferred = std::min( 0.25, AccPreferred ); // nie hamuj - VelDesired = Global::Min0RSpeed( 20.0, VelDesired ); + VelDesired = + Global::Min0RSpeed( + VelDesired, + ( vehicle->fTrackBlock > 150.0 ? + 20.0: + 4.0 ) ); VelNext = 2.0; // i pakuj się na tamtego } else { @@ -4345,7 +4385,12 @@ TController::UpdateSituation(double dt) { else { if( OrderCurrentGet() & Connect ) { // if there's something nearby in the connect mode don't speed up too much - VelDesired = Global::Min0RSpeed( 20.0, VelDesired ); + VelDesired = + Global::Min0RSpeed( + VelDesired, + ( vehicle->fTrackBlock > 150.0 ? + 20.0 : + 4.0 ) ); } } } @@ -4452,6 +4497,7 @@ TController::UpdateSituation(double dt) { AbsAccS /= fMass; } AbsAccS_pub = AbsAccS; + AbsAccS_avg = interpolate( AbsAccS_avg, mvOccupied->AccS * iDirection, 0.25 ); #if LOGVELOCITY // WriteLog("VelDesired="+AnsiString(VelDesired)+", @@ -4493,10 +4539,19 @@ TController::UpdateSituation(double dt) { */ if( VelNext == 0.0 ) { if( mvOccupied->CategoryFlag & 1 ) { - // hamowanie tak, aby stanąć - VelDesired = VelNext; - AccDesired = ( VelNext * VelNext - vel * vel ) / ( 25.92 * ( ActualProximityDist + 0.1 - 0.5*fMinProximityDist ) ); - AccDesired = std::min( AccDesired, fAccThreshold ); + // trains + if( ( OrderCurrentGet() & Shunt ) + && ( pVehicles[0]->fTrackBlock < 50.0 ) ) { + // crude detection of edge case, if approaching another vehicle coast slowly until min distance + // this should allow to bunch up trainsets more on sidings + VelDesired = Global::Min0RSpeed( VelDesired, 5.0 ); + } + else { + // hamowanie tak, aby stanąć + VelDesired = VelNext; + AccDesired = ( VelNext * VelNext - vel * vel ) / ( 25.92 * ( ActualProximityDist + 0.1 - 0.5*fMinProximityDist ) ); + AccDesired = std::min( AccDesired, fAccThreshold ); + } } else { // for cars (and others) coast at low speed until we hit min proximity range @@ -4580,26 +4635,53 @@ TController::UpdateSituation(double dt) { // decisions based on current speed if( mvOccupied->CategoryFlag == 1 ) { - // try to estimate increase of current velocity before engaged brakes start working - auto const speedestimate = vel + vel * ( 1.0 - fBrake_a0[ 0 ] ) * AbsAccS; - if( speedestimate > VelDesired ) { - // jesli jedzie za szybko do AKTUALNEGO - if( VelDesired == 0.0 ) { - // jesli stoj, to hamuj, ale i tak juz za pozno :) - AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie - } - else { - if( speedestimate > ( VelDesired + fVelPlus ) ) { - // if it looks like we'll exceed maximum allowed speed start thinking about slight slowing down - AccDesired = std::min( AccDesired, -0.25 ); + + if( fAccGravity < 0.025 ) { + // on flats on uphill we can be less careful + if( vel > VelDesired ) { + // jesli jedzie za szybko do AKTUALNEGO + if( VelDesired == 0.0 ) { + // jesli stoj, to hamuj, ale i tak juz za pozno :) + AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie } else { - // close enough to target to stop accelerating - AccDesired = std::min( - AccDesired, // but don't override decceleration for VelNext - interpolate( // ease off as you close to the target velocity - -0.06, AccPreferred, - clamp( speedestimate - vel, 0.0, fVelPlus ) / fVelPlus ) ); + // slow down, not full stop + if( vel > ( VelDesired + fVelPlus ) ) { + // hamuj tak średnio + AccDesired = std::min( AccDesired, -0.25 ); + } + else { + // o 5 km/h to olej (zacznij luzować) + AccDesired = std::min( + AccDesired, // but don't override decceleration for VelNext + std::max( 0.0, AccPreferred ) ); + } + } + } + } + else { + // going sharply downhill we may need to start braking sooner than usual + // try to estimate increase of current velocity before engaged brakes start working + auto const speedestimate = vel + ( 1.0 - fBrake_a0[ 0 ] ) * 30.0 * AbsAccS; + if( speedestimate > VelDesired ) { + // jesli jedzie za szybko do AKTUALNEGO + if( VelDesired == 0.0 ) { + // jesli stoj, to hamuj, ale i tak juz za pozno :) + AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie + } + else { + if( speedestimate > ( VelDesired + fVelPlus ) ) { + // if it looks like we'll exceed maximum allowed speed start thinking about slight slowing down + AccDesired = std::min( AccDesired, -0.25 ); + } + else { + // close enough to target to stop accelerating + AccDesired = std::min( + AccDesired, // but don't override decceleration for VelNext + interpolate( // ease off as you close to the target velocity + -0.06, AccPreferred, + clamp( speedestimate - vel, 0.0, fVelPlus ) / fVelPlus ) ); + } } } } @@ -4631,8 +4713,10 @@ TController::UpdateSituation(double dt) { // last step sanity check, until the whole calculation is straightened out AccDesired = std::min( AccDesired, AccPreferred ); - if( mvOccupied->CategoryFlag == 1 ) { - // also take into account impact of gravity + + if( ( mvOccupied->CategoryFlag == 1 ) + && ( fAccGravity > 0.025 ) ) { + // going downhill also take into account impact of gravity AccDesired = clamp( AccDesired - fAccGravity, -0.9, 0.9 ); } @@ -4884,41 +4968,19 @@ TController::UpdateSituation(double dt) { } } } - // Mietek-end1 - SpeedSet(); // ciągla regulacja prędkości -#if LOGVELOCITY - WriteLog("BrakePos=" + AnsiString(mvOccupied->BrakeCtrlPos) + ", MainCtrl=" + - AnsiString(mvControlling->MainCtrlPos)); -#endif - - /* //Ra: mamy teraz wskażnik na człon silnikowy, gorzej jak są dwa w - ukrotnieniu... - //zapobieganie poslizgowi w czlonie silnikowym; Ra: Couplers[1] powinno - być - if (Controlling->Couplers[0].Connected!=NULL) - if (TestFlag(Controlling->Couplers[0].CouplingFlag,ctrain_controll)) - if (Controlling->Couplers[0].Connected->SlippingWheels) - if (Controlling->ScndCtrlPos>0?!Controlling->DecScndCtrl(1):true) - { - if (!Controlling->DecMainCtrl(1)) - if (mvOccupied->BrakeCtrlPos==mvOccupied->BrakeCtrlPosNo) - mvOccupied->DecBrakeLevel(); - ++iDriverFailCount; - } - */ - // zapobieganie poslizgowi u nas - if (mvControlling->SlippingWheels) - { - if (!mvControlling->DecScndCtrl(2)) // bocznik na zero - mvControlling->DecMainCtrl(1); - if (mvOccupied->BrakeCtrlPos == - mvOccupied->BrakeCtrlPosNo) // jeśli ostatnia pozycja hamowania - //yB: ten warunek wyżej nie ma sensu - mvOccupied->DecBrakeLevel(); // to cofnij hamulec - else - mvControlling->AntiSlippingButton(); - ++iDriverFailCount; - //mvControlling->SlippingWheels = false; // flaga już wykorzystana + if ((AccDesired < fAccGravity - 0.05) && (AbsAccS < AccDesired - fBrake_a1[0]*0.51)) { + // jak hamuje, to nie tykaj kranu za często + // yB: luzuje hamulec dopiero przy różnicy opóźnień rzędu 0.2 + if( OrderList[ OrderPos ] != Disconnect ) { + // przy odłączaniu nie zwalniamy tu hamulca + DecBrake(); // tutaj zmniejszało o 1 przy odczepianiu + } + fBrakeTime = ( + mvOccupied->BrakeDelayFlag > bdelay_G ? + mvOccupied->BrakeDelay[ 0 ] : + mvOccupied->BrakeDelay[ 2 ] ) + / 3.0; + fBrakeTime *= 0.5; // Ra: tymczasowo, bo przeżyna S1 } // stop-gap measure to ensure cars actually brake to stop even when above calculactions go awry // instead of releasing the brakes and creeping into obstacle at 1-2 km/h @@ -5177,7 +5239,7 @@ std::string TController::StopReasonText() if (eStopReason != 7) // zawalidroga będzie inaczej return StopReasonTable[eStopReason]; else - return "Blocked by " + (pVehicles[0]->PrevAny()->GetName()); + return "Blocked by " + (pVehicles[0]->PrevAny()->name()); }; //---------------------------------------------------------------------------------------------------------------------- @@ -5280,7 +5342,7 @@ TTrack * TController::BackwardTraceRoute(double &fDistance, double &fDirection, } // sprawdzanie zdarzeń semaforów i ograniczeń szlakowych -void TController::SetProximityVelocity(double dist, double vel, const vector3 *pos) +void TController::SetProximityVelocity( double dist, double vel, glm::dvec3 const *pos ) { // Ra:przeslanie do AI prędkości /* //!!!! zastąpić prawidłową reakcją AI na SetProximityVelocity !!!! @@ -5292,7 +5354,7 @@ void TController::SetProximityVelocity(double dist, double vel, const vector3 *p if ((vel<0)?true:dist>0.1*(MoverParameters->Vel*MoverParameters->Vel-vel*vel)+50) {//jeśli jest dalej od umownej drogi hamowania */ - PutCommand("SetProximityVelocity", dist, vel, pos); + PutCommand( "SetProximityVelocity", dist, vel, pos ); /* } else @@ -5304,43 +5366,47 @@ void TController::SetProximityVelocity(double dist, double vel, const vector3 *p TCommandType TController::BackwardScan() { // sprawdzanie zdarzeń semaforów z tyłu pojazdu, zwraca komendę - // dzięki temu będzie można stawać za wskazanym sygnalizatorem, a zwłaszcza jeśli będzie jazda - // na kozioł - // ograniczenia prędkości nie są wtedy istotne, również koniec toru jest do niczego nie - // przydatny + // dzięki temu będzie można stawać za wskazanym sygnalizatorem, a zwłaszcza jeśli będzie jazda na kozioł + // ograniczenia prędkości nie są wtedy istotne, również koniec toru jest do niczego nie przydatny // zwraca true, jeśli należy odwrócić kierunek jazdy pojazdu - if ((OrderList[OrderPos] & ~(Shunt | Connect))) - return cm_Unknown; // skanowanie sygnałów tylko gdy jedzie w trybie manewrowym albo czeka na - // rozkazy + if( ( OrderList[ OrderPos ] & ~( Shunt | Connect ) ) ) { + // skanowanie sygnałów tylko gdy jedzie w trybie manewrowym albo czeka na rozkazy + return cm_Unknown; + } vector3 sl; - int startdir = - -pVehicles[0]->DirectionGet(); // kierunek jazdy względem sprzęgów pojazdu na czele - if (startdir == 0) // jeśli kabina i kierunek nie jest określony - return cm_Unknown; // nie robimy nic - double scandir = - startdir * pVehicles[0]->RaDirectionGet(); // szukamy od pierwszej osi w wybranym kierunku - if (scandir != - 0.0) // skanowanie toru w poszukiwaniu eventów GetValues (PutValues nie są przydatne) - { // Ra: przy wstecznym skanowaniu prędkość nie ma znaczenia - // scanback=pVehicles[1]->NextDistance(fLength+1000.0); //odległość do następnego pojazdu, - // 1000 gdy nic nie ma + // kierunek jazdy względem sprzęgów pojazdu na czele + int const startdir = -pVehicles[0]->DirectionGet(); + if( startdir == 0 ) { + // jeśli kabina i kierunek nie jest określony nie robimy nic + return cm_Unknown; + } + // szukamy od pierwszej osi w wybranym kierunku + double scandir = startdir * pVehicles[0]->RaDirectionGet(); + if (scandir != 0.0) { + // skanowanie toru w poszukiwaniu eventów GetValues (PutValues nie są przydatne) + // Ra: przy wstecznym skanowaniu prędkość nie ma znaczenia double scanmax = 1000; // 1000m do tyłu, żeby widział przeciwny koniec stacji double scandist = scanmax; // zmodyfikuje na rzeczywiście przeskanowane TEvent *e = NULL; // event potencjalnie od semafora - // opcjonalnie może być skanowanie od "wskaźnika" z przodu, np. W5, Tm=Ms1, koniec toru - TTrack *scantrack = BackwardTraceRoute(scandist, scandir, pVehicles[0]->RaTrackGet(), - e); // wg drugiej osi w kierunku ruchu - vector3 dir = startdir * pVehicles[0]->VectorFront(); // wektor w kierunku jazdy/szukania - if (!scantrack) // jeśli wstecz wykryto koniec toru - return cm_Unknown; // to raczej nic się nie da w takiej sytuacji zrobić - else - { // a jeśli są dalej tory + // opcjonalnie może być skanowanie od "wskaźnika" z przodu, np. W5, Tm=Ms1, koniec toru wg drugiej osi w kierunku ruchu + TTrack *scantrack = BackwardTraceRoute(scandist, scandir, pVehicles[0]->RaTrackGet(), e); + vector3 const dir = startdir * pVehicles[0]->VectorFront(); // wektor w kierunku jazdy/szukania + if( !scantrack ) { + // jeśli wstecz wykryto koniec toru to raczej nic się nie da w takiej sytuacji zrobić + return cm_Unknown; + } + else { + // a jeśli są dalej tory double vmechmax; // prędkość ustawiona semaforem - if (e) - { // jeśli jest jakiś sygnał na widoku + if( e != nullptr ) { + // jeśli jest jakiś sygnał na widoku #if LOGBACKSCAN - AnsiString edir = - pVehicle->asName + " - " + AnsiString((scandir > 0) ? "Event2 " : "Event1 "); + std::string edir { + "Backward scan by " + + pVehicle->asName + " - " + + ( ( scandir > 0 ) ? + "Event2 " : + "Event1 " ) }; #endif // najpierw sprawdzamy, czy semafor czy inny znak został przejechany vector3 pos = pVehicles[1]->RearPosition(); // pozycja tyłu @@ -5348,66 +5414,67 @@ TCommandType TController::BackwardScan() if (e->Type == tp_GetValues) { // przesłać info o zbliżającym się semaforze #if LOGBACKSCAN - edir += "(" + (e->Params[8].asGroundNode->asName) + "): "; + edir += "(" + ( e->asNodeName ) + ")"; #endif sl = e->PositionGet(); // położenie komórki pamięci sem = sl - pos; // wektor do komórki pamięci od końca składu - // sem=e->Params[8].asGroundNode->pCenter-pos; //wektor do komórki pamięci - if (dir.x * sem.x + dir.z * sem.z < 0) // jeśli został minięty - // if ((mvOccupied->CategoryFlag&1)?(VelNext!=0.0):true) //dla pociągu wymagany - // sygnał zezwalający - { // iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu + if (dir.x * sem.x + dir.z * sem.z < 0) { + // jeśli został minięty + // iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu #if LOGBACKSCAN - WriteLog(edir + "- ignored as not passed yet"); + WriteLog(edir + " - ignored as not passed yet"); #endif return cm_Unknown; // nic } vmechmax = e->ValueGet(1); // prędkość przy tym semaforze - // przeliczamy odległość od semafora - potrzebne by były współrzędne początku - // składu - // scandist=(pos-e->Params[8].asGroundNode->pCenter).Length()-0.5*mvOccupied->Dim.L-10; - // //10m luzu + // przeliczamy odległość od semafora - potrzebne by były współrzędne początku składu scandist = sem.Length() - 2; // 2m luzu przy manewrach wystarczy - if (scandist < 0) - scandist = 0; // ujemnych nie ma po co wysyłać + if( scandist < 0 ) { + // ujemnych nie ma po co wysyłać + scandist = 0; + } bool move = false; // czy AI w trybie manewerowym ma dociągnąć pod S1 - if (e->Command() == cm_SetVelocity) - if ((vmechmax == 0.0) ? (OrderCurrentGet() & (Shunt | Connect)) : - (OrderCurrentGet() & - Connect)) // przy podczepianiu ignorować wyjazd? + if( e->Command() == cm_SetVelocity ) { + if( ( vmechmax == 0.0 ) ? + ( OrderCurrentGet() & ( Shunt | Connect ) ) : + ( OrderCurrentGet() & Connect ) ) { // przy podczepianiu ignorować wyjazd? move = true; // AI w trybie manewerowym ma dociągnąć pod S1 - else - { // - if ((scandist > fMinProximityDist) ? - (mvOccupied->Vel > 0.0) && (OrderCurrentGet() != Shunt) : - false) - { // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o -// zmianie prędkości -// jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył -// Mechanik->PutCommand("SetProximityVelocity",scandist,vmechmax,sl); + } + else { + if( ( scandist > fMinProximityDist ) + && ( ( mvOccupied->Vel > 0.0 ) + && ( OrderCurrentGet() != Shunt ) ) ) { + // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości + // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył #if LOGBACKSCAN - // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist)+" - // "+AnsiString(vmechmax)); + // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist) + AnsiString(vmechmax)); WriteLog(edir); #endif // SetProximityVelocity(scandist,vmechmax,&sl); - return (vmechmax > 0) ? cm_SetVelocity : cm_Unknown; + return ( + vmechmax > 0 ? + cm_SetVelocity : + cm_Unknown ); } - else // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma - // stać - // if ((MoverParameters->Vel==0.0)||(vmechmax==0.0)) //jeśli stoi lub ma - // stanąć/stać - { // semafor na tym torze albo lokomtywa stoi, a ma ruszyć, albo ma -// stanąć, albo nie ruszać -// stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam -// PutCommand("SetVelocity",vmechmax,e->Params[9].asMemCell->Value2(),&sl,stopSem); + else { + // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać + // if ((MoverParameters->Vel==0.0)||(vmechmax==0.0)) //jeśli stoi lub ma stanąć/stać + // semafor na tym torze albo lokomtywa stoi, a ma ruszyć, albo ma stanąć, albo nie ruszać + // stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam + // PutCommand("SetVelocity",vmechmax,e->Params[9].asMemCell->Value2(),&sl,stopSem); #if LOGBACKSCAN - WriteLog(edir + "SetVelocity " + AnsiString(vmechmax) + " " + - AnsiString(e->Params[9].asMemCell->Value2())); + WriteLog( + edir + " - [SetVelocity] [" + + to_string( vmechmax, 2 ) + "] [" + + to_string( e->Params[ 9 ].asMemCell->Value2(), 2 ) + "]" ); #endif - return (vmechmax > 0) ? cm_SetVelocity : cm_Unknown; + return ( + vmechmax > 0 ? + cm_SetVelocity : + cm_Unknown ); } } + } if (OrderCurrentGet() ? OrderCurrentGet() & (Shunt | Connect) : true) // w Wait_for_orders też widzi tarcze { // reakcja AI w trybie manewrowym dodatkowo na sygnały manewrowe @@ -5434,28 +5501,34 @@ TCommandType TController::BackwardScan() // to można zmienić kierunek } } - else // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, albo stanąć albo - // ma stać pod tarczą - { // stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam - // if ((MoverParameters->Vel==0.0)||(vmechmax==0.0)) //jeśli jedzie - // lub ma stanąć/stać + else { + // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, albo stanąć albo ma stać pod tarczą + // stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam + // if ((MoverParameters->Vel==0.0)||(vmechmax==0.0)) //jeśli jedzie lub ma stanąć/stać { // nie dostanie komendy jeśli jedzie i ma jechać -// PutCommand("ShuntVelocity",vmechmax,e->Params[9].asMemCell->Value2(),&sl,stopSem); + // PutCommand("ShuntVelocity",vmechmax,e->Params[9].asMemCell->Value2(),&sl,stopSem); #if LOGBACKSCAN - WriteLog(edir + "ShuntVelocity " + AnsiString(vmechmax) + " " + - AnsiString(e->ValueGet(2))); + WriteLog( + edir + " - [ShuntVelocity] [" + + to_string( vmechmax, 2 ) + "] [" + + to_string( e->ValueGet( 2 ), 2 ) + "]" ); #endif - return (vmechmax > 0) ? cm_ShuntVelocity : cm_Unknown; + return ( + vmechmax > 0 ? + cm_ShuntVelocity : + cm_Unknown ); } } - if ((vmechmax != 0.0) && (scandist < 100.0)) - { // jeśli Tm w odległości do 100m podaje zezwolenie na jazdę, to od -// razu ją ignorujemy, aby móc szukać kolejnej -// eSignSkip=e; //wtedy uznajemy ignorowaną przy poszukiwaniu nowej + if ((vmechmax != 0.0) && (scandist < 100.0)) { + // jeśli Tm w odległości do 100m podaje zezwolenie na jazdę, to od razu ją ignorujemy, aby móc szukać kolejnej + // eSignSkip=e; //wtedy uznajemy ignorowaną przy poszukiwaniu nowej #if LOGBACKSCAN - WriteLog(edir + "- will be ignored due to Ms2"); + WriteLog(edir + " - will be ignored due to Ms2"); #endif - return (vmechmax > 0) ? cm_ShuntVelocity : cm_Unknown; + return ( + vmechmax > 0 ? + cm_ShuntVelocity : + cm_Unknown ); } } // if (move?... } // if (OrderCurrentGet()==Shunt) @@ -5644,31 +5717,30 @@ int TController::CrossRoute(TTrack *tr) } return 0; // nic nie znaleziono? }; - +/* void TController::RouteSwitch(int d) { // ustawienie kierunku jazdy z kabiny d &= 3; - if( d ) { - if( iRouteWanted != d ) { // nowy kierunek - iRouteWanted = d; // zapamiętanie - if( mvOccupied->CategoryFlag & 2 ) { - // jeśli samochód - for( std::size_t i = 0; i < sSpeedTable.size(); ++i ) { - // szukanie pierwszego skrzyżowania i resetowanie kierunku na nim - if( true == TestFlag( sSpeedTable[ i ].iFlags, spEnabled | spTrack ) ) { - // jeśli pozycja istotna (1) oraz odcinek (2) - if( false == TestFlag( sSpeedTable[ i ].iFlags, spElapsed ) ) { - // odcinek nie może być miniętym - if( sSpeedTable[ i ].trTrack->eType == tt_Cross ) // jeśli skrzyżowanie - { - while( sSpeedTable.size() >= i ) { - // NOTE: we're ignoring semaphor flags and not resetting them like we do for train route trimming - // but what if there's street lights? - // TODO: investigate - sSpeedTable.pop_back(); - } - iLast = sSpeedTable.size(); + if( ( d != 0 ) + && ( iRouteWanted != d ) ) { // nowy kierunek + iRouteWanted = d; // zapamiętanie + if( mvOccupied->CategoryFlag & 2 ) { + // jeśli samochód + for( std::size_t i = 0; i < sSpeedTable.size(); ++i ) { + // szukanie pierwszego skrzyżowania i resetowanie kierunku na nim + if( true == TestFlag( sSpeedTable[ i ].iFlags, spEnabled | spTrack ) ) { + // jeśli pozycja istotna (1) oraz odcinek (2) + if( false == TestFlag( sSpeedTable[ i ].iFlags, spElapsed ) ) { + // odcinek nie może być miniętym + if( sSpeedTable[ i ].trTrack->eType == tt_Cross ) // jeśli skrzyżowanie + { + while( sSpeedTable.size() >= i ) { + // NOTE: we're ignoring semaphor flags and not resetting them like we do for train route trimming + // but what if there's street lights? + // TODO: investigate + sSpeedTable.pop_back(); } + iLast = sSpeedTable.size(); } } } @@ -5676,6 +5748,7 @@ void TController::RouteSwitch(int d) } } }; +*/ std::string TController::OwnerName() const { return ( pVehicle ? pVehicle->MoverParameters->Name : "none" ); diff --git a/Driver.h b/Driver.h index 2fe884fc..c4122257 100644 --- a/Driver.h +++ b/Driver.h @@ -209,6 +209,7 @@ public: double BrakeAccFactor(); double fBrakeReaction = 1.0; //opóźnienie zadziałania hamulca - czas w s / (km/h) double fAccThreshold = 0.0; // próg opóźnienia dla zadziałania hamulca + double AbsAccS_avg = 0.0; // averaged out directional acceleration double AbsAccS_pub = 0.0; // próg opóźnienia dla zadziałania hamulca double fBrake_a0[BrakeAccTableSize+1] = { 0.0 }; // próg opóźnienia dla zadziałania hamulca double fBrake_a1[BrakeAccTableSize+1] = { 0.0 }; // próg opóźnienia dla zadziałania hamulca @@ -230,8 +231,9 @@ private: TAction GetAction() { return eAction; } bool AIControllFlag = false; // rzeczywisty/wirtualny maszynista - int iRouteWanted = 3; // oczekiwany kierunek jazdy (0-stop,1-lewo,2-prawo,3-prosto) np. odpala - // migacz lub czeka na stan zwrotnicy +/* + int iRouteWanted = 3; // oczekiwany kierunek jazdy (0-stop,1-lewo,2-prawo,3-prosto) np. odpala migacz lub czeka na stan zwrotnicy +*/ private: TDynamicObject *pVehicle = nullptr; // pojazd w którym siedzi sterujący TDynamicObject *pVehicles[2]; // skrajne pojazdy w składzie (niekoniecznie bezpośrednio sterowane) @@ -311,10 +313,8 @@ private: public: Mtable::TTrainParameters *Timetable() { return TrainParams; }; - void PutCommand(std::string NewCommand, double NewValue1, double NewValue2, - const TLocation &NewLocation, TStopReason reason = stopComm); - bool PutCommand(std::string NewCommand, double NewValue1, double NewValue2, - const vector3 *NewLocation, TStopReason reason = stopComm); + void PutCommand(std::string NewCommand, double NewValue1, double NewValue2, const TLocation &NewLocation, TStopReason reason = stopComm); + bool PutCommand( std::string NewCommand, double NewValue1, double NewValue2, glm::dvec3 const *NewLocation, TStopReason reason = stopComm ); void UpdateSituation(double dt); // uruchamiac przynajmniej raz na sekundę // procedury dotyczace rozkazow dla maszynisty // uaktualnia informacje o prędkości @@ -374,7 +374,7 @@ private: bool BackwardTrackBusy(TTrack *Track); TEvent *CheckTrackEventBackward(double fDirection, TTrack *Track); TTrack *BackwardTraceRoute(double &fDistance, double &fDirection, TTrack *Track, TEvent *&Event); - void SetProximityVelocity(double dist, double vel, const vector3 *pos); + void SetProximityVelocity( double dist, double vel, glm::dvec3 const *pos ); TCommandType BackwardScan(); public: @@ -401,7 +401,9 @@ private: void DirectionInitial(); std::string TableText(std::size_t const Index); int CrossRoute(TTrack *tr); +/* void RouteSwitch(int d); +*/ std::string OwnerName() const; TMoverParameters const *Controlling() const { return mvControlling; } diff --git a/DynObj.cpp b/DynObj.cpp index 479c2c85..15ddb7ad 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -15,34 +15,20 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "DynObj.h" -#include "Logs.h" -#include "MdlMngr.h" -#include "Timer.h" -#include "usefull.h" -// McZapkie-260202 +#include "simulation.h" #include "Globals.h" -#include "renderer.h" -#include "AirCoupler.h" - -#include "TractionPower.h" -#include "Ground.h" //bo Global::pGround->bDynamicRemove -#include "Event.h" -#include "Driver.h" -#include "Camera.h" //bo likwidujemy trzęsienie +#include "Timer.h" +#include "Logs.h" #include "Console.h" #include "Traction.h" #include "sound.h" +#include "MdlMngr.h" // Ra: taki zapis funkcjonuje lepiej, ale może nie jest optymalny #define vWorldFront Math3D::vector3(0, 0, 1) #define vWorldUp Math3D::vector3(0, 1, 0) #define vWorldLeft CrossProduct(vWorldUp, vWorldFront) -// Ra: bo te poniżej to się powielały w każdym module odobno -// vector3 vWorldFront=vector3(0,0,1); -// vector3 vWorldUp=vector3(0,1,0); -// vector3 vWorldLeft=CrossProduct(vWorldUp,vWorldFront); - #define M_2PI 6.283185307179586476925286766559; const float maxrot = (float)(M_PI / 3.0); // 60° @@ -50,6 +36,7 @@ std::string const TDynamicObject::MED_labels[] = { "masa: ", "amax: ", "Fzad: ", "FmPN: ", "FmED: ", "FrED: ", "FzPN: ", "nPrF: " }; +bool TDynamicObject::bDynamicRemove { false }; //--------------------------------------------------------------------------- void TAnimPant::AKP_4E() @@ -416,14 +403,22 @@ void TDynamicObject::UpdateDoorTranslate(TAnim *pAnim) // Ra: te współczynniki są bez sensu, bo modyfikują wektor przesunięcia // w efekcie drzwi otwierane na zewnątrz będą odlatywac dowolnie daleko :) // ograniczyłem zakres ruchu funkcją max - if (pAnim->smAnimated) - { - if (pAnim->iNumber & 1) + if (pAnim->smAnimated) { + + if( pAnim->iNumber & 1 ) { pAnim->smAnimated->SetTranslate( - vector3(0, 0, Min0R(dDoorMoveR * pAnim->fSpeed, dDoorMoveR))); - else + vector3{ + 0.0, + 0.0, + dDoorMoveR } ); + } + else { pAnim->smAnimated->SetTranslate( - vector3(0, 0, Min0R(dDoorMoveL * pAnim->fSpeed, dDoorMoveL))); + vector3{ + 0.0, + 0.0, + dDoorMoveL } ); + } } }; @@ -494,18 +489,30 @@ void TDynamicObject::UpdatePant(TAnim *pAnim) void TDynamicObject::UpdateDoorPlug(TAnim *pAnim) { // animacja drzwi - odskokprzesuw - if (pAnim->smAnimated) - { - if (pAnim->iNumber & 1) + if (pAnim->smAnimated) { + + if( pAnim->iNumber & 1 ) { pAnim->smAnimated->SetTranslate( - vector3(Min0R(dDoorMoveR * 2, MoverParameters->DoorMaxPlugShift), 0, - Max0R(0, Min0R(dDoorMoveR * pAnim->fSpeed, dDoorMoveR) - - MoverParameters->DoorMaxPlugShift * 0.5f))); - else + vector3 { + std::min( + dDoorMoveR * 2, + MoverParameters->DoorMaxPlugShift ), + 0.0, + std::max( + 0.0, + dDoorMoveR - MoverParameters->DoorMaxPlugShift * 0.5 ) } ); + } + else { pAnim->smAnimated->SetTranslate( - vector3(Min0R(dDoorMoveL * 2, MoverParameters->DoorMaxPlugShift), 0, - Max0R(0, Min0R(dDoorMoveL * pAnim->fSpeed, dDoorMoveL) - - MoverParameters->DoorMaxPlugShift * 0.5f))); + vector3 { + std::min( + dDoorMoveL * 2, + MoverParameters->DoorMaxPlugShift ), + 0.0, + std::max( + 0.0, + dDoorMoveL - MoverParameters->DoorMaxPlugShift * 0.5f ) } ); + } } }; @@ -1456,7 +1463,7 @@ void TDynamicObject::ABuScanObjects( int Direction, double Distance ) if( distance < 100.0 ) { // at short distances start to calculate range between couplers directly // odległość do najbliższego pojazdu w linii prostej - fTrackBlock = std::min( fTrackBlock, MoverParameters->Couplers[ mycoupler ].CoupleDist ); + fTrackBlock = MoverParameters->Couplers[ mycoupler ].CoupleDist; } if( ( false == TestFlag( track->iCategoryFlag, 1 ) ) && ( distance > 50.0 ) ) { @@ -1757,8 +1764,8 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" } if (ActPar.find('0') != std::string::npos) // wylaczanie na sztywno { - MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz MoverParameters->Hamulec->ForceEmptiness(); + MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } if (ActPar.find('E') != std::string::npos) // oprozniony { @@ -1782,16 +1789,16 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" { if (Random(10) < 1) // losowanie 1/10 { - MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz MoverParameters->Hamulec->ForceEmptiness(); + MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } } if (ActPar.find('X') != std::string::npos) // agonalny wylaczanie 20%, usrednienie przekladni { if (Random(100) < 20) // losowanie 20/100 { - MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz MoverParameters->Hamulec->ForceEmptiness(); + MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } if (MoverParameters->BrakeCylMult[2] * MoverParameters->BrakeCylMult[1] > 0.01) // jesli jest nastawiacz mechaniczny PL @@ -2611,91 +2618,50 @@ bool TDynamicObject::Update(double dt, double dt1) // if (Global::bLiveTraction) { // Ra 2013-12: to niżej jest chyba trochę bez sensu double v = MoverParameters->PantRearVolt; - if (v == 0.0) - { + if (v == 0.0) { v = MoverParameters->PantFrontVolt; - if (v == 0.0) - if ((MoverParameters->TrainType & (dt_EZT | dt_ET40 | dt_ET41 | dt_ET42)) && - MoverParameters->EngineType != - ElectricInductionMotor) // dwuczłony mogą mieć sprzęg WN + if( v == 0.0 ) { + if( MoverParameters->TrainType & ( dt_EZT | dt_ET40 | dt_ET41 | dt_ET42 ) ) { + // dwuczłony mogą mieć sprzęg WN v = MoverParameters->GetTrainsetVoltage(); // ostatnia szansa + } + } } if (v != 0.0) { // jeśli jest zasilanie NoVoltTime = 0; tmpTraction.TractionVoltage = v; } - else - { - /* - if (MoverParameters->Vel>0.1f) //jeśli jedzie - if (NoVoltTime==0.0) //tylko przy pierwszym zaniku napięcia - if (MoverParameters->PantFrontUp||MoverParameters->PantRearUp) - //if - ((pants[0].fParamPants->PantTraction>1.0)||(pants[1].fParamPants->PantTraction>1.0)) - {//wspomagacz usuwania problemów z siecią - if (!Global::iPause) - {//Ra: tymczasowa teleportacja do miejsca, gdzie brakuje prądu - Global::SetCameraPosition(vPosition+vector3(0,0,5)); //nowa - pozycja dla - generowania obiektów - Global::pCamera->Init(vPosition+vector3(0,0,5),Global::pFreeCameraInitAngle[0]); - //przestawienie - } - Global:l::pGround->Silence(Global::pCamera->Pos); //wyciszenie - wszystkiego - z poprzedniej pozycji - Globa:iPause|=1; //tymczasowe zapauzowanie, gdy problem z - siecią - } - */ - NoVoltTime = NoVoltTime + dt; - if (NoVoltTime > 0.2) // jeśli brak zasilania dłużej niż 0.2 sekundy (25km/h pod - // izolatorem daje 0.15s) - { // Ra 2F1H: prowizorka, trzeba przechować napięcie, żeby nie wywalało - // WS pod - // izolatorem - if (MoverParameters->Vel > 0.5) // jeśli jedzie - if (MoverParameters->PantFrontUp || - MoverParameters->PantRearUp) // Ra 2014-07: doraźna blokada logowania - // zimnych lokomotyw - zrobić to trzeba - // inaczej - // if (NoVoltTime>0.02) //tu można ograniczyć czas rozłączenia - // if (DebugModeFlag) //logowanie nie zawsze - if ((MoverParameters->Mains) && - ((MoverParameters->EngineType != ElectricInductionMotor) - || (MoverParameters->GetTrainsetVoltage() < 0.1f))) - { // Ra 15-01: logować tylko, jeśli WS załączony - // yB 16-03: i nie jest to asynchron zasilany z daleka - // if (MoverParameters->PantFrontUp&&pants) - // Ra 15-01: bezwzględne współrzędne pantografu nie są dostępne, - // więc lepiej się tego nie zaloguje - ErrorLog("Voltage loss: by " + MoverParameters->Name + " at " + - to_string(vPosition.x, 2, 7) + " " + - to_string(vPosition.y, 2, 7) + " " + - to_string(vPosition.z, 2, 7) + ", time " + - to_string(NoVoltTime, 2, 7)); - // if (MoverParameters->PantRearUp) - // if (iAnimType[ANIM_PANTS]>1) - // if (pants[1]) - // ErrorLog("Voltage loss: by "+MoverParameters->Name+" at - // "+FloatToStrF(vPosition.x,ffFixed,7,2)+" - // "+FloatToStrF(vPosition.y,ffFixed,7,2)+" - // "+FloatToStrF(vPosition.z,ffFixed,7,2)+", time - // "+FloatToStrF(NoVoltTime,ffFixed,7,2)); + else { + NoVoltTime += dt; + if( NoVoltTime > 0.2 ) { + // jeśli brak zasilania dłużej niż 0.2 sekundy (25km/h pod izolatorem daje 0.15s) + // Ra 2F1H: prowizorka, trzeba przechować napięcie, żeby nie wywalało WS pod izolatorem + if( MoverParameters->Vel > 0.5 ) { + // jeśli jedzie + // Ra 2014-07: doraźna blokada logowania zimnych lokomotyw - zrobić to trzeba inaczej + if( MoverParameters->PantFrontUp + || MoverParameters->PantRearUp ) { + + if( ( MoverParameters->Mains ) + && ( MoverParameters->GetTrainsetVoltage() < 0.1f ) ) { + // Ra 15-01: logować tylko, jeśli WS załączony + // yB 16-03: i nie jest to asynchron zasilany z daleka + // Ra 15-01: bezwzględne współrzędne pantografu nie są dostępne, + // więc lepiej się tego nie zaloguje + ErrorLog( + "Bad traction: " + MoverParameters->Name + + " lost power for " + to_string( NoVoltTime, 2 ) + " sec. at " + + to_string( glm::dvec3{ vPosition } ) ); } - // Ra 2F1H: nie było sensu wpisywać tu zera po upływie czasu, bo - // zmienna była + } + } + // Ra 2F1H: nie było sensu wpisywać tu zera po upływie czasu, bo zmienna była // tymczasowa, a napięcie zerowane od razu tmpTraction.TractionVoltage = 0; // Ra 2013-12: po co tak? - // pControlled->MainSwitch(false); //może tak? } } } - // else //Ra: nie no, trzeba podnieść pantografy, jak nie będzie drutu, to - // będą miały prąd - // po osiągnięciu 1.4m - // tmpTraction.TractionVoltage=0.95*MoverParameters->EnginePowerSource.MaxVoltage; } else tmpTraction.TractionVoltage = 0.95 * MoverParameters->EnginePowerSource.MaxVoltage; @@ -2760,9 +2726,9 @@ bool TDynamicObject::Update(double dt, double dt1) 1000; // chwilowy max ED -> do rozdzialu sil FfulED = std::min(p->MoverParameters->eimv[eimv_Fful], 0.0) * 1000; // chwilowy max ED -> do rozdzialu sil - FrED -= std::min(p->MoverParameters->eimv[eimv_Fr], 0.0) * + FrED -= std::min(p->MoverParameters->eimv[eimv_Fmax], 0.0) * 1000; // chwilowo realizowane ED -> do pneumatyki - Frj += std::max(p->MoverParameters->eimv[eimv_Fr], 0.0) * + Frj += std::max(p->MoverParameters->eimv[eimv_Fmax], 0.0) * 1000;// chwilowo realizowany napęd -> do utrzymującego masa += p->MoverParameters->TotalMass; osie += p->MoverParameters->NAxles; @@ -2861,10 +2827,10 @@ bool TDynamicObject::Update(double dt, double dt1) if ((FzEP[i] > 0.01) && (FzEP[i] > p->MoverParameters->TotalMass * p->MoverParameters->eimc[eimc_p_eped] + - Min0R(p->MoverParameters->eimv[eimv_Fr], 0) * 1000) && + Min0R(p->MoverParameters->eimv[eimv_Fmax], 0) * 1000) && (!PrzekrF[i])) { - float przek1 = -Min0R(p->MoverParameters->eimv[eimv_Fr], 0) * 1000 + + float przek1 = -Min0R(p->MoverParameters->eimv[eimv_Fmax], 0) * 1000 + FzEP[i] - p->MoverParameters->TotalMass * p->MoverParameters->eimc[eimc_p_eped] * 0.999; @@ -2969,19 +2935,13 @@ bool TDynamicObject::Update(double dt, double dt1) // McZapkie-260202 - dMoveLen przyda sie przy stukocie kol dDOMoveLen = GetdMoveLen() + MoverParameters->ComputeMovement(dt, dt1, ts, tp, tmpTraction, l, r); - // yB: zeby zawsze wrzucalo w jedna strone zakretu -/* - // this seemed to have opposite effect, if anything -- the sway direction would be affected - // by the 'direction' of the track, making the sway go sometimes inward, sometimes outward - MoverParameters->AccN *= -ABuGetDirection(); -*/ // if (dDOMoveLen!=0.0) //Ra: nie może być, bo blokuje Event0 if( Mechanik ) Mechanik->MoveDistanceAdd( dDOMoveLen ); // dodanie aktualnego przemieszczenia Move(dDOMoveLen); if (!bEnabled) // usuwane pojazdy nie mają toru { // pojazd do usunięcia - Global::pGround->bDynamicRemove = true; // sprawdzić + bDynamicRemove = true; // sprawdzić return false; } Global::ABuDebug = dDOMoveLen / dt1; @@ -3218,19 +3178,18 @@ bool TDynamicObject::Update(double dt, double dt1) if ((MoverParameters->PantRearVolt == 0.0) && (MoverParameters->PantFrontVolt == 0.0) && sPantUp) sPantUp->gain(vol).position(vPosition).play(); - if (p->hvPowerWire) // TODO: wyliczyć trzeba prąd przypadający na - // pantograf i - // wstawić do GetVoltage() - { - MoverParameters->PantRearVolt = - p->hvPowerWire->VoltageGet(MoverParameters->Voltage, fPantCurrent); + if (p->hvPowerWire) { + // TODO: wyliczyć trzeba prąd przypadający na pantograf i wstawić do GetVoltage() + MoverParameters->PantRearVolt = p->hvPowerWire->VoltageGet( MoverParameters->Voltage, fPantCurrent ); fCurrent -= fPantCurrent; // taki prąd płynie przez powyższy pantograf } else MoverParameters->PantRearVolt = 0.0; } - else + else { +// Global::iPause ^= 2; MoverParameters->PantRearVolt = 0.0; + } break; } // pozostałe na razie nie obsługiwane if( MoverParameters->PantPress > ( @@ -3297,8 +3256,8 @@ bool TDynamicObject::Update(double dt, double dt1) p->fAngleU = acos((p->fLenL1 * cos(k) + p->fHoriz) / p->fLenU1); // górne ramię // wyliczyć aktualną wysokość z wzoru sinusowego // h=a*sin()+b*sin() - p->PantWys = p->fLenL1 * sin(k) + p->fLenU1 * sin(p->fAngleU) + - p->fHeight; // wysokość całości + // wysokość całości + p->PantWys = p->fLenL1 * sin(k) + p->fLenU1 * sin(p->fAngleU) + p->fHeight; } } } // koniec pętli po pantografach @@ -3562,7 +3521,7 @@ void TDynamicObject::RenderSounds() // McZapkie-010302: ulepszony dzwiek silnika double freq; double vol = 0; - double dt = Timer::GetDeltaTime(); + double dt = Timer::GetDeltaRenderTime(); // double sounddist; // sounddist=SquareMagnitude(Global::pCameraPosition-vPosition); @@ -4183,7 +4142,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, // Ra 15-01: gałka nastawy hamulca parser.getTokens(); parser >> asAnimName; - smBrakeMode = mdModel->GetFromName(asAnimName.c_str()); + smBrakeMode = mdModel->GetFromName(asAnimName); // jeszcze wczytać kąty obrotu dla poszczególnych ustawień } @@ -4191,7 +4150,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, // Ra 15-01: gałka nastawy hamulca parser.getTokens(); parser >> asAnimName; - smLoadMode = mdModel->GetFromName(asAnimName.c_str()); + smLoadMode = mdModel->GetFromName(asAnimName); // jeszcze wczytać kąty obrotu dla poszczególnych ustawień } @@ -4203,7 +4162,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, for (i = 0; i < iAnimType[ANIM_WHEELS]; ++i) // liczba osi { // McZapkie-050402: wyszukiwanie kol o nazwie str* asAnimName = token + std::to_string(i + 1); - pAnimations[i].smAnimated = mdModel->GetFromName(asAnimName.c_str()); // ustalenie submodelu + pAnimations[i].smAnimated = mdModel->GetFromName(asAnimName); // ustalenie submodelu if (pAnimations[i].smAnimated) { //++iAnimatedAxles; pAnimations[i].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu @@ -4344,8 +4303,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, } } else - ErrorLog("Bad model: " + asFileName + " - missed submodel " + - asAnimName); // brak ramienia + ErrorLog("Bad model: " + asFileName + " - missed submodel " + asAnimName); // brak ramienia } } @@ -4379,10 +4337,9 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, } } else - ErrorLog( "Bad model: " + asFileName + " - missed submodel " + - asAnimName ); // brak ramienia + ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName ); // brak ramienia } - } + } } else if( token == "animpantrg1prefix:" ) { @@ -4473,10 +4430,8 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, // pants[i].fParamPants->vPos.z=0; //niezerowe dla pantografów // asymetrycznych pants[ i ].fParamPants->PantTraction = pants[ i ].fParamPants->PantWys; - pants[ i ].fParamPants->fWidth = - 0.5 * - MoverParameters->EnginePowerSource.CollectorParameters - .CSW; // połowa szerokości ślizgu; jest w "Power: CSW=" + // połowa szerokości ślizgu; jest w "Power: CSW=" + pants[ i ].fParamPants->fWidth = 0.5 * MoverParameters->EnginePowerSource.CollectorParameters.CSW; } } } @@ -4955,7 +4910,9 @@ void TDynamicObject::RadioStop() if( ( MoverParameters->SecuritySystem.RadioStop ) && ( MoverParameters->Radio ) ) { // jeśli pojazd ma RadioStop i jest on aktywny - Mechanik->PutCommand( "Emergency_brake", 1.0, 1.0, &vPosition, stopRadio ); + // HAX cast until math types unification + glm::dvec3 pos = static_cast(vPosition); + Mechanik->PutCommand( "Emergency_brake", 1.0, 1.0, &pos, stopRadio ); // add onscreen notification for human driver // TODO: do it selectively for the 'local' driver once the multiplayer is in if( false == Mechanik->AIControllFlag ) { @@ -5414,3 +5371,166 @@ TDynamicObject::ConnectedEnginePowerSource( TDynamicObject const *Caller ) const // ...if we're still here, report lack of power source return MoverParameters->EnginePowerSource.SourceType; } + + + +// legacy method, calculates changes in simulation state over specified time +void +vehicle_table::update( double Deltatime, int Iterationcount ) { + // 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 + for( auto *vehicle : m_items ) { + if( false == vehicle->bEnabled ) { continue; } + // Ra: zmienić warunek na sprawdzanie pantografów w jednej zmiennej: czy pantografy i czy podniesione + if( vehicle->MoverParameters->EnginePowerSource.SourceType == CurrentCollector ) { + update_traction( vehicle ); + } + vehicle->MoverParameters->ComputeConstans(); + vehicle->CoupleDist(); + } + if( Iterationcount > 1 ) { + // ABu: ponizsze wykonujemy tylko jesli wiecej niz jedna iteracja + for( int iteration = 0; iteration < ( Iterationcount - 1 ); ++iteration ) { + for( auto *vehicle : m_items ) { + vehicle->UpdateForce( Deltatime, Deltatime, false ); + } + for( auto *vehicle : m_items ) { + vehicle->FastUpdate( Deltatime ); + } + } + } + + auto const totaltime { Deltatime * Iterationcount }; // całkowity czas + + for( auto *vehicle : m_items ) { + vehicle->UpdateForce( Deltatime, totaltime, true ); + } + for( auto *vehicle : m_items ) { + // Ra 2015-01: tylko tu przelicza sieć trakcyjną + vehicle->Update( Deltatime, totaltime ); + } +/* + // TODO: re-implement + if (TDynamicObject::bDynamicRemove) + { // jeśli jest coś do usunięcia z listy, to trzeba na końcu + for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) + if ( false == Current->DynamicObject->bEnabled) + { + DynamicRemove(Current->DynamicObject); // usunięcie tego i podłączonych + Current = nRootDynamic; // sprawdzanie listy od początku + } + TDynamicObject::bDynamicRemove = false; // na razie koniec + } +*/ +} + +// legacy method, checks for presence and height of traction wire for specified vehicle +void +vehicle_table::update_traction( TDynamicObject *Vehicle ) { + + auto const vFront = glm::make_vec3( Vehicle->VectorFront().getArray() ); // wektor normalny dla płaszczyzny ruchu pantografu + auto const vUp = glm::make_vec3( Vehicle->VectorUp().getArray() ); // wektor pionu pudła (pochylony od pionu na przechyłce) + auto const vLeft = glm::make_vec3( Vehicle->VectorLeft().getArray() ); // wektor odległości w bok (odchylony od poziomu na przechyłce) + auto const position = glm::dvec3 { Vehicle->GetPosition() }; // współrzędne środka pojazdu + + for( int pantographindex = 0; pantographindex < Vehicle->iAnimType[ ANIM_PANTS ]; ++pantographindex ) { + // pętla po pantografach + auto pantograph { Vehicle->pants[ pantographindex ].fParamPants }; + if( true == ( + pantographindex == TMoverParameters::side::front ? + Vehicle->MoverParameters->PantFrontUp : + Vehicle->MoverParameters->PantRearUp ) ) { + // jeśli pantograf podniesiony + auto const pant0 { position + ( vLeft * pantograph->vPos.z ) + ( vUp * pantograph->vPos.y ) + ( vFront * pantograph->vPos.x ) }; + if( pantograph->hvPowerWire != nullptr ) { + // jeżeli znamy drut z poprzedniego przebiegu + for( int attempts = 0; attempts < 30; ++attempts ) { + // powtarzane aż do znalezienia odpowiedniego odcinka na liście dwukierunkowej + if( pantograph->hvPowerWire->iLast & 0x3 ) { + // dla ostatniego i przedostatniego przęsła wymuszamy szukanie innego + // nie to, że nie ma, ale trzeba sprawdzić inne + pantograph->hvPowerWire = nullptr; + break; + } + if( pantograph->hvPowerWire->hvParallel ) { + // jeśli przęsło tworzy bieżnię wspólną, to trzeba sprawdzić pozostałe + // nie to, że nie ma, ale trzeba sprawdzić inne + pantograph->hvPowerWire = nullptr; + break; + } + // obliczamy wyraz wolny równania płaszczyzny (to miejsce nie jest odpowienie) + // podstawiamy równanie parametryczne drutu do równania płaszczyzny pantografu + auto const fRaParam = + -( glm::dot( pantograph->hvPowerWire->pPoint1, vFront ) - glm::dot( pant0, vFront ) ) + / glm::dot( pantograph->hvPowerWire->vParametric, vFront ); + + if( fRaParam < -0.001 ) { + // histereza rzędu 7cm na 70m typowego przęsła daje 1 promil + pantograph->hvPowerWire = pantograph->hvPowerWire->hvNext[ 0 ]; + continue; + } + if( fRaParam > 1.001 ) { + pantograph->hvPowerWire = pantograph->hvPowerWire->hvNext[ 1 ]; + continue; + } + // jeśli t jest w przedziale, wyznaczyć odległość wzdłuż wektorów vUp i vLeft + // punkt styku płaszczyzny z drutem (dla generatora łuku el.) + auto const vStyk { pantograph->hvPowerWire->pPoint1 + fRaParam * pantograph->hvPowerWire->vParametric }; + auto const vGdzie { vStyk - pant0 }; // wektor + // odległość w pionie musi być w zasięgu ruchu "pionowego" pantografu + // musi się mieścić w przedziale ruchu pantografu + auto const fVertical { glm::dot( vGdzie, vUp ) }; + // odległość w bok powinna być mniejsza niż pół szerokości pantografu + // to się musi mieścić w przedziale zależnym od szerokości pantografu + auto const fHorizontal { std::abs( glm::dot( vGdzie, vLeft ) ) - pantograph->fWidth }; + // jeśli w pionie albo w bok jest za daleko, to dany drut jest nieużyteczny + if( fHorizontal <= 0.0 ) { + // koniec pętli, aktualny drut pasuje + pantograph->PantTraction = fVertical; + break; + } + else { + // the wire is outside contact area and as of now we don't have good detection of parallel sections + // as such there's no guaratee there isn't parallel section present. + // therefore we don't bother checking if the wire is still within range of guide horns + // but simply force area search for potential better option + pantograph->hvPowerWire = nullptr; + break; + } + } + } + + if( pantograph->hvPowerWire == nullptr ) { + // look in the region for a suitable traction piece if we don't already have any + simulation::Region->update_traction( Vehicle, pantographindex ); + } + + if( ( pantograph->hvPowerWire == nullptr ) + && ( false == Global::bLiveTraction ) ) { + // jeśli drut nie znaleziony ale można oszukiwać to dajemy coś tam dla picu + Vehicle->pants[ pantographindex ].fParamPants->PantTraction = 1.4; + } + } + else { + // pantograph is down + pantograph->hvPowerWire = nullptr; + } + } +} + +// legacy method, sends list of vehicles over network +void +vehicle_table::DynamicList( bool const Onlycontrolled ) const { + // odesłanie nazw pojazdów dostępnych na scenerii (nazwy, szczególnie wagonów, mogą się powtarzać!) + for( auto const *vehicle : m_items ) { + if( ( false == Onlycontrolled ) + || ( vehicle->Mechanik != nullptr ) ) { + // same nazwy pojazdów + multiplayer::WyslijString( vehicle->asName, 6 ); + } + } + // informacja o końcu listy + multiplayer::WyslijString( "none", 6 ); +} diff --git a/DynObj.h b/DynObj.h index c8369855..1d6c6818 100644 --- a/DynObj.h +++ b/DynObj.h @@ -149,6 +149,9 @@ class TDynamicObject { // klasa pojazdu friend class opengl_renderer; +public: + static bool bDynamicRemove; // moved from ground + private: // położenie pojazdu w świecie oraz parametry ruchu Math3D::vector3 vPosition; // Ra: pozycja pojazdu liczona zaraz po przesunięciu Math3D::vector3 vCoulpler[ 2 ]; // współrzędne sprzęgów do liczenia zderzeń czołowych @@ -158,19 +161,17 @@ private: // położenie pojazdu w świecie oraz parametry ruchu TTrackParam tp; // parametry toru przekazywane do fizyki TTrackFollower Axle0; // oś z przodu (od sprzęgu 0) TTrackFollower Axle1; // oś z tyłu (od sprzęgu 1) - int iAxleFirst; // numer pierwszej osi w kierunku ruchu (oś wiążąca pojazd z torem i wyzwalająca - // eventy) + int iAxleFirst; // numer pierwszej osi w kierunku ruchu (oś wiążąca pojazd z torem i wyzwalająca eventy) float fAxleDist; // rozstaw wózków albo osi do liczenia proporcji zacienienia Math3D::vector3 modelRot; // obrot pudła względem świata - do przeanalizowania, czy potrzebne!!! - // bool bCameraNear; //blisko kamer są potrzebne dodatkowe obliczenia szczegółów TDynamicObject * ABuFindNearestObject( TTrack *Track, TDynamicObject *MyPointer, int &CouplNr ); -public: // parametry położenia pojazdu dostępne publicznie +public: + // parametry położenia pojazdu dostępne publicznie std::string asTrack; // nazwa toru początkowego; wywalić? std::string asDestination; // dokąd pojazd ma być kierowany "(stacja):(tor)" Math3D::matrix4x4 mMatrix; // macierz przekształcenia do renderowania modeli TMoverParameters *MoverParameters; // parametry fizyki ruchu oraz przeliczanie - // TMoverParameters *pControlled; //wskaźnik do sterowanego członu silnikowego TDynamicObject *NextConnected; // pojazd podłączony od strony sprzęgu 1 (kabina -1) TDynamicObject *PrevConnected; // pojazd podłączony od strony sprzęgu 0 (kabina 1) int NextConnectedNo; // numer sprzęgu podłączonego z tyłu @@ -180,7 +181,7 @@ public: // parametry położenia pojazdu dostępne publicznie TPowerSource ConnectedEnginePowerSource( TDynamicObject const *Caller ) const; -public: // modele składowe pojazdu + // modele składowe pojazdu TModel3d *mdModel; // model pudła TModel3d *mdLoad; // model zmiennego ładunku TModel3d *mdKabina; // model kabiny dla użytkownika; McZapkie-030303: to z train.h @@ -199,28 +200,26 @@ public: // modele składowe pojazdu bool SectionLightsActive { false }; // flag indicating whether section lights were set. float fShade; // zacienienie: 0:normalnie, -1:w ciemności, +1:dodatkowe światło (brak koloru?) - private: // zmienne i metody do animacji submodeli; Ra: sprzatam animacje w pojeździe +private: + // zmienne i metody do animacji submodeli; Ra: sprzatam animacje w pojeździe material_data m_materialdata; - public: +public: inline material_data const *Material() const { return &m_materialdata; } // tymczasowo udostępnione do wyszukiwania drutu int iAnimType[ ANIM_TYPES ]; // 0-osie,1-drzwi,2-obracane,3-zderzaki,4-wózki,5-pantografy,6-tłoki - private: +private: int iAnimations; // liczba obiektów animujących /* TAnim *pAnimations; // obiekty animujące (zawierają wskaźnik do funkcji wykonującej animację) */ std::vector pAnimations; - TSubModel ** - pAnimated; // lista animowanych submodeli (może być ich więcej niż obiektów animujących) - double dWheelAngle[3]; // kąty obrotu kół: 0=przednie toczne, 1=napędzające i wiązary, 2=tylne - // toczne - void - UpdateNone(TAnim *pAnim){}; // animacja pusta (funkcje ustawiania submodeli, gdy blisko kamery) + TSubModel ** pAnimated; // lista animowanych submodeli (może być ich więcej niż obiektów animujących) + double dWheelAngle[3]; // kąty obrotu kół: 0=przednie toczne, 1=napędzające i wiązary, 2=tylne toczne + void UpdateNone(TAnim *pAnim){}; // animacja pusta (funkcje ustawiania submodeli, gdy blisko kamery) void UpdateAxle(TAnim *pAnim); // animacja osi void UpdateBoogie(TAnim *pAnim); // animacja wózka void UpdateDoorTranslate(TAnim *pAnim); // animacja drzwi - przesuw @@ -266,15 +265,14 @@ public: // modele składowe pojazdu TButton btCoupler1; // sprzegi TButton btCoupler2; - TAirCoupler - btCPneumatic1; // sprzegi powietrzne //yB - zmienione z Button na AirCoupler - krzyzyki - TAirCoupler btCPneumatic2; - TAirCoupler btCPneumatic1r; // ABu: to zeby nie bylo problemow przy laczeniu wagonow, - TAirCoupler btCPneumatic2r; // jesli beda polaczone sprzegami 1<->1 lub 0<->0 - TAirCoupler btPneumatic1; // ABu: sprzegi powietrzne zolte - TAirCoupler btPneumatic2; - TAirCoupler btPneumatic1r; // ABu: analogicznie jak 4 linijki wyzej - TAirCoupler btPneumatic2r; + AirCoupler btCPneumatic1; // sprzegi powietrzne //yB - zmienione z Button na AirCoupler - krzyzyki + AirCoupler btCPneumatic2; + AirCoupler btCPneumatic1r; // ABu: to zeby nie bylo problemow przy laczeniu wagonow, + AirCoupler btCPneumatic2r; // jesli beda polaczone sprzegami 1<->1 lub 0<->0 + AirCoupler btPneumatic1; // ABu: sprzegi powietrzne zolte + AirCoupler btPneumatic2; + AirCoupler btPneumatic1r; // ABu: analogicznie jak 4 linijki wyzej + AirCoupler btPneumatic2r; TButton btCCtrl1; // sprzegi sterowania TButton btCCtrl2; @@ -298,8 +296,6 @@ public: // modele składowe pojazdu TButton btHeadSignals23; TButton btMechanik1; TButton btMechanik2; - //TSubModel *smMechanik0; // Ra: mechanik wbudowany w model jako submodel? - //TSubModel *smMechanik1; // mechanik od strony sprzęgu 1 double enginevolume; // MC: pomocnicze zeby gladziej silnik buczal int iAxles; // McZapkie: to potem mozna skasowac i zastapic iNumAxles @@ -323,7 +319,6 @@ public: // modele składowe pojazdu sound* sReleaser = nullptr; // Winger 010304 - // sound* rsPanTup; //PSound sPantUp; sound* sPantUp = nullptr; sound* sPantDown = nullptr; sound* rsDoorOpen = nullptr; // Ra: przeniesione z kabiny @@ -350,8 +345,6 @@ public: // modele składowe pojazdu int iHornWarning; // numer syreny do użycia po otrzymaniu sygnału do jazdy bool bEnabled; // Ra: wyjechał na portal i ma być usunięty protected: - // TTrackFollower Axle2; //dwie osie z czterech (te są protected) - // TTrackFollower Axle3; //Ra: wyłączyłem, bo kąty są liczone w Segment.cpp int iNumAxles; // ilość osi std::string asModel; @@ -371,26 +364,18 @@ public: // modele składowe pojazdu TDynamicObject * PrevC(int C); TDynamicObject * NextC(int C); double NextDistance(double d = -1.0); - void SetdMoveLen(double dMoveLen) - { - MoverParameters->dMoveLen = dMoveLen; - } - void ResetdMoveLen() - { - MoverParameters->dMoveLen = 0; - } - double GetdMoveLen() - { - return MoverParameters->dMoveLen; - } + void SetdMoveLen(double dMoveLen) { + MoverParameters->dMoveLen = dMoveLen; } + void ResetdMoveLen() { + MoverParameters->dMoveLen = 0; } + double GetdMoveLen() { + return MoverParameters->dMoveLen; } int GetPneumatic(bool front, bool red); void SetPneumatic(bool front, bool red); std::string asName; - std::string GetName() - { - return this ? asName : std::string(""); - }; + std::string name() const { + return this ? asName : std::string(); }; sound* rsDiesielInc = nullptr; // youBy sound* rscurve = nullptr; // youBy @@ -403,7 +388,6 @@ public: // modele składowe pojazdu TDynamicObject * ABuScanNearestObject(TTrack *Track, double ScanDir, double ScanDist, int &CouplNr); TDynamicObject * GetFirstDynamic(int cpl_type, int cf = 1); - // TDynamicObject* GetFirstCabDynamic(int cpl_type); void ABuSetModelShake( Math3D::vector3 mShake); // McZapkie-010302 @@ -435,99 +419,65 @@ public: // modele składowe pojazdu void Move(double fDistance); void FastMove(double fDistance); void RenderSounds(); - inline Math3D::vector3 GetPosition() const - { - return vPosition; - }; - inline Math3D::vector3 HeadPosition() - { - return vCoulpler[iDirection ^ 1]; - }; // pobranie współrzędnych czoła - inline Math3D::vector3 RearPosition() - { - return vCoulpler[iDirection]; - }; // pobranie współrzędnych tyłu - inline Math3D::vector3 AxlePositionGet() - { - return iAxleFirst ? Axle1.pPosition : Axle0.pPosition; - }; - inline Math3D::vector3 VectorFront() const - { - return vFront; - }; - inline Math3D::vector3 VectorUp() - { - return vUp; - }; - inline Math3D::vector3 VectorLeft() const - { - return vLeft; - }; - inline double * Matrix() - { - return mMatrix.getArray(); - }; - inline double GetVelocity() - { - return MoverParameters->Vel; - }; - inline double GetLength() const - { - return MoverParameters->Dim.L; - }; - inline double GetWidth() const - { - return MoverParameters->Dim.W; - }; - inline TTrack * GetTrack() - { - return (iAxleFirst ? Axle1.GetTrack() : Axle0.GetTrack()); - }; - // void UpdatePos(); + inline Math3D::vector3 GetPosition() const { + return vPosition; }; + // pobranie współrzędnych czoła + inline Math3D::vector3 HeadPosition() { + return vCoulpler[iDirection ^ 1]; }; + // pobranie współrzędnych tyłu + inline Math3D::vector3 RearPosition() { + return vCoulpler[iDirection]; }; + inline Math3D::vector3 AxlePositionGet() { + return iAxleFirst ? Axle1.pPosition : Axle0.pPosition; }; + inline Math3D::vector3 VectorFront() const { + return vFront; }; + inline Math3D::vector3 VectorUp() { + return vUp; }; + inline Math3D::vector3 VectorLeft() const { + return vLeft; }; + inline double * Matrix() { + return mMatrix.getArray(); }; + inline double GetVelocity() { + return MoverParameters->Vel; }; + inline double GetLength() const { + return MoverParameters->Dim.L; }; + inline double GetWidth() const { + return MoverParameters->Dim.W; }; + inline TTrack * GetTrack() { + return (iAxleFirst ? Axle1.GetTrack() : Axle0.GetTrack()); }; // McZapkie-260202 void LoadMMediaFile(std::string BaseDir, std::string TypeName, std::string ReplacableSkin); - inline double ABuGetDirection() const // ABu. - { - return (Axle1.GetTrack() == MyTrack ? Axle1.GetDirection() : Axle0.GetDirection()); - }; - // inline double ABuGetTranslation() //ABu. - // {//zwraca przesunięcie wózka względem Point1 toru - // return (Axle1.GetTrack()==MyTrack?Axle1.GetTranslation():Axle0.GetTranslation()); - // }; - inline double RaDirectionGet() - { // zwraca kierunek pojazdu na torze z aktywną osą - return iAxleFirst ? Axle1.GetDirection() : Axle0.GetDirection(); - }; - inline double RaTranslationGet() - { // zwraca przesunięcie wózka względem Point1 toru z aktywną osią - return iAxleFirst ? Axle1.GetTranslation() : Axle0.GetTranslation(); - }; - inline TTrack * RaTrackGet() - { // zwraca tor z aktywną osią - return iAxleFirst ? Axle1.GetTrack() : Axle0.GetTrack(); - }; + inline double ABuGetDirection() const { // ABu. + return (Axle1.GetTrack() == MyTrack ? Axle1.GetDirection() : Axle0.GetDirection()); }; + // zwraca kierunek pojazdu na torze z aktywną osą + inline double RaDirectionGet() { + return iAxleFirst ? Axle1.GetDirection() : Axle0.GetDirection(); }; + // zwraca przesunięcie wózka względem Point1 toru z aktywną osią + inline double RaTranslationGet() { + return iAxleFirst ? Axle1.GetTranslation() : Axle0.GetTranslation(); }; + // zwraca tor z aktywną osią + inline TTrack * RaTrackGet() { + return iAxleFirst ? Axle1.GetTrack() : Axle0.GetTrack(); }; void CouplersDettach(double MinDist, int MyScanDir); void RadioStop(); void Damage(char flag); void RaLightsSet(int head, int rear); - // void RaAxleEvent(TEvent *e); TDynamicObject * FirstFind(int &coupler_nr, int cf = 1); float GetEPP(); // wyliczanie sredniego cisnienia w PG int DirectionSet(int d); // ustawienie kierunku w składzie - int DirectionGet() - { - return iDirection + iDirection - 1; - }; // odczyt kierunku w składzie + // odczyt kierunku w składzie + int DirectionGet() { + return iDirection + iDirection - 1; }; int DettachStatus(int dir); int Dettach(int dir); TDynamicObject * Neightbour(int &dir); void CoupleDist(); TDynamicObject * ControlledFind(); void ParamSet(int what, int into); - int RouteWish(TTrack *tr); // zapytanie do AI, po którym segmencie skrzyżowania - // jechać + // zapytanie do AI, po którym segmencie skrzyżowania jechać + int RouteWish(TTrack *tr); void DestinationSet(std::string to, std::string numer); std::string TextureTest(std::string const &name); void OverheadTrack(float o); @@ -535,4 +485,20 @@ public: // modele składowe pojazdu static std::string const MED_labels[ 8 ]; }; + + +class vehicle_table : public basic_table { + +public: + // legacy method, calculates changes in simulation state over specified time + void + update( double dt, int iter ); + // legacy method, checks for presence and height of traction wire for specified vehicle + void + update_traction( TDynamicObject *Vehicle ); + // legacy method, sends list of vehicles over network + void + DynamicList( bool const Onlycontrolled = false ) const; +}; + //--------------------------------------------------------------------------- diff --git a/EU07.cpp b/EU07.cpp index 1573a1ed..b2e41532 100644 --- a/EU07.cpp +++ b/EU07.cpp @@ -20,7 +20,10 @@ Stele, firleju, szociu, hunter, ZiomalCl, OLI_EU and others #include #include +#include "World.h" +#include "simulation.h" #include "Globals.h" +#include "Timer.h" #include "Logs.h" #include "keyboardinput.h" #include "mouseinput.h" @@ -336,16 +339,6 @@ int main(int argc, char *argv[]) BaseWindowProc = (WNDPROC)::SetWindowLongPtr( Hwnd, GWLP_WNDPROC, (LONG_PTR)WndProc ); // switch off the topmost flag ::SetWindowPos( Hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); - - //const HANDLE icon = ::LoadImage( - // ::GetModuleHandle( 0 ), - // MAKEINTRESOURCE( IDI_ICON1 ), - // IMAGE_ICON, - // ::GetSystemMetrics( SM_CXSMICON ), - // ::GetSystemMetrics( SM_CYSMICON ), - // 0 ); - //if( icon ) - // ::SendMessage( Hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast( icon ) ); #endif try { @@ -424,9 +417,11 @@ int main(int argc, char *argv[]) #ifdef _WIN32 Console::Off(); // wyłączenie konsoli (komunikacji zwrotnej) - delete pConsole; + SafeDelete( pConsole ); #endif + SafeDelete( simulation::Region ); + glfwDestroyWindow(window); glfwTerminate(); diff --git a/EvLaunch.cpp b/EvLaunch.cpp index 6e1dc704..af92cfbc 100644 --- a/EvLaunch.cpp +++ b/EvLaunch.cpp @@ -17,36 +17,14 @@ http://mozilla.org/MPL/2.0/. #include "EvLaunch.h" #include "Globals.h" #include "Logs.h" -#include "usefull.h" -#include "McZapkie/mctools.h" #include "Event.h" #include "MemCell.h" -#include "mtable.h" #include "Timer.h" #include "parser.h" #include "Console.h" -using namespace Mtable; - //--------------------------------------------------------------------------- -TEventLauncher::TEventLauncher() -{ // ustawienie początkowych wartości dla wszystkich zmiennych - iKey = 0; - DeltaTime = -1; - UpdatedTime = 0; - fVal1 = fVal2 = 0; - iHour = iMinute = -1; // takiego czasu nigdy nie będzie - dRadius = 0; - Event1 = Event2 = NULL; - MemCell = NULL; - iCheckMask = 0; -} - -void TEventLauncher::Init() -{ -} - // encodes expected key in a short, where low byte represents the actual key, // and the high byte holds modifiers: 0x1 = shift, 0x2 = ctrl, 0x4 = alt int vk_to_glfw_key( int const Keycode ) { @@ -145,9 +123,9 @@ bool TEventLauncher::Load(cParser *parser) *parser >> token; } return true; -}; +} -bool TEventLauncher::Render() +bool TEventLauncher::check_conditions() { //"renderowanie" wyzwalacza bool bCond = false; if (iKey != 0) @@ -199,13 +177,20 @@ bool TEventLauncher::Render() return bCond; // sprawdzanie dRadius w Ground.cpp } -bool TEventLauncher::IsGlobal() -{ // sprawdzenie, czy jest globalnym wyzwalaczem czasu - if (DeltaTime == 0) - if (iHour >= 0) - if (iMinute >= 0) - if (dRadius < 0.0) // bez ograniczenia zasięgu - return true; - return false; -}; +// sprawdzenie, czy jest globalnym wyzwalaczem czasu +bool TEventLauncher::IsGlobal() const { + + return ( ( DeltaTime == 0 ) + && ( iHour >= 0 ) + && ( iMinute >= 0 ) + && ( dRadius < 0.0 ) ); // bez ograniczenia zasięgu +} + +// calculates node's bounding radius +void +TEventLauncher::radius_() { + + m_area.radius = std::sqrt( dRadius ); +} + //--------------------------------------------------------------------------- diff --git a/EvLaunch.h b/EvLaunch.h index 42ce2c73..d6fe9324 100644 --- a/EvLaunch.h +++ b/EvLaunch.h @@ -10,32 +10,48 @@ http://mozilla.org/MPL/2.0/. #pragma once #include -#include "Classes.h" -class TEventLauncher -{ - private: - int iKey; - double DeltaTime; - double UpdatedTime; - double fVal1; - double fVal2; - std::string szText; - int iHour, iMinute; // minuta uruchomienia - public: - double dRadius; +#include "Classes.h" +#include "scenenode.h" + +class TEventLauncher : public editor::basic_node { + +public: +// constructor + TEventLauncher( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {} + // legacy constructor + TEventLauncher() = default; + +// methods + bool Load( cParser *parser ); + // checks conditions associated with the event. returns: true if the conditions are met + bool check_conditions(); + bool IsGlobal() const; +// members std::string asEvent1Name; std::string asEvent2Name; std::string asMemCellName; - TEvent *Event1; - TEvent *Event2; - TMemCell *MemCell; - int iCheckMask; - TEventLauncher(); - void Init(); - bool Load(cParser *parser); - bool Render(); - bool IsGlobal(); + TEvent *Event1 { nullptr }; + TEvent *Event2 { nullptr }; + TMemCell *MemCell { nullptr }; + int iCheckMask { 0 }; + double dRadius { 0.0 }; + +protected: + // calculates node's bounding radius + void + radius_(); + +private: +// members + int iKey { 0 }; + double DeltaTime { -1.0 }; + double UpdatedTime { 0.0 }; + double fVal1 { 0.0 }; + double fVal2 { 0.0 }; + std::string szText; + int iHour { -1 }; + int iMinute { -1 }; // minuta uruchomienia }; //--------------------------------------------------------------------------- diff --git a/Event.cpp b/Event.cpp index 41b40aca..81a3bd68 100644 --- a/Event.cpp +++ b/Event.cpp @@ -14,15 +14,12 @@ http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" -#include "Event.h" + +#include "event.h" +#include "simulation.h" #include "Globals.h" -#include "Logs.h" -#include "usefull.h" -#include "parser.h" #include "Timer.h" -#include "MemCell.h" -#include "Ground.h" -#include "McZapkie/mctools.h" +#include "Logs.h" TEvent::TEvent( std::string const &m ) : asNodeName( m ) @@ -36,13 +33,12 @@ TEvent::TEvent( std::string const &m ) : } }; -TEvent::~TEvent() -{ +TEvent::~TEvent() { + switch (Type) { // sprzątanie case tp_Multiple: - // SafeDeleteArray(Params[9].asText); //nie usuwać - nazwa obiektu powiązanego zamieniana na - // wskaźnik + // SafeDeleteArray(Params[9].asText); //nie usuwać - nazwa obiektu powiązanego zamieniana na wskaźnik if (iFlags & conditional_memstring) // o ile jest łańcuch do porównania w memcompare SafeDeleteArray(Params[10].asText); break; @@ -53,20 +49,16 @@ TEvent::~TEvent() SafeDeleteArray(Params[10].asText); break; case tp_Animation: // nic - // SafeDeleteArray(Params[9].asText); //nie usuwać - nazwa jest zamieniana na wskaźnik do - // submodelu - if (Params[0].asInt == 4) // jeśli z pliku VMD - delete[] (char*)(Params[8].asPointer); // zwolnić obszar + break; case tp_GetValues: // nic break; case tp_PutValues: // params[0].astext stores the token SafeDeleteArray( Params[ 0 ].asText ); break; + default: + break; } - evJoined = NULL; // nie usuwać podczepionych tutaj -}; - -void TEvent::Init(){ + evJoined = nullptr; // nie usuwać podczepionych tutaj }; @@ -78,8 +70,7 @@ void TEvent::Conditions(cParser *parser, std::string s) if (!asNodeName.empty()) { // podczepienie łańcucha, jeśli nie jest pusty // BUG: source of a memory leak -- the array never gets deleted. fix the destructor - Params[9].asText = new char[asNodeName.size() + 1]; // usuwane i zamieniane na - // wskaźnik + Params[9].asText = new char[asNodeName.size() + 1]; // usuwane i zamieniane na wskaźnik strcpy(Params[9].asText, asNodeName.c_str()); } parser->getTokens(); @@ -138,7 +129,7 @@ void TEvent::Conditions(cParser *parser, std::string s) } }; -void TEvent::Load(cParser *parser, vector3 *org) +void TEvent::Load(cParser *parser, Math3D::vector3 const &org) { std::string token; @@ -202,13 +193,12 @@ void TEvent::Load(cParser *parser, vector3 *org) parser->getTokens(); *parser >> token; - // str = AnsiString(token.c_str()); if (token != "none") asNodeName = token; // nazwa obiektu powiązanego if (asName.substr(0, 5) == "none_") - Type = tp_Ignored; // Ra: takie są ignorowane + m_ignored = true; // Ra: takie są ignorowane switch (Type) { @@ -218,7 +208,7 @@ void TEvent::Load(cParser *parser, vector3 *org) // if (Type==tp_UpdateValues) iFlags=0; //co modyfikować parser->getTokens(1, false); // case sensitive *parser >> token; - Params[0].asText = new char[token.size() + 1]; // BUG: source of memory leak + Params[0].asText = new char[token.size() + 1]; strcpy(Params[0].asText, token.c_str()); if (token != "*") // czy ma zostać bez zmian? iFlags |= update_memstring; @@ -294,12 +284,12 @@ void TEvent::Load(cParser *parser, vector3 *org) parser->getTokens(3); *parser >> Params[3].asdouble >> Params[4].asdouble >> Params[5].asdouble; // położenie // X,Y,Z - if (org) + if ( !(org == Math3D::vector3()) ) { // przesunięcie // tmp->pCenter.RotateY(aRotate.y/180.0*M_PI); //Ra 2014-11: uwzględnienie rotacji - Params[3].asdouble += org->x; // współrzędne w scenerii - Params[4].asdouble += org->y; - Params[5].asdouble += org->z; + Params[3].asdouble += org.x; // współrzędne w scenerii + Params[4].asdouble += org.y; + Params[5].asdouble += org.z; } // Params[12].asInt=0; parser->getTokens(1, false); // komendy 'case sensitive' @@ -597,7 +587,7 @@ void TEvent::Load(cParser *parser, vector3 *org) // str = AnsiString(token.c_str()); } while (token != "endevent"); break; - case tp_Ignored: // ignorowany +// case tp_Ignored: // ignorowany case tp_Unknown: // nieznany do { @@ -679,16 +669,16 @@ double TEvent::ValueGet(int n) return 0.0; // inne eventy się nie liczą }; -vector3 TEvent::PositionGet() const +glm::dvec3 TEvent::PositionGet() const { // pobranie współrzędnych eventu switch (Type) { // case tp_GetValues: - return Params[9].asMemCell->Position(); // współrzędne podłączonej komórki pamięci + return Params[9].asMemCell->location(); // współrzędne podłączonej komórki pamięci case tp_PutValues: - return vector3(Params[3].asdouble, Params[4].asdouble, Params[5].asdouble); + return glm::dvec3(Params[3].asdouble, Params[4].asdouble, Params[5].asdouble); } - return vector3(0, 0, 0); // inne eventy się nie liczą + return glm::dvec3(0, 0, 0); // inne eventy się nie liczą }; bool TEvent::StopCommand() @@ -714,3 +704,939 @@ void TEvent::Append(TEvent *e) e->bEnabled = true; // ten doczepiony może być tylko kolejkowany } }; + + + +event_manager::~event_manager() { + + for( auto *event : m_events ) { + delete event; + } +} + +// adds specified event launcher to the list of global launchers +void +event_manager::queue( TEventLauncher *Launcher ) { + + m_launcherqueue.emplace_back( Launcher ); +} + +// legacy method, updates event queues +void +event_manager::update() { + + // process currently queued events + CheckQuery(); + // test list of global events for possible new additions to the queue + for( auto *launcher : m_launcherqueue ) { + + if( true == launcher->check_conditions() ) { + // NOTE: we're presuming global events aren't going to use event2 + WriteLog( "Eventlauncher " + launcher->name() ); + if( launcher->Event1 ) { + AddToQuery( launcher->Event1, nullptr ); + } + } + } +} + +// adds provided event to the collection. returns: true on success +// TODO: return handle instead of pointer +bool +event_manager::insert( TEvent *Event ) { + + if( Event->Type == tp_Unknown ) { return false; } + + // najpierw sprawdzamy, czy nie ma, a potem dopisujemy + auto lookup = m_eventmap.find( Event->asName ); + if( lookup != m_eventmap.end() ) { + // duplicate of already existing event + auto const size = Event->asName.size(); + // zawsze jeden znak co najmniej jest + if( Event->asName[ 0 ] == '#' ) { + // utylizacja duplikatu z krzyżykiem + return false; + } + // tymczasowo wyjątki: + else if( ( size > 8 ) + && ( Event->asName.substr( 0, 9 ) == "lineinfo:" ) ) { + // tymczasowa utylizacja duplikatów W5 + return false; + } + else if( ( size > 8 ) + && ( Event->asName.substr( size - 8 ) == "_warning" ) ) { + // tymczasowa utylizacja duplikatu z trąbieniem + return false; + } + else if( ( size > 4 ) + && ( Event->asName.substr( size - 4 ) == "_shp" ) ) { + // nie podlegają logowaniu + // tymczasowa utylizacja duplikatu SHP + return false; + } + + auto *duplicate = m_events[ lookup->second ]; + if( Global::bJoinEvents ) { + // doczepka (taki wirtualny multiple bez warunków) + duplicate->Append( Event ); + } + else { + // NOTE: somewhat convoluted way to deal with 'replacing' events without leaving dangling pointers + // can be cleaned up if pointers to events were replaced with handles + ErrorLog( "Bad event: encountered duplicated event, \"" + Event->asName + "\"" ); + duplicate->Append( Event ); // doczepka (taki wirtualny multiple bez warunków) + duplicate->m_ignored = true; // dezaktywacja pierwotnego - taka proteza na wsteczną zgodność + } + } + + m_events.emplace_back( Event ); + if( lookup == m_eventmap.end() ) { + // if it's first event with such name, it's potential candidate for the execution queue + m_eventmap.emplace( Event->asName, m_events.size() - 1 ); + if( ( Event->m_ignored != true ) + && ( Event->asName.find( "onstart" ) != std::string::npos ) ) { + // event uruchamiany automatycznie po starcie + AddToQuery( Event, nullptr ); + } + } + + return true; +} + +// legacy method, returns pointer to specified event, or null +TEvent * +event_manager::FindEvent( std::string const &Name ) { + + if( Name.empty() ) { return nullptr; } + + auto const lookup = m_eventmap.find( Name ); + return ( + lookup != m_eventmap.end() ? + m_events[ lookup->second ] : + nullptr ); +} + +// legacy method, inserts specified event in the event query +bool +event_manager::AddToQuery( TEvent *Event, TDynamicObject *Owner ) { + + if( ( false == Event->m_ignored ) && ( true == Event->bEnabled ) ) { + // jeśli może być dodany do kolejki (nie używany w skanowaniu) + if( !Event->iQueued ) // jeśli nie dodany jeszcze do kolejki + { // kolejka eventów jest posortowana względem (fStartTime) + Event->Activator = Owner; + if( ( Event->Type == tp_AddValues ) + && ( Event->fDelay == 0.0 ) ) { + // eventy AddValues trzeba wykonywać natychmiastowo, inaczej kolejka może zgubić jakieś dodawanie + // Ra: kopiowanie wykonania tu jest bez sensu, lepiej by było wydzielić funkcję + // wykonującą eventy i ją wywołać + if( EventConditon( Event ) ) { // teraz mogą być warunki do tych eventów + Event->Params[ 5 ].asMemCell->UpdateValues( + Event->Params[ 0 ].asText, Event->Params[ 1 ].asdouble, + Event->Params[ 2 ].asdouble, Event->iFlags ); + if( Event->Params[ 6 ].asTrack ) { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla + // wszystkich 'dynamic' na danym torze + for( auto dynamic : Event->Params[ 6 ].asTrack->Dynamics ) { + Event->Params[ 5 ].asMemCell->PutCommand( + dynamic->Mechanik, + Event->Params[ 4 ].asLocation ); + } + //if (DebugModeFlag) + WriteLog( + "EVENT EXECUTED" + ( Owner ? ( " by " + Owner->asName ) : "" ) + ": AddValues & Track command - [" + + std::string{ Event->Params[ 0 ].asText } + "] [" + + to_string( Event->Params[ 1 ].asdouble, 2 ) + "] [" + + to_string( Event->Params[ 2 ].asdouble, 2 ) + " ]" ); + } + //else if (DebugModeFlag) + WriteLog( + "EVENT EXECUTED" + ( Owner ? ( " by " + Owner->asName ) : "" ) + ": AddValues - [" + + std::string( Event->Params[ 0 ].asText ) + "] [" + + to_string( Event->Params[ 1 ].asdouble, 2 ) + "] [" + + to_string( Event->Params[ 2 ].asdouble, 2 ) + "]" ); + } + // jeśli jest kolejny o takiej samej nazwie, to idzie do kolejki (and if there's no joint event it'll be set to null and processing will end here) + do { + Event = Event->evJoined; + // NOTE: we could've received a new event from joint event above, so we need to check conditions just in case and discard the bad events + // TODO: refactor this arrangement, it's hardly optimal + } while( ( Event != nullptr ) + && ( ( false == Event->bEnabled ) + || ( Event->iQueued > 0 ) ) ); + } + if( Event != nullptr ) { + // standardowe dodanie do kolejki + ++Event->iQueued; // zabezpieczenie przed podwójnym dodaniem do kolejki + WriteLog( "EVENT ADDED TO QUEUE" + ( Owner ? ( " by " + Owner->asName ) : "" ) + ": " + Event->asName ); + Event->fStartTime = std::abs( Event->fDelay ) + Timer::GetTime(); // czas od uruchomienia scenerii + if( Event->fRandomDelay > 0.0 ) { + // doliczenie losowego czasu opóźnienia + Event->fStartTime += Event->fRandomDelay * Random( 10000 ) * 0.0001; + } + if( QueryRootEvent != nullptr ) { + TEvent::AddToQuery( Event, QueryRootEvent ); + } + else { + QueryRootEvent = Event; + QueryRootEvent->evNext = nullptr; + } + } + } + } + return true; +} + +// legacy method, executes queued events +bool +event_manager::CheckQuery() { + + TLocation loc; + int i; + while( ( QueryRootEvent != nullptr ) + && ( QueryRootEvent->fStartTime < Timer::GetTime() ) ) + { // eventy są posortowana wg czasu wykonania + m_workevent = 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 = m_workevent->evNext; // pamiętając o następnym z kolejki + QueryRootEvent->fStartTime = m_workevent->fStartTime; // czas musi być ten sam, bo nie jest aktualizowany + QueryRootEvent->Activator = m_workevent->Activator; // pojazd aktywujący + QueryRootEvent->iQueued = 1; + // w sumie można by go dodać normalnie do kolejki, ale trzeba te połączone posortować wg czasu wykonania + } + else // a jak nazwa jest unikalna, to kolejka idzie dalej + QueryRootEvent = QueryRootEvent->evNext; // NULL w skrajnym przypadku + if( ( false == m_workevent->m_ignored ) && ( true == m_workevent->bEnabled ) ) { + // w zasadzie te wyłączone są skanowane i nie powinny się nigdy w kolejce znaleźć + --m_workevent->iQueued; // teraz moze być ponownie dodany do kolejki + WriteLog( "EVENT LAUNCHED" + ( m_workevent->Activator ? ( " by " + m_workevent->Activator->asName ) : "" ) + ": " + m_workevent->asName ); + switch (m_workevent->Type) + { + case tp_CopyValues: // skopiowanie wartości z innej komórki + m_workevent->Params[5].asMemCell->UpdateValues( + m_workevent->Params[9].asMemCell->Text(), + m_workevent->Params[9].asMemCell->Value1(), + m_workevent->Params[9].asMemCell->Value2(), + m_workevent->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(m_workevent)) + { // teraz mogą być warunki do tych eventów + if (m_workevent->Type != tp_CopyValues) // dla CopyValues zrobiło się wcześniej + m_workevent->Params[5].asMemCell->UpdateValues( + m_workevent->Params[0].asText, + m_workevent->Params[1].asdouble, + m_workevent->Params[2].asdouble, + m_workevent->iFlags); + if (m_workevent->Params[6].asTrack) + { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla + // wszystkich 'dynamic' na danym torze + for( auto dynamic : m_workevent->Params[ 6 ].asTrack->Dynamics ) { + m_workevent->Params[ 5 ].asMemCell->PutCommand( + dynamic->Mechanik, + m_workevent->Params[ 4 ].asLocation ); + } + //if (DebugModeFlag) + WriteLog("Type: UpdateValues & Track command - [" + + m_workevent->Params[5].asMemCell->Text() + "] [" + + to_string( m_workevent->Params[ 5 ].asMemCell->Value1(), 2 ) + "] [" + + to_string( m_workevent->Params[ 5 ].asMemCell->Value2(), 2 ) + "]" ); + } + else //if (DebugModeFlag) + WriteLog("Type: UpdateValues - [" + + m_workevent->Params[5].asMemCell->Text() + "] [" + + to_string( m_workevent->Params[ 5 ].asMemCell->Value1(), 2 ) + "] [" + + to_string( m_workevent->Params[ 5 ].asMemCell->Value2(), 2 ) + "]" ); + } + break; + case tp_GetValues: { + if( m_workevent->Activator ) { + // TODO: re-enable when messaging module is in place + if( Global::iMultiplayer ) { + // potwierdzenie wykonania dla serwera (odczyt semafora już tak nie działa) + multiplayer::WyslijEvent( m_workevent->asName, m_workevent->Activator->name() ); + } + m_workevent->Params[ 9 ].asMemCell->PutCommand( + m_workevent->Activator->Mechanik, + m_workevent->Params[ 8 ].asLocation ); + } + WriteLog( "Type: GetValues" ); + break; + } + case tp_PutValues: { + if (m_workevent->Activator) { + // zamiana, bo fizyka ma inaczej niż sceneria + loc.X = -m_workevent->Params[3].asdouble; + loc.Y = m_workevent->Params[5].asdouble; + loc.Z = m_workevent->Params[4].asdouble; + if( m_workevent->Activator->Mechanik ) { + // przekazanie rozkazu do AI + m_workevent->Activator->Mechanik->PutCommand( + m_workevent->Params[0].asText, m_workevent->Params[1].asdouble, + m_workevent->Params[2].asdouble, loc); + } + else { + // przekazanie do pojazdu + m_workevent->Activator->MoverParameters->PutCommand( + m_workevent->Params[0].asText, m_workevent->Params[1].asdouble, + m_workevent->Params[2].asdouble, loc); + } + WriteLog("Type: PutValues - [" + + std::string(m_workevent->Params[0].asText) + "] [" + + to_string( m_workevent->Params[ 1 ].asdouble, 2 ) + "] [" + + to_string( m_workevent->Params[ 2 ].asdouble, 2 ) + "]" ); + } + break; + } + case tp_Lights: { + if( m_workevent->Params[ 9 ].asModel ) { + for( i = 0; i < iMaxNumLights; ++i ) { + if( m_workevent->Params[ i ].asdouble >= 0 ) { + // -1 zostawia bez zmiany + m_workevent->Params[ 9 ].asModel->LightSet( + i, + m_workevent->Params[ i ].asdouble ); + } + } + } + break; + } + case tp_Visible: { + if( m_workevent->Params[ 9 ].asEditorNode ) + m_workevent->Params[ 9 ].asEditorNode->visible( m_workevent->Params[ i ].asInt > 0 ); + break; + } + case tp_Velocity: { + Error( "Not implemented yet :(" ); + break; + } + case tp_Exit: { + Global::iTextMode = -1; // wyłączenie takie samo jak sekwencja F10 -> Y + return false; + } + case tp_Sound: { + if (m_workevent->Params[ 9 ].tsTextSound == nullptr) + break; + switch( m_workevent->Params[ 0 ].asInt ) { + // trzy możliwe przypadki: + case 0: { + m_workevent->Params[ 9 ].tsTextSound->stop(); + break; + } + case 1: { + m_workevent->Params[ 9 ].tsTextSound->loop(false).play(); + break; + } + case -1: { + m_workevent->Params[ 9 ].tsTextSound->loop().play(); + break; + } + default: { + break; + } + } + break; + } + case tp_Disable: + Error("Not implemented yet :("); + break; + case tp_Animation: { + switch( m_workevent->Params[ 0 ].asInt ) { + case 1: { + m_workevent->Params[ 9 ].asAnimContainer->SetRotateAnim( + Math3D::vector3 { + m_workevent->Params[ 1 ].asdouble, + m_workevent->Params[ 2 ].asdouble, + m_workevent->Params[ 3 ].asdouble }, + m_workevent->Params[ 4 ].asdouble ); + break; + } + case 2: { + m_workevent->Params[ 9 ].asAnimContainer->SetTranslateAnim( + Math3D::vector3 { + m_workevent->Params[ 1 ].asdouble, + m_workevent->Params[ 2 ].asdouble, + m_workevent->Params[ 3 ].asdouble }, + m_workevent->Params[ 4 ].asdouble ); + break; + } + case 4: { + m_workevent->Params[ 9 ].asModel->AnimationVND( + m_workevent->Params[ 8 ].asPointer, + m_workevent->Params[ 1 ].asdouble, // tu mogą być dodatkowe parametry, np. od-do + m_workevent->Params[ 2 ].asdouble, + m_workevent->Params[ 3 ].asdouble, + m_workevent->Params[ 4 ].asdouble ); + break; + } + default: { + break; + } + } + break; + } + case tp_Switch: { + if( m_workevent->Params[ 9 ].asTrack ) { + m_workevent->Params[ 9 ].asTrack->Switch( + m_workevent->Params[ 0 ].asInt, + m_workevent->Params[ 1 ].asdouble, + m_workevent->Params[ 2 ].asdouble ); + } + if( Global::iMultiplayer ) { + // dajemy znać do serwera o przełożeniu + multiplayer::WyslijEvent( m_workevent->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 (m_workevent->Params[9].asTrack) { + // prędkość na zwrotnicy może być ograniczona z góry we wpisie, większej się nie ustawi eventem + m_workevent->Params[9].asTrack->VelocitySet(m_workevent->Params[0].asdouble); + // wyświetlana jest ta faktycznie ustawiona + WriteLog( "Type: TrackVel - [" + + to_string( m_workevent->Params[ 0 ].asdouble, 2 ) + "]" + + ( DebugModeFlag ? + ", actual [ " + to_string( m_workevent->Params[ 9 ].asTrack->VelocityGet(), 2 ) + "]" : + "" ) ); + } + break; + case tp_DynVel: + Error("Event \"DynVel\" is obsolete"); + break; + case tp_Multiple: { + auto const bCondition = EventConditon(m_workevent); + if( ( bCondition ) + || ( m_workevent->iFlags & conditional_anyelse ) ) { + // warunek spelniony albo było użyte else + WriteLog("Type: Multi-event"); + for (i = 0; i < 8; ++i) { + // dodawane do kolejki w kolejności zapisania + if( m_workevent->Params[ i ].asEvent ) { + if( bCondition != ( ( ( m_workevent->iFlags & ( conditional_else << i ) ) != 0 ) ) ) { + if( m_workevent->Params[ i ].asEvent != m_workevent ) + AddToQuery( m_workevent->Params[ i ].asEvent, m_workevent->Activator ); // normalnie dodać + else { + // jeśli ma być rekurencja to musi mieć sensowny okres powtarzania + if( m_workevent->fDelay >= 5.0 ) { + AddToQuery( m_workevent, m_workevent->Activator ); + } + } + } + } + } + if( Global::iMultiplayer ) { + // dajemy znać do serwera o wykonaniu + if( ( m_workevent->iFlags & conditional_anyelse ) == 0 ) { + // jednoznaczne tylko, gdy nie było else + if( m_workevent->Activator ) { + multiplayer::WyslijEvent( m_workevent->asName, m_workevent->Activator->name() ); + } + else { + multiplayer::WyslijEvent( m_workevent->asName, "" ); + } + } + } + } + break; + } + case tp_WhoIs: { + // pobranie nazwy pociągu do komórki pamięci + if (m_workevent->iFlags & update_load) { + // jeśli pytanie o ładunek + if( m_workevent->iFlags & update_memadd ) { + // jeśli typ pojazdu + // TODO: define and recognize individual request types + auto const owner = ( + ( ( m_workevent->Activator->Mechanik != nullptr ) && ( m_workevent->Activator->Mechanik->Primary() ) ) ? + m_workevent->Activator->Mechanik : + m_workevent->Activator->ctOwner ); + auto const consistbrakelevel = ( + owner != nullptr ? + owner->fReady : + -1.0 ); + auto const collisiondistance = ( + owner != nullptr ? + owner->TrackBlock() : + -1.0 ); + + m_workevent->Params[ 9 ].asMemCell->UpdateValues( + m_workevent->Activator->MoverParameters->TypeName, // typ pojazdu + consistbrakelevel, + collisiondistance, + m_workevent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) ); + + WriteLog( + "Type: WhoIs (" + to_string( m_workevent->iFlags ) + ") - " + + "[name: " + m_workevent->Activator->MoverParameters->TypeName + "], " + + "[consist brake level: " + to_string( consistbrakelevel, 2 ) + "], " + + "[obstacle distance: " + to_string( collisiondistance, 2 ) + " m]" ); + } + else { + // jeśli parametry ładunku + m_workevent->Params[ 9 ].asMemCell->UpdateValues( + m_workevent->Activator->MoverParameters->LoadType, // nazwa ładunku + m_workevent->Activator->MoverParameters->Load, // aktualna ilość + m_workevent->Activator->MoverParameters->MaxLoad, // maksymalna ilość + m_workevent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) ); + + WriteLog( + "Type: WhoIs (" + to_string( m_workevent->iFlags ) + ") - " + + "[load type: " + m_workevent->Activator->MoverParameters->LoadType + "], " + + "[current load: " + to_string( m_workevent->Activator->MoverParameters->Load, 2 ) + "], " + + "[max load: " + to_string( m_workevent->Activator->MoverParameters->MaxLoad, 2 ) + "]" ); + } + } + else if (m_workevent->iFlags & update_memadd) + { // jeśli miejsce docelowe pojazdu + m_workevent->Params[ 9 ].asMemCell->UpdateValues( + m_workevent->Activator->asDestination, // adres docelowy + m_workevent->Activator->DirectionGet(), // kierunek pojazdu względem czoła składu (1=zgodny,-1=przeciwny) + m_workevent->Activator->MoverParameters->Power, // moc pojazdu silnikowego: 0 dla wagonu + m_workevent->iFlags & (update_memstring | update_memval1 | update_memval2)); + + WriteLog( + "Type: WhoIs (" + to_string( m_workevent->iFlags ) + ") - " + + "[destination: " + m_workevent->Activator->asDestination + "], " + + "[direction: " + to_string( m_workevent->Activator->DirectionGet() ) + "], " + + "[engine power: " + to_string( m_workevent->Activator->MoverParameters->Power, 2 ) + "]" ); + } + else if (m_workevent->Activator->Mechanik) + if (m_workevent->Activator->Mechanik->Primary()) + { // tylko jeśli ktoś tam siedzi - nie powinno dotyczyć pasażera! + m_workevent->Params[ 9 ].asMemCell->UpdateValues( + m_workevent->Activator->Mechanik->TrainName(), + m_workevent->Activator->Mechanik->StationCount() - m_workevent->Activator->Mechanik->StationIndex(), // ile przystanków do końca + m_workevent->Activator->Mechanik->IsStop() ? + 1 : + 0, // 1, gdy ma tu zatrzymanie + m_workevent->iFlags); + WriteLog("Train detected: " + m_workevent->Activator->Mechanik->TrainName()); + } + break; + } + case tp_LogValues: { + // zapisanie zawartości komórki pamięci do logu + if( m_workevent->Params[ 9 ].asMemCell ) { + // jeśli była podana nazwa komórki + WriteLog( "Memcell \"" + m_workevent->asNodeName + "\": [" + + m_workevent->Params[ 9 ].asMemCell->Text() + "] [" + + to_string( m_workevent->Params[ 9 ].asMemCell->Value1(), 2 ) + "] [" + + to_string( m_workevent->Params[ 9 ].asMemCell->Value2(), 2 ) + "]" ); + } + else { + // lista wszystkich + simulation::Memory.log_all(); + } + break; + } + case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource) + if (m_workevent->Params[9].psPower) + { // na razie takie chamskie ustawienie napięcia zasilania + WriteLog("Type: Voltage"); + m_workevent->Params[9].psPower->VoltageSet(m_workevent->Params[0].asdouble); + } + case tp_Friction: // zmiana tarcia na scenerii + { // na razie takie chamskie ustawienie napięcia zasilania + WriteLog("Type: Friction"); + Global::fFriction = (m_workevent->Params[0].asdouble); + } + break; + case tp_Message: // wyświetlenie komunikatu + break; + case tp_Lua: + ((lua::eventhandler_t)m_workevent->Params[0].asPointer)(m_workevent, m_workevent->Activator); + break; + } // switch (tmpEvent->Type) + } // if (tmpEvent->bEnabled) + } // while + return true; +} + +// legacy method, initializes events after deserialization from scenario file +void +event_manager::InitEvents() { + + //łączenie eventów z pozostałymi obiektami + for( auto *event : m_events ) { + + switch( event->Type ) { + + case tp_AddValues: // sumowanie wartości + case tp_UpdateValues: { // zmiana wartości + auto *cell = simulation::Memory.find( event->asNodeName ); // nazwa komórki powiązanej z eventem + if( cell != nullptr ) { // McZapkie-100302 + if( event->iFlags & ( conditional_trackoccupied | conditional_trackfree ) ) { + // jeśli chodzi o zajetosc toru (tor może być inny, niż wpisany w komórce) + // nazwa toru ta sama, co nazwa komórki + event->Params[ 9 ].asTrack = simulation::Paths.find( event->asNodeName ); + if( event->Params[ 9 ].asTrack == nullptr ) { + ErrorLog( "Bad event: track \"" + event->asNodeName + "\" referenced in event \"" + event->asName + "\" doesn't exist" ); + } + } + event->Params[ 4 ].asLocation = &( cell->location() ); + event->Params[ 5 ].asMemCell = cell; // komórka do aktualizacji + if( event->iFlags & ( conditional_memcompare ) ) { + // komórka do badania warunku + event->Params[ 9 ].asMemCell = cell; + } + if( false == cell->asTrackName.empty() ) { + // tor powiązany z komórką powiązaną z eventem + // tu potrzebujemy wskaźnik do komórki w (tmp) + event->Params[ 6 ].asTrack = simulation::Paths.find( cell->asTrackName ); + if( event->Params[ 6 ].asTrack == nullptr ) { + ErrorLog( "Bad memcell: track \"" + cell->asTrackName + "\" referenced in memcell \"" + cell->name() + "\" doesn't exist" ); + } + } + else { + event->Params[ 6 ].asTrack = nullptr; + } + } + else { + // nie ma komórki, to nie będzie działał poprawnie + event->m_ignored = true; // deaktywacja + ErrorLog( "Bad event: event \"" + event->asName + "\" cannot find memcell \"" + event->asNodeName + "\"" ); + } + break; + } + case tp_LogValues: { + // skojarzenie z memcell + if( event->asNodeName.empty() ) { // brak skojarzenia daje logowanie wszystkich + event->Params[ 9 ].asMemCell = nullptr; + break; + } + } + case tp_GetValues: + case tp_WhoIs: { + auto *cell = simulation::Memory.find( event->asNodeName ); + if( cell != nullptr ) { + event->Params[ 8 ].asLocation = &( cell->location() ); + event->Params[ 9 ].asMemCell = cell; + if( ( event->Type == tp_GetValues ) + && ( cell->IsVelocity() ) ) { + // jeśli odczyt komórki a komórka zawiera komendę SetVelocity albo ShuntVelocity + // to event nie będzie dodawany do kolejki + event->bEnabled = false; + } + } + else { + // nie ma komórki, to nie będzie działał poprawnie + event->m_ignored = true; // deaktywacja + ErrorLog( "Bad event: event \"" + event->asName + "\" cannot find memcell \"" + event->asNodeName + "\"" ); + } + break; + } + case tp_CopyValues: { + // skopiowanie komórki do innej + auto *cell = simulation::Memory.find( event->asNodeName ); // komórka docelowa + if( cell != nullptr ) { + event->Params[ 4 ].asLocation = &( cell->location() ); + event->Params[ 5 ].asMemCell = cell; // komórka docelowa + if( false == cell->asTrackName.empty() ) { + // tor powiązany z komórką powiązaną z eventem + // tu potrzebujemy wskaźnik do komórki w (tmp) + event->Params[ 6 ].asTrack = simulation::Paths.find( cell->asTrackName ); + if( event->Params[ 6 ].asTrack == nullptr ) { + ErrorLog( "Bad memcell: track \"" + cell->asTrackName + "\" referenced in memcell \"" + cell->name() + "\" doesn't exists" ); + } + } + else { + event->Params[ 6 ].asTrack = nullptr; + } + } + else { + ErrorLog( "Bad event: copyvalues event \"" + event->asName + "\" cannot find memcell \"" + event->asNodeName + "\"" ); + } + std::string const cellastext { event->Params[ 9 ].asText }; + cell = simulation::Memory.find( event->Params[ 9 ].asText ); // komórka źódłowa + SafeDeleteArray( event->Params[ 9 ].asText ); // usunięcie nazwy komórki + if( cell != nullptr ) { + event->Params[ 8 ].asLocation = &( cell->location() ); + event->Params[ 9 ].asMemCell = cell; // komórka źródłowa + } + else { + ErrorLog( "Bad event: copyvalues event \"" + event->asName + "\" cannot find memcell \"" + cellastext + "\"" ); + } + break; + } + case tp_Animation: { + // animacja modelu + // retrieve target name parameter + std::string const cellastext = event->Params[ 9 ].asText; + SafeDeleteArray( event->Params[ 9 ].asText ); + // egzemplarz modelu do animowania + auto *instance = simulation::Instances.find( event->asNodeName ); + if( instance != nullptr ) { + if( event->Params[ 0 ].asInt == 4 ) { + // model dla całomodelowych animacji + event->Params[ 9 ].asModel = instance; + } + else { + // standardowo przypisanie submodelu + event->Params[ 9 ].asAnimContainer = instance->GetContainer( cellastext ); // submodel + if( event->Params[ 9 ].asAnimContainer ) { + event->Params[ 9 ].asAnimContainer->WillBeAnimated(); // oflagowanie animacji + if( event->Params[ 9 ].asAnimContainer->Event() == nullptr ) { + // nie szukać, gdy znaleziony + event->Params[ 9 ].asAnimContainer->EventAssign( + FindEvent( event->asNodeName + "." + cellastext + ":done" ) ); + } + } + } + } + else { + ErrorLog( "Bad event: animation event \"" + event->asName + "\" cannot find model instance \"" + event->asNodeName + "\"" ); + } + event->asNodeName = ""; + break; + } + case tp_Lights: { + // zmiana świeteł modelu + auto *instance = simulation::Instances.find( event->asNodeName ); + if( instance != nullptr ) + event->Params[ 9 ].asModel = instance; + else + ErrorLog( "Bad event: lights event \"" + event->asName + "\" cannot find model instance \"" + event->asNodeName + "\"" ); + event->asNodeName = ""; + break; + } + case tp_Visible: { + // ukrycie albo przywrócenie obiektu + editor::basic_node *node = simulation::Instances.find( event->asNodeName ); // najpierw model + if( node == nullptr ) { + // albo tory? + node = simulation::Paths.find( event->asNodeName ); + } + if( node == nullptr ) { + // może druty? + node = simulation::Traction.find( event->asNodeName ); + } + if( node != nullptr ) + event->Params[ 9 ].asEditorNode = node; + else + ErrorLog( "Bad event: visibility event \"" + event->asName + "\" cannot find item \"" + event->asNodeName + "\"" ); + event->asNodeName = ""; + break; + } + case tp_Switch: { + // przełożenie zwrotnicy albo zmiana stanu obrotnicy + auto *track = simulation::Paths.find( event->asNodeName ); + if( track != nullptr ) { + // dowiązanie toru + if( track->iAction == 0 ) { + // jeśli nie jest zwrotnicą ani obrotnicą to będzie się zmieniał stan uszkodzenia + track->iAction |= 0x100; + } + event->Params[ 9 ].asTrack = track; + if( ( event->Params[ 0 ].asInt == 0 ) + && ( event->Params[ 2 ].asdouble >= 0.0 ) ) { + // jeśli przełącza do stanu 0 & jeśli jest zdefiniowany dodatkowy ruch iglic + // przesłanie parametrów + event->Params[ 9 ].asTrack->Switch( + event->Params[ 0 ].asInt, + event->Params[ 1 ].asdouble, + event->Params[ 2 ].asdouble ); + } + } + else { + ErrorLog( "Bad event: switch event \"" + event->asName + "\" cannot find track \"" + event->asNodeName + "\"" ); + } + event->asNodeName = ""; + break; + } + case tp_Sound: { + // odtworzenie dźwięku + auto *sound = sound_man->create_sound(event->asNodeName); + if( sound != nullptr ) + event->Params[ 9 ].tsTextSound = sound; + else + ErrorLog( "Bad event: sound event \"" + event->asName + "\" cannot find static sound \"" + event->asNodeName + "\"" ); + event->asNodeName = ""; + break; + } + case tp_TrackVel: { + // ustawienie prędkości na torze + if( false == event->asNodeName.empty() ) { + auto *track = simulation::Paths.find( event->asNodeName ); + if( track != nullptr ) { + // flaga zmiany prędkości toru jest istotna dla skanowania + track->iAction |= 0x200; + event->Params[ 9 ].asTrack = track; + } + else { + ErrorLog( "Bad event: track velocity event \"" + event->asName + "\" cannot find track \"" + event->asNodeName + "\"" ); + } + } + event->asNodeName = ""; + break; + } + case tp_DynVel: { + // komunikacja z pojazdem o konkretnej nazwie + if( event->asNodeName == "activator" ) + event->Params[ 9 ].asDynamic = nullptr; + else { + auto *vehicle = simulation::Vehicles.find( event->asNodeName ); + if( vehicle != nullptr ) + event->Params[ 9 ].asDynamic = vehicle; + else + ErrorLog( "Bad event: vehicle velocity event \"" + event->asName + "\" cannot find vehicle \"" + event->asNodeName + "\"" ); + } + event->asNodeName = ""; + break; + } + case tp_Multiple: { + std::string cellastext; + if( event->Params[ 9 ].asText != nullptr ) { // przepisanie nazwy do bufora + cellastext = event->Params[ 9 ].asText; + SafeDeleteArray( event->Params[ 9 ].asText ); + event->Params[ 9 ].asPointer = nullptr; // zerowanie wskaźnika, aby wykryć brak obeiktu + } + if( event->iFlags & ( conditional_trackoccupied | conditional_trackfree ) ) { + // jeśli chodzi o zajetosc toru + event->Params[ 9 ].asTrack = simulation::Paths.find( cellastext ); + if( event->Params[ 9 ].asTrack == nullptr ) { + ErrorLog( "Bad event: multi-event \"" + event->asName + "\" cannot find track \"" + cellastext + "\"" ); + event->iFlags &= ~( conditional_trackoccupied | conditional_trackfree ); // zerowanie flag + } + } + else if( event->iFlags & ( conditional_memstring | conditional_memval1 | conditional_memval2 ) ) { + // jeśli chodzi o komorke pamieciową + event->Params[ 9 ].asMemCell = simulation::Memory.find( cellastext ); + if( event->Params[ 9 ].asMemCell == nullptr ) { + ErrorLog( "Bad event: multi-event \"" + event->asName + "\" cannot find memory cell \"" + cellastext + "\"" ); + event->iFlags &= ~( conditional_memstring | conditional_memval1 | conditional_memval2 ); + } + } + for( int i = 0; i < 8; ++i ) { + if( event->Params[ i ].asText != nullptr ) { + cellastext = event->Params[ i ].asText; + SafeDeleteArray( event->Params[ i ].asText ); + event->Params[ i ].asEvent = FindEvent( cellastext ); + if( event->Params[ i ].asEvent == nullptr ) { + // Ra: tylko w logu informacja o braku + ErrorLog( "Bad event: multi-event \"" + event->asName + "\" cannot find event \"" + cellastext + "\"" ); + } + } + } + break; + } + case tp_Voltage: { + // zmiana napięcia w zasilaczu (TractionPowerSource) + if( false == event->asNodeName.empty() ) { + auto *powersource = simulation::Powergrid.find( event->asNodeName ); // podłączenie zasilacza + if( powersource != nullptr ) + event->Params[ 9 ].psPower = powersource; + else + ErrorLog( "Bad event: voltage event \"" + event->asName + "\" cannot find power source \"" + event->asNodeName + "\"" ); + } + event->asNodeName = ""; + break; + } + case tp_Message: { + // wyświetlenie komunikatu + break; + } + + } // switch + + if( event->fDelay < 0 ) { AddToQuery( event, nullptr ); } + } +} + +// legacy method, initializes event launchers after deserialization from scenario file +void +event_manager::InitLaunchers() { + + for( auto *launcher : m_launchers.sequence() ) { + + if( launcher->iCheckMask != 0 ) { + if( launcher->asMemCellName != "none" ) { + // jeśli jest powiązana komórka pamięci + launcher->MemCell = simulation::Memory.find( launcher->asMemCellName ); + if( launcher->MemCell == nullptr ) { + ErrorLog( "Bad scenario: event launcher \"" + launcher->name() + "\" cannot find memcell \"" + launcher->asMemCellName + "\"" ); + } + } + else { + launcher->MemCell = nullptr; + } + } + + launcher->Event1 = ( + launcher->asEvent1Name != "none" ? + simulation::Events.FindEvent( launcher->asEvent1Name ) : + nullptr ); + launcher->Event2 = ( + launcher->asEvent2Name != "none" ? + simulation::Events.FindEvent( launcher->asEvent2Name ) : + nullptr ); + } +} + +// legacy method, verifies condition for specified event +bool +event_manager::EventConditon( TEvent *Event ) { + + if (Event->iFlags <= update_only) + return true; // bezwarunkowo + + if (Event->iFlags & conditional_trackoccupied) + return (!Event->Params[9].asTrack->IsEmpty()); + else if (Event->iFlags & conditional_trackfree) + return (Event->Params[9].asTrack->IsEmpty()); + else if (Event->iFlags & conditional_propability) + { + double rprobability = Random(); + WriteLog( "Random integer: " + std::to_string( rprobability ) + " / " + std::to_string( Event->Params[ 10 ].asdouble ) ); + return (Event->Params[10].asdouble > rprobability); + } + else if( Event->iFlags & conditional_memcompare ) { + // porównanie wartości + if( nullptr == Event->Params[9].asMemCell ) { + + ErrorLog( "Event " + Event->asName + " trying conditional_memcompare with nonexistent memcell" ); + return true; // though this is technically error, we report success to maintain backward compatibility + } + auto const comparisonresult = + m_workevent->Params[ 9 ].asMemCell->Compare( + ( Event->Params[ 10 ].asText != nullptr ? + Event->Params[ 10 ].asText : + "" ), + Event->Params[ 11 ].asdouble, + Event->Params[ 12 ].asdouble, + Event->iFlags ); + + std::string comparisonlog = "Type: MemCompare - "; + + comparisonlog += + "[" + Event->Params[ 9 ].asMemCell->Text() + "]" + + " [" + to_string( Event->Params[ 9 ].asMemCell->Value1(), 2 ) + "]" + + " [" + to_string( m_workevent->Params[ 9 ].asMemCell->Value2(), 2 ) + "]"; + + comparisonlog += ( + true == comparisonresult ? + " == " : + " != " ); + + comparisonlog += ( + TestFlag( Event->iFlags, conditional_memstring ) ? + "[" + std::string( m_workevent->Params[ 10 ].asText ) + "]" : + "[*]" ); + comparisonlog += ( + TestFlag( m_workevent->iFlags, conditional_memval1 ) ? + " [" + to_string( m_workevent->Params[ 11 ].asdouble, 2 ) + "]" : + " [*]" ); + comparisonlog += ( + TestFlag( m_workevent->iFlags, conditional_memval2 ) ? + " [" + to_string( m_workevent->Params[ 12 ].asdouble, 2 ) + "]" : + " [*]" ); + + WriteLog( comparisonlog ); + return comparisonresult; + } + // unrecognized request + return false; +} diff --git a/Event.h b/Event.h index abc9dd4e..c002f015 100644 --- a/Event.h +++ b/Event.h @@ -12,8 +12,9 @@ http://mozilla.org/MPL/2.0/. #include #include "dumb3d.h" #include "Classes.h" - -using namespace Math3D; +#include "Names.h" +#include "scenenode.h" +#include "EvLaunch.h" enum TEventType { tp_Unknown, @@ -32,7 +33,7 @@ enum TEventType { tp_TrackVel, tp_Multiple, tp_AddValues, - tp_Ignored, +// tp_Ignored, // NOTE: refactored to separate flag tp_CopyValues, tp_WhoIs, tp_LogValues, @@ -63,7 +64,8 @@ union TParam { void *asPointer; TMemCell *asMemCell; - TGroundNode *nGroundNode; + editor::basic_node *asEditorNode; + glm::dvec3 const *asLocation; TTrack *asTrack; TAnimModel *asModel; TAnimContainer *asAnimContainer; @@ -86,11 +88,10 @@ class TEvent // zmienne: ev* public: std::string asName; + bool m_ignored { false }; // replacement for tp_ignored bool bEnabled = false; // false gdy ma nie być dodawany do kolejki (skanowanie sygnałów) int iQueued = 0; // ile razy dodany do kolejki - // bool bIsHistory; TEvent *evNext = nullptr; // następny w kolejce - TEvent *evNext2 = nullptr; TEventType Type = tp_Unknown; double fStartTime = 0.0; double fDelay = 0.0; @@ -100,19 +101,79 @@ class TEvent // zmienne: ev* std::string asNodeName; // McZapkie-100302 - dodalem zeby zapamietac nazwe toru TEvent *evJoined = nullptr; // kolejny event z tą samą nazwą - od wersji 378 double fRandomDelay = 0.0; // zakres dodatkowego opóźnienia // standardowo nie będzie dodatkowego losowego opóźnienia - public: // metody +public: + // metody TEvent(std::string const &m = ""); ~TEvent(); - void Init(); - void Load(cParser *parser, vector3 *org); + void Load(cParser *parser, Math3D::vector3 const &org); static void AddToQuery( TEvent *Event, TEvent *&Start ); std::string CommandGet(); TCommandType Command(); double ValueGet(int n); - vector3 PositionGet() const; + glm::dvec3 PositionGet() const; bool StopCommand(); void StopCommandSent(); void Append(TEvent *e); }; +class event_manager { + +public: +// destructor + ~event_manager(); +// methods + // adds specified event launcher to the list of global launchers + void + queue( TEventLauncher *Launcher ); + // legacy method, updates event queues + void + update(); + // adds provided event to the collection. returns: true on success + // TBD, TODO: return handle to the event + bool + insert( TEvent *Event ); + bool + insert( TEventLauncher *Launcher ) { + return m_launchers.insert( Launcher ); } + // legacy method, returns pointer to specified event, or null + TEvent * + FindEvent( std::string const &Name ); + // legacy method, inserts specified event in the event query + bool + AddToQuery( TEvent *Event, TDynamicObject *Owner ); + // legacy method, executes queued events + bool + CheckQuery(); + // legacy method, initializes events after deserialization from scenario file + void + InitEvents(); + // legacy method, initializes event launchers after deserialization from scenario file + void + InitLaunchers(); + +private: +// types + using event_sequence = std::deque; + using event_map = std::unordered_map; + using eventlauncher_sequence = std::vector; + +// methods + // legacy method, verifies condition for specified event + bool + EventConditon( TEvent *Event ); + +// members + event_sequence m_events; +/* + // NOTE: disabled until event class refactoring + event_sequence m_eventqueue; +*/ + // legacy version of the above + TEvent *QueryRootEvent { nullptr }; + TEvent *m_workevent { nullptr }; + event_map m_eventmap; + basic_table m_launchers; + eventlauncher_sequence m_launcherqueue; +}; + //--------------------------------------------------------------------------- diff --git a/Gauge.cpp b/Gauge.cpp index 3339e581..9ce331ad 100644 --- a/Gauge.cpp +++ b/Gauge.cpp @@ -143,10 +143,10 @@ TGauge::Load_mapping( cParser &Input ) { sound_man->create_sound(value) : nullptr ); } - else if( key.find( "sound" ) == 0 ) { + else if( key.compare( 0, std::min( key.size(), 5 ), "sound" ) == 0 ) { // sounds assigned to specific gauge values, defined by key soundFoo: where Foo = value - auto const indexstart = key.find_first_of( "-1234567890" ); - auto const indexend = key.find_first_not_of( "-1234567890", indexstart ); + auto const indexstart { key.find_first_of( "-1234567890" ) }; + auto const indexend { key.find_first_not_of( "-1234567890", indexstart ) }; if( indexstart != std::string::npos ) { m_soundfxvalues.emplace( std::stoi( key.substr( indexstart, indexend - indexstart ) ), diff --git a/Gauge.h b/Gauge.h index 12e84494..77eaf475 100644 --- a/Gauge.h +++ b/Gauge.h @@ -12,15 +12,14 @@ http://mozilla.org/MPL/2.0/. #include "Classes.h" #include "sound.h" -typedef enum -{ // typ ruchu +enum TGaugeType { + // typ ruchu gt_Unknown, // na razie nie znany gt_Rotate, // obrót gt_Move, // przesunięcie równoległe - gt_Wiper, // obrót trzech kolejnych submodeli o ten sam kąt (np. wycieraczka, drzwi - // harmonijkowe) + gt_Wiper, // obrót trzech kolejnych submodeli o ten sam kąt (np. wycieraczka, drzwi harmonijkowe) gt_Digital // licznik cyfrowy, np. kilometrów -} TGaugeType; +}; // animowany wskaźnik, mogący przyjmować wiele stanów pośrednich class TGauge { diff --git a/Globals.cpp b/Globals.cpp index eb6c9e93..60baac5e 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -12,22 +12,17 @@ http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" - #include "Globals.h" -#include "usefull.h" -//#include "Mover.h" -#include "Console.h" -#include "Driver.h" -#include "Logs.h" -#include "PyInt.h" + #include "World.h" -#include "parser.h" +#include "simulation.h" +#include "Logs.h" +#include "Console.h" +#include "PyInt.h" // namespace Global { // parametry do użytku wewnętrznego -// double Global::tSinceStart=0; -TGround *Global::pGround = NULL; std::string Global::AppName{ "EU07" }; std::string Global::asCurrentSceneryPath = "scenery/"; std::string Global::asCurrentTexturePath = std::string(szTexturePath); @@ -74,12 +69,15 @@ std::vector Global::FreeCameraInitAngle; GLfloat Global::FogColor[] = {0.6f, 0.7f, 0.8f}; double Global::fFogStart = 1700; double Global::fFogEnd = 2000; -float Global::Overcast{ 0.1f }; // NOTE: all this weather stuff should be moved elsewhere -int Global::DynamicLightCount = 7; -bool Global::ScaleSpecularValues = false; +float Global::Overcast { 0.1f }; // NOTE: all this weather stuff should be moved elsewhere +std::string Global::Season; // season of the year, based on simulation date + float Global::BaseDrawRange { 2500.f }; -bool Global::RenderShadows { false }; +opengl_light Global::DayLight; +int Global::DynamicLightCount { 3 }; +bool Global::ScaleSpecularValues { true }; bool Global::BasicRenderer { false }; +bool Global::RenderShadows { true }; Global::shadowtune_t Global::shadowtune = { 2048, 250.f, 250.f, 500.f }; bool Global::bRollFix = true; // czy wykonać przeliczanie przechyłki bool Global::bJoinEvents = false; // czy grupować eventy o tych samych nazwach @@ -118,7 +116,7 @@ bool Global::bLiveTraction = true; float Global::AnisotropicFiltering = 8.0f; // requested level of anisotropic filtering. TODO: move it to renderer object std::string Global::LastGLError; GLint Global::iMaxTextureSize = 4096; // maksymalny rozmiar tekstury -bool Global::bSmoothTraction = false; // wygładzanie drutów starym sposobem +bool Global::bSmoothTraction { true }; // wygładzanie drutów starym sposobem float Global::SplineFidelity { 1.f }; // determines segment size during conversion of splines to geometry std::string Global::szDefaultExt = Global::szTexturesDDS; // domyślnie od DDS int Global::iMultisampling = 2; // tryb antyaliasingu: 0=brak,1=2px,2=4px,3=8px,4=16px @@ -172,7 +170,6 @@ Global::uart_conf_t Global::uart_conf; //randomizacja std::mt19937 Global::random_engine = std::mt19937(std::time(NULL)); -opengl_light Global::DayLight; Global::soundmode_t Global::soundpitchmode = Global::linear; Global::soundmode_t Global::soundgainmode = Global::linear; Global::soundstopmode_t Global::soundstopmode = Global::queue; @@ -181,19 +178,6 @@ Global::soundstopmode_t Global::soundstopmode = Global::queue; //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- -std::string Global::GetNextSymbol() -{ // pobranie tokenu z aktualnego parsera - - std::string token; - if (pParser != nullptr) - { - - pParser->getTokens(); - *pParser >> token; - }; - return token; -}; - void Global::LoadIniFile(std::string asFileName) { FreeCameraInit.resize( 10 ); @@ -449,6 +433,7 @@ void Global::ConfigParse(cParser &Parser) std::tm *localtime = std::localtime(&timenow); Global::fMoveLight = localtime->tm_yday + 1; // numer bieżącego dnia w roku } + Global::pWorld->compute_season( Global::fMoveLight ); } else if( token == "dynamiclights" ) { // number of dynamic lights in the scene @@ -949,20 +934,6 @@ void Global::TrainDelete(TDynamicObject *d) pWorld->TrainDelete(d); }; -TDynamicObject *Global::DynamicNearest() -{ // ustalenie pojazdu najbliższego kamerze - return pGround->DynamicNearest(pCamera->Pos); -}; - -TDynamicObject *Global::CouplerNearest() -{ // ustalenie pojazdu najbliższego kamerze - return pGround->CouplerNearest(pCamera->Pos); -}; - -bool Global::AddToQuery(TEvent *event, TDynamicObject *who) -{ - return pGround->AddToQuery(event, who); -}; //--------------------------------------------------------------------------- TTranscripts::TTranscripts() diff --git a/Globals.h b/Globals.h index 820d0d3f..62d4d1f7 100644 --- a/Globals.h +++ b/Globals.h @@ -150,16 +150,27 @@ private: void Update(); }; -class Global -{ - private: - public: +class Global { + +public: +// methods + static void LoadIniFile(std::string asFileName); + static void ConfigParse( cParser &parser ); + static void InitKeys(); + inline static Math3D::vector3 GetCameraPosition() { return pCameraPosition; }; + static void SetCameraPosition(Math3D::vector3 pNewCameraPosition); + static void SetCameraRotation(double Yaw); + static void TrainDelete(TDynamicObject *d); + static bool DoEvents(); + static std::string Bezogonkow(std::string str, bool _ = false); + static double Min0RSpeed(double vel1, double vel2); + +// members static int Keys[MaxKeys]; static bool RealisticControlMode; // controls ability to steer the vehicle from outside views static Math3D::vector3 pCameraPosition; // pozycja kamery w świecie static Math3D::vector3 DebugCameraPosition; // pozycja kamery w świecie - static double - pCameraRotation; // kierunek bezwzględny kamery w świecie: 0=północ, 90°=zachód (-azymut) + static double pCameraRotation; // kierunek bezwzględny kamery w świecie: 0=północ, 90°=zachód (-azymut) static double pCameraRotationDeg; // w stopniach, dla animacji billboard static std::vector FreeCameraInit; // pozycje kamery static std::vector FreeCameraInitAngle; @@ -179,27 +190,21 @@ class Global static bool bLiveTraction; static float fMouseXScale; static float fMouseYScale; - static double fFogStart; - static double fFogEnd; - static TGround *pGround; static std::string szDefaultExt; static std::string SceneryFile; static std::string AppName; static std::string asCurrentSceneryPath; static std::string asCurrentTexturePath; static std::string asCurrentDynamicPath; - // McZapkie-170602: zewnetrzna definicja pojazdu uzytkownika - static std::string asHumanCtrlVehicle; - static void LoadIniFile(std::string asFileName); - static void InitKeys(); - inline static Math3D::vector3 GetCameraPosition() { return pCameraPosition; }; - static void SetCameraPosition(Math3D::vector3 pNewCameraPosition); - static void SetCameraRotation(double Yaw); static int iWriteLogEnabled; // maska bitowa: 1-zapis do pliku, 2-okienko static bool MultipleLogs; - // McZapkie-221002: definicja swiatla dziennego - static GLfloat FogColor[]; + // McZapkie-170602: zewnetrzna definicja pojazdu uzytkownika + static std::string asHumanCtrlVehicle; + // world environment static float Overcast; + static double fFogStart; + static double fFogEnd; + static std::string Season; // season of the year, based on simulation date // TODO: put these things in the renderer static float BaseDrawRange; @@ -221,6 +226,9 @@ class Global static float FieldOfView; // vertical field of view for the camera. TODO: move it to the renderer static GLint iMaxTextureSize; // maksymalny rozmiar tekstury static int iMultisampling; // tryb antyaliasingu: 0=brak,1=2px,2=4px,3=8px,4=16px + static bool bSmoothTraction; // wygładzanie drutów + static float SplineFidelity; // determines segment size during conversion of splines to geometry + static GLfloat FogColor[]; static bool FullPhysics; // full calculations performed for each simulation step static int iSlowMotion; @@ -247,8 +255,6 @@ class Global static int iScreenMode[12]; // numer ekranu wyświetlacza tekstowego static double fMoveLight; // numer dnia w roku albo -1 static bool FakeLight; // toggle between fixed and dynamic daylight - static bool bSmoothTraction; // wygładzanie drutów - static float SplineFidelity; // determines segment size during conversion of splines to geometry static double fTimeSpeed; // przyspieszenie czasu, zmienna do testów static double fTimeAngleDeg; // godzina w postaci kąta static float fClockAngleDeg[6]; // kąty obrotu cylindrów dla zegara cyfrowego @@ -324,16 +330,6 @@ class Global }; static uart_conf_t uart_conf; - // metody - static void TrainDelete(TDynamicObject *d); - static void ConfigParse(cParser &parser); - static std::string GetNextSymbol(); - static TDynamicObject * DynamicNearest(); - static TDynamicObject * CouplerNearest(); - static bool AddToQuery(TEvent *event, TDynamicObject *who); - static std::string Bezogonkow(std::string str, bool _ = false); - static double Min0RSpeed(double vel1, double vel2); - static opengl_light DayLight; enum soundmode_t diff --git a/Ground.cpp b/Ground.cpp deleted file mode 100644 index 8ab0cb70..00000000 --- a/Ground.cpp +++ /dev/null @@ -1,4360 +0,0 @@ -/* -This Source Code Form is subject to the -terms of the Mozilla Public License, v. -2.0. If a copy of the MPL was not -distributed with this file, You can -obtain one at -http://mozilla.org/MPL/2.0/. -*/ - -/* - MaSzyna EU07 locomotive simulator - Copyright (C) 2001-2004 Marcin Wozniak and others - -*/ - -#include "stdafx.h" -#include "Ground.h" - -#include "Globals.h" -#include "Logs.h" -#include "usefull.h" -#include "Timer.h" -#include "renderer.h" -#include "Event.h" -#include "EvLaunch.h" -#include "TractionPower.h" -#include "Traction.h" -#include "Track.h" -#include "AnimModel.h" -#include "MemCell.h" -#include "mtable.h" -#include "DynObj.h" -#include "Data.h" -#include "parser.h" //Tolaris-010603 -#include "Driver.h" -#include "Console.h" -#include "Names.h" -#include "World.h" -#include "uilayer.h" -#include "sound.h" - -//--------------------------------------------------------------------------- - -#ifdef _WIN32 -extern "C" -{ - GLFWAPI HWND glfwGetWin32Window( GLFWwindow* window ); -} -#endif - -bool bCondition; // McZapkie: do testowania warunku na event multiple -std::string 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; - nNext = nNext2 = NULL; - iCount = 0; // wierzchołków w trójkącie - // iNumPts=0; //punktów w linii - m_material = 0; - iFlags = 0; // tryb przezroczystości nie zbadany - Pointer = NULL; // zerowanie wskaźnika kontekstowego - bVisible = false; // czy widoczny - fSquareRadius = 10000 * 10000; - fSquareMinRadius = 0; - asName = ""; - fLineThickness=1.0; //mm dla linii - nNext3 = NULL; // nie wyświetla innych -} - -TGroundNode::~TGroundNode() -{ - // if (iFlags&0x200) //czy obiekt został utworzony? - switch (iType) - { - case TP_MEMCELL: - SafeDelete(MemCell); - break; - case TP_EVLAUNCH: - SafeDelete(EvLaunch); - break; - case TP_TRACTION: - SafeDelete(hvTraction); - break; - case TP_TRACTIONPOWERSOURCE: - SafeDelete(psTractionPowerSource); - break; - case TP_TRACK: - SafeDelete(pTrack); - break; - case TP_DYNAMIC: - SafeDelete(DynamicObject); - break; - case TP_MODEL: - if (iFlags & 0x200) // czy model został utworzony? - delete Model; - Model = NULL; - break; - case TP_SOUND: - SafeDelete(tsStaticSound); - break; - case TP_TERRAIN: - { // pierwsze nNode zawiera model E3D, reszta to trójkąty - delete[] nNode; // usunięcie tablicy i pierwszego elementu - } - case TP_SUBMODEL: // dla formalności, nie wymaga usuwania - break; - case GL_LINES: - case GL_LINE_STRIP: - case GL_LINE_LOOP: - case GL_TRIANGLE_STRIP: - case GL_TRIANGLE_FAN: - case GL_TRIANGLES: - SafeDelete( Piece ); - break; - } -} -/* -void TGroundNode::Init(int n) -{ // utworzenie tablicy wierzchołków - bVisible = false; - iNumVerts = n; - Vertices = new TGroundVertex[iNumVerts]; -} -*/ -TGroundNode::TGroundNode( TGroundNodeType t ) : - TGroundNode() { - // utworzenie obiektu - iType = t; - switch (iType) { - // zależnie od typu - case GL_TRIANGLES: { - Piece = new piece_node; - break; - } - case TP_TRACK: { - pTrack = new TTrack( this ); - break; - } - default: { - break; - } - } -} - -// obliczenie wektorów normalnych -void TGroundNode::InitNormals() { - - glm::dvec3 v1, v2; - glm::vec3 n1; - glm::vec2 t1; - - for( auto i = 0; i < iNumVerts; i += 3 ) { - - v1 = Piece->vertices[ i + 0 ].position - Piece->vertices[ i + 1 ].position; - v2 = Piece->vertices[ i + 1 ].position - Piece->vertices[ i + 2 ].position; - n1 = glm::normalize( glm::cross( v1, v2 ) ); - if( Piece->vertices[ i + 0 ].normal == glm::vec3() ) - Piece->vertices[ i + 0 ].normal = ( n1 ); - if( Piece->vertices[ i + 1 ].normal == glm::vec3() ) - Piece->vertices[ i + 1 ].normal = ( n1 ); - if( Piece->vertices[ i + 2 ].normal == glm::vec3() ) - Piece->vertices[ i + 2 ].normal = ( n1 ); - t1 = glm::vec2( - std::floor( Piece->vertices[ i + 0 ].texture.s ), - std::floor( Piece->vertices[ i + 0 ].texture.t ) ); - Piece->vertices[ i + 1 ].texture -= t1; - Piece->vertices[ i + 2 ].texture -= t1; - Piece->vertices[ i + 0 ].texture -= t1; - } -} - -void TGroundNode::RenderHidden() -{ // renderowanie obiektów niewidocznych - double mgn = SquareMagnitude(pCenter - Global::pCameraPosition); - switch (iType) - { - case TP_EVLAUNCH: - if (EvLaunch->Render()) - if ((EvLaunch->dRadius < 0) || (mgn < EvLaunch->dRadius)) - { - WriteLog("Eventlauncher " + asName); - if (Global::shiftState && (EvLaunch->Event2)) - Global::AddToQuery(EvLaunch->Event2, NULL); - else if (EvLaunch->Event1) - Global::AddToQuery(EvLaunch->Event1, NULL); - } - return; - } -}; - -//------------------------------------------------------------------------------ -//------------------ Podstawowy pojemnik terenu - sektor ----------------------- -//------------------------------------------------------------------------------ -TSubRect::~TSubRect() -{ - // TODO: usunąć obiekty z listy (nRootMesh), bo są one tworzone dla sektora - delete[] tTracks; -} - -void TSubRect::NodeAdd(TGroundNode *Node) -{ // przyczepienie obiektu do sektora, wstępna kwalifikacja na listy renderowania - if (!this) - return; // zabezpiecznie przed obiektami przekraczającymi obszar roboczy - // Ra: sortowanie obiektów na listy renderowania: - // nRenderHidden - lista obiektów niewidocznych, "renderowanych" również z tyłu - // nRenderRect - lista grup renderowanych z sektora - // nRenderRectAlpha - lista grup renderowanych z sektora z przezroczystością - // nRender - lista grup renderowanych z własnych VBO albo DL - // nRenderAlpha - lista grup renderowanych z własnych VBO albo DL z przezroczystością - // nRenderWires - lista grup renderowanych z własnych VBO albo DL - druty i linie - - Node->m_rootposition = m_area.center; - - // since ground rectangle can be empty, we're doing lazy initialization of the geometry bank, when something may actually use it - // NOTE: this method is called for both subcell and cell, but subcells get first created and passed the handle from their parent - // thus, this effectively only gets executed for the 'parent' ground cells. Not the most elegant, but for now it'll do - if( m_geometrybank == null_handle ) { - m_geometrybank = GfxRenderer.Create_Bank(); - } - - switch (Node->iType) - { - case TP_SOUND: // te obiekty są sprawdzanie niezależnie od kierunku patrzenia - case TP_EVLAUNCH: - Node->nNext3 = nRenderHidden; - nRenderHidden = Node; // do listy koniecznych - break; - case TP_TRACK: // TODO: tory z cieniem (tunel, canyon) też dać bez łączenia? - ++iTracks; // jeden tor więcej - Node->pTrack->RaOwnerSet(this); // do którego sektora ma zgłaszać animację - // NOTE: track merge/sort temporarily disabled to simplify unification of render code - // TODO: refactor sorting as universal part of drawing process in the renderer - Node->nNext3 = nRenderRect; - nRenderRect = Node; - break; - case GL_TRIANGLE_STRIP: - case GL_TRIANGLE_FAN: - case GL_TRIANGLES: - if (Node->iFlags & 0x20) { - // do przezroczystych z sektora - Node->nNext3 = nRenderRectAlpha; - nRenderRectAlpha = Node; - } - else { - // do nieprzezroczystych z sektora - Node->nNext3 = nRenderRect; - nRenderRect = Node; - } - break; - case TP_TRACTION: - case GL_LINES: - case GL_LINE_STRIP: - case GL_LINE_LOOP: // te renderowane na końcu, żeby nie łapały koloru nieba - Node->nNext3 = nRenderWires; - nRenderWires = Node; // lista drutów - break; - case TP_MODEL: // modele zawsze wyświetlane z własnego VBO - // jeśli model jest prosty, można próbować zrobić wspólną siatkę (słupy) - if ((Node->iFlags & 0x20200020) == 0) // czy brak przezroczystości? - { - Node->nNext3 = nRender; - nRender = Node; - } // do nieprzezroczystych - else if ((Node->iFlags & 0x10100010) == 0) // czy brak nieprzezroczystości? - { - Node->nNext3 = nRenderAlpha; - nRenderAlpha = Node; - } // do przezroczystych - else // jak i take i takie, to będzie dwa razy renderowane... - { - Node->nNext3 = nRenderMixed; - nRenderMixed = Node; - } // do mieszanych - break; -#ifdef EU07_SCENERY_EDITOR - case TP_MEMCELL: { - m_memcells.emplace_back( Node ); - break; - } -#endif - case TP_TRACTIONPOWERSOURCE: // a te w ogóle pomijamy -/* - // case TP_ISOLATED: //lista torów w obwodzie izolowanym - na razie ignorowana - break; -*/ - case TP_DYNAMIC: - return; // tych nie dopisujemy wcale - } - // dopisanie do ogólnej listy - Node->nNext2 = nRootNode; - nRootNode = Node; - // licznik obiektów - ++iNodeCount; -} - -// przygotowanie sektora do renderowania -void TSubRect::Sort() { - - if( tTracks != nullptr ) { - SafeDelete( tTracks ); - } - if( iTracks > 0 ) { - tTracks = new TTrack *[ iTracks ]; // tworzenie tabeli torów do renderowania pojazdów - int i = 0; - for( TGroundNode *node = nRootNode; node != nullptr; node = node->nNext2 ) { - // kolejne obiekty z sektora - if( node->iType == TP_TRACK ) { - tTracks[ i++ ] = node->pTrack; - } - } - } - // NOTE: actual sort procedure temporarily removed as part of render code unification -} - -TTrack * TSubRect::FindTrack(vector3 *Point, int &iConnection, TTrack *Exclude) -{ // szukanie toru, którego koniec jest najbliższy (*Point) - for( int i = 0; i < iTracks; ++i ) { - if( tTracks[ i ] != Exclude ) // można użyć tabelę torów, bo jest mniejsza - { - iConnection = tTracks[ i ]->TestPoint( Point ); - if( iConnection >= 0 ) - return tTracks[ i ]; // szukanie TGroundNode nie jest potrzebne - } - } - return NULL; -}; - -bool TSubRect::RaTrackAnimAdd(TTrack *t) -{ // aktywacja animacji torów w VBO (zwrotnica, obrotnica) - if( false == m_geometrycreated ) { - // nie ma animacji, gdy nie widać - return true; - } - if (tTrackAnim) - tTrackAnim->RaAnimListAdd(t); - else - tTrackAnim = t; - return false; // będzie animowane... -} - -// wykonanie animacji -void TSubRect::RaAnimate( unsigned int const Framestamp ) { - - if( ( tTrackAnim == nullptr ) - || ( Framestamp == m_framestamp ) ) { - // nie ma nic do animowania - return; - } - tTrackAnim = tTrackAnim->RaAnimate(); // przeliczenie animacji kolejnego - - m_framestamp = Framestamp; -}; - -TTraction * TSubRect::FindTraction(glm::dvec3 const &Point, int &iConnection, TTraction *Exclude) -{ // szukanie przęsła w sektorze, którego koniec jest najbliższy (*Point) - TGroundNode *Current; - for (Current = nRenderWires; Current; Current = Current->nNext3) - if ((Current->iType == TP_TRACTION) && (Current->hvTraction != Exclude)) - { - iConnection = Current->hvTraction->TestPoint(Point); - if (iConnection >= 0) - return Current->hvTraction; - } - return NULL; -}; - -// utworzenie siatek VBO dla wszystkich node w sektorze -void TSubRect::LoadNodes() { - - if( true == m_geometrycreated ) { - // obiekty były już sprawdzone - return; - } - else { - // mark it done for future checks - m_geometrycreated = true; - } - - auto *node { nRootNode }; - while( node != nullptr ) { - switch (node->iType) { - case GL_TRIANGLES: - case GL_LINES: { - vertex_array vertices; - for( auto const &vertex : node->Piece->vertices ) { - vertices.emplace_back( - vertex.position - node->m_rootposition, - vertex.normal, - vertex.texture ); - } - node->Piece->geometry = GfxRenderer.Insert( vertices, m_geometrybank, node->iType ); - node->Piece->vertices.clear(); - node->Piece->vertices.shrink_to_fit(); - // TODO: get rid of the vertex counters, they're obsolete at this point - if( node->iType == GL_LINES ) { node->iNumVerts = 0; } - else { node->iNumPts = 0; } - break; - } - case TP_TRACK: - if( node->pTrack->bVisible ) { // bo tory zabezpieczające są niewidoczne - node->pTrack->create_geometry( m_geometrybank ); - } - break; - case TP_TRACTION: - node->hvTraction->create_geometry( m_geometrybank, node->m_rootposition ); - break; - default: { break; } - } - node = node->nNext2; // następny z sektora - } -} - -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 ------------------------------------ -//--------------------------------------------------------------------------- -TGroundRect::~TGroundRect() -{ - SafeDeleteArray(pSubRects); -}; - -void -TGroundRect::Init() { - // since ground rectangle can be empty, we're doing lazy initialization of the geometry bank, when something may actually use it - if( m_geometrybank == null_handle ) { - m_geometrybank = GfxRenderer.Create_Bank(); - } - - pSubRects = new TSubRect[ iNumSubRects * iNumSubRects ]; - float const subrectsize = 1000.0f / iNumSubRects; - for( int column = 0; column < iNumSubRects; ++column ) { - for( int row = 0; row < iNumSubRects; ++row ) { - auto subcell = FastGetSubRect( column, row ); - auto &area = subcell->m_area; - area.center = - m_area.center - - glm::vec3( 500.0f, 0.0f, 500.0f ) // 'upper left' corner of rectangle - + glm::vec3( subrectsize * 0.5f, 0.0f, subrectsize * 0.5f ) // center of sub-rectangle - + glm::vec3( subrectsize * column, 0.0f, subrectsize * row ); - area.radius = subrectsize * (float)M_SQRT2; - // all subcells share the same geometry bank with their parent, to reduce buffer switching during render - subcell->m_geometrybank = m_geometrybank; - } - } -}; - -// dodanie obiektu do sektora na etapie rozdzielania na sektory -void -TGroundRect::NodeAdd( TGroundNode *Node ) { - - // override visibility ranges, to ensure the content is drawn from far enough - Node->fSquareRadius = 50000.0 * 50000.0; - Node->fSquareMinRadius = 0.0; - - // if the cell already has a node with matching material settings, we can just add the new geometry to it - if( ( Node->iType == GL_TRIANGLES ) ) { - // cell node only receives opaque geometry, so we can skip transparency test - auto matchingnode { nRenderRect }; - while( ( matchingnode != nullptr ) - && ( false == mergeable( *Node, *matchingnode ) ) ) { - // search will get us either a matching node, or a nullptr - matchingnode = matchingnode->nNext3; - } - if( matchingnode != nullptr ) { - // a valid match, so dump the content into it - // updating centre points isn't strictly necessary since render is based off the cell's middle, but, eh - matchingnode->pCenter = - interpolate( - matchingnode->pCenter, Node->pCenter, - static_cast( Node->iNumVerts ) / ( Node->iNumVerts + matchingnode->iNumVerts ) ); - matchingnode->iNumVerts += Node->iNumVerts; - matchingnode->Piece->vertices.insert( - std::end( matchingnode->Piece->vertices ), - std::begin( Node->Piece->vertices ), std::end( Node->Piece->vertices ) ); - // clear content of the node we're copying. a minor memory saving at best, but still a saving - std::vector().swap( Node->Piece->vertices ); - Node->iNumVerts = 0; - // since we've put the data in existing node we can skip adding the new one... - return; - // ...for others, they'll go through the regular procedure, along with other non-mergeable types - } - } - - return TSubRect::NodeAdd( Node ); -} - -// compares two provided nodes, returns true if their content can be merged -bool -TGroundRect::mergeable( TGroundNode const &Left, TGroundNode const &Right ) { - // since view ranges and transparency type for all nodes put through this method are guaranteed to be equal, - // we can skip their tests and only do the material check. - // TODO, TBD: material as dedicated type, and refactor this method into a simple equality test - return ( ( Left.m_material == Right.m_material ) - && ( Left.Ambient == Right.Ambient ) - && ( Left.Diffuse == Right.Diffuse ) - && ( Left.Specular == Right.Specular ) ); -} - -//--------------------------------------------------------------------------- - -BYTE TempConnectionType[ 200 ]; // Ra: sprzêgi w sk³adzie; ujemne, gdy odwrotnie - -TGround::TGround() -{ - Global::pGround = this; - - for( int i = 0; i < TP_LAST; ++i ) { - nRootOfType[ i ] = nullptr; // zerowanie tablic wyszukiwania - } - memset(TempConnectionType, 0, sizeof(TempConnectionType)); - // set bounding area information for ground rectangles - float const rectsize = 1000.0f; - glm::vec3 const worldcenter; - for( int column = 0; column < iNumRects; ++column ) { - for( int row = 0; row < iNumRects; ++row ) { - auto &area = Rects[ column ][ row ].m_area; - // NOTE: in current implementation triangles can stick out to ~200m from the area, so we add extra padding - area.radius = ( rectsize * 0.5f * (float)M_SQRT2 ) + 200.0f; - area.center = - worldcenter - - glm::vec3( (iNumRects / 2) * rectsize, 0.0f, (iNumRects / 2) * rectsize ) // 'upper left' corner of the world - + glm::vec3( rectsize * 0.5f, 0.0f, rectsize * 0.5f ) // center of the rectangle - + glm::vec3( rectsize * column, 0.0f, rectsize * row ); - } - } -} - -TGround::~TGround() -{ - Free(); -} - -void TGround::Free() -{ - 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; - } - // RootNode=NULL; - nRootDynamic = NULL; -} - -TGroundNode * TGround::DynamicFindAny(std::string const &Name) -{ // wyszukanie pojazdu o podanej nazwie, szukanie po wszystkich (użyć drzewa!) - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - if ((Current->asName == Name)) - return Current; - return NULL; -}; - -TGroundNode * TGround::DynamicFind(std::string const &Name) -{ // wyszukanie pojazdu z obsadą o podanej nazwie (użyć drzewa!) - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - if (Current->DynamicObject->Mechanik) - if ((Current->asName == Name)) - return Current; - return NULL; -}; - -void TGround::DynamicList(bool all) -{ // odesłanie nazw pojazdów dostępnych na scenerii (nazwy, szczególnie wagonów, mogą się - // powtarzać!) - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - if (all || Current->DynamicObject->Mechanik) - WyslijString(Current->asName, 6); // same nazwy pojazdów - WyslijString("none", 6); // informacja o końcu listy -}; - -TGroundNode * TGround::FindGroundNode(std::string 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 -/* - switch( iNodeType ) { - - case TP_TRACK: { - auto const lookup = m_trackmap.find( asNameToFind ); - return - lookup != m_trackmap.end() ? - lookup->second : - nullptr; - } - case TP_MODEL: { - auto const lookup = m_modelmap.find( asNameToFind ); - return - lookup != m_modelmap.end() ? - lookup->second : - nullptr; - } - case TP_MEMCELL: { - auto const lookup = m_memcellmap.find( asNameToFind ); - return - lookup != m_memcellmap.end() ? - lookup->second : - nullptr; - } - } - return nullptr; -*/ - return m_trackmap.Find( iNodeType, asNameToFind ); - } - // standardowe wyszukiwanie liniowe - TGroundNode *Current; - for (Current = nRootOfType[iNodeType]; Current; Current = Current->nNext) - if (Current->asName == asNameToFind) - return Current; - return NULL; -} - -TGroundRect * -TGround::GetRect( double x, double z ) { - - auto const column = GetColFromX( x ) / iNumSubRects; - auto const row = GetRowFromZ( z ) / iNumSubRects; - - if( ( column >= 0 ) && ( column < iNumRects ) - && ( row >= 0 ) && ( row < iNumRects ) ) { - return &Rects[ column ][ row ]; - } - else { - return nullptr; - } -}; - -// convert tp_terrain model to a series of triangle nodes -void -TGround::convert_terrain( TGroundNode const *Terrain ) { - - TSubModel *submodel { nullptr }; - for( auto cellindex = 1; cellindex < Terrain->iCount; ++cellindex ) { - // go through all submodels for individual terrain cells... - submodel = Terrain->nNode[ cellindex ].smTerrain; - convert_terrain( submodel ); - // if there's more than one group of triangles in the cell they're held as children of the primary submodel - submodel = submodel->ChildGet(); - while( submodel != nullptr ) { - convert_terrain( submodel ); - submodel = submodel->NextGet(); - } - } -} - -void -TGround::convert_terrain( TSubModel const *Submodel ) { - - auto groundnode = new TGroundNode( GL_TRIANGLES ); - Submodel->convert( *groundnode ); - - if( groundnode->iNumVerts > 0 ) { - // jeśli nie jest pojazdem ostatni dodany dołączamy na końcu nowego - groundnode->nNext = nRootOfType[ groundnode->iType ]; - // ustawienie nowego na początku listy - nRootOfType[ groundnode->iType ] = groundnode; - } - else { - delete groundnode; - } -} - -double fTrainSetVel = 0; -double fTrainSetDir = 0; -double fTrainSetDist = 0; // odległość składu od punktu 1 w stronę punktu 2 -std::string asTrainSetTrack = ""; -int iTrainSetConnection = 0; -bool bTrainSet = false; -std::string asTrainName = ""; -int iTrainSetWehicleNumber = 0; -TGroundNode *nTrainSetNode = NULL; // poprzedni pojazd do łączenia -TGroundNode *nTrainSetDriver = NULL; // pojazd, któremu zostanie wysłany rozkład - -void TGround::RaTriangleDivider(TGroundNode *node) -{ // tworzy dodatkowe trójkąty i zmiejsza podany - // to jest wywoływane przy wczytywaniu trójkątów - // dodatkowe trójkąty są dodawane do głównej listy trójkątów - // podział trójkątów na sektory i kwadraty jest dokonywany później w FirstInit - if (node->iType != GL_TRIANGLES) - return; // tylko pojedyncze trójkąty - if (node->iNumVerts != 3) - return; // tylko gdy jeden trójkąt - double x0 = 1000.0 * std::floor(0.001 * node->pCenter.x) - 200.0; - double x1 = x0 + 1400.0; - double z0 = 1000.0 * std::floor(0.001 * node->pCenter.z) - 200.0; - double z1 = z0 + 1400.0; - if ((node->Piece->vertices[0].position.x >= x0) && (node->Piece->vertices[0].position.x <= x1) && - (node->Piece->vertices[0].position.z >= z0) && (node->Piece->vertices[0].position.z <= z1) && - (node->Piece->vertices[1].position.x >= x0) && (node->Piece->vertices[1].position.x <= x1) && - (node->Piece->vertices[1].position.z >= z0) && (node->Piece->vertices[1].position.z <= z1) && - (node->Piece->vertices[2].position.x >= x0) && (node->Piece->vertices[2].position.x <= x1) && - (node->Piece->vertices[2].position.z >= z0) && (node->Piece->vertices[2].position.z <= z1)) - return; // trójkąt wystający mniej niż 200m z kw. kilometrowego jest do przyjęcia - // Ra: przerobić na dzielenie na 2 trójkąty, podział w przecięciu z siatką kilometrową - // Ra: i z rekurencją będzie dzielić trzy trójkąty, jeśli będzie taka potrzeba - int divide = -1; // bok do podzielenia: 0=AB, 1=BC, 2=CA; +4=podział po OZ; +8 na x1/z1 - double min = 0, mul; // jeśli przechodzi przez oś, iloczyn będzie ujemny - x0 += 200.0; - x1 -= 200.0; // przestawienie na siatkę - z0 += 200.0; - z1 -= 200.0; - mul = (node->Piece->vertices[0].position.x - x0) * (node->Piece->vertices[1].position.x - x0); // AB na wschodzie - if (mul < min) - min = mul, divide = 0; - mul = (node->Piece->vertices[1].position.x - x0) * (node->Piece->vertices[2].position.x - x0); // BC na wschodzie - if (mul < min) - min = mul, divide = 1; - mul = (node->Piece->vertices[2].position.x - x0) * (node->Piece->vertices[0].position.x - x0); // CA na wschodzie - if (mul < min) - min = mul, divide = 2; - mul = (node->Piece->vertices[0].position.x - x1) * (node->Piece->vertices[1].position.x - x1); // AB na zachodzie - if (mul < min) - min = mul, divide = 8; - mul = (node->Piece->vertices[1].position.x - x1) * (node->Piece->vertices[2].position.x - x1); // BC na zachodzie - if (mul < min) - min = mul, divide = 9; - mul = (node->Piece->vertices[2].position.x - x1) * (node->Piece->vertices[0].position.x - x1); // CA na zachodzie - if (mul < min) - min = mul, divide = 10; - mul = (node->Piece->vertices[0].position.z - z0) * (node->Piece->vertices[1].position.z - z0); // AB na południu - if (mul < min) - min = mul, divide = 4; - mul = (node->Piece->vertices[1].position.z - z0) * (node->Piece->vertices[2].position.z - z0); // BC na południu - if (mul < min) - min = mul, divide = 5; - mul = (node->Piece->vertices[2].position.z - z0) * (node->Piece->vertices[0].position.z - z0); // CA na południu - if (mul < min) - min = mul, divide = 6; - mul = (node->Piece->vertices[0].position.z - z1) * (node->Piece->vertices[1].position.z - z1); // AB na północy - if (mul < min) - min = mul, divide = 12; - mul = (node->Piece->vertices[1].position.z - z1) * (node->Piece->vertices[2].position.z - z1); // BC na północy - if (mul < min) - min = mul, divide = 13; - mul = (node->Piece->vertices[2].position.z - z1) * (node->Piece->vertices[0].position.z - z1); // CA na północy - if (mul < min) - divide = 14; - // tworzymy jeden dodatkowy trójkąt, dzieląc jeden bok na przecięciu siatki kilometrowej - TGroundNode *ntri; // wskaźnik na nowy trójkąt - ntri = new TGroundNode(GL_TRIANGLES); // a ten jest nowy - // kopiowanie parametrów, przydałby się konstruktor kopiujący - ntri->m_material = node->m_material; - ntri->iFlags = node->iFlags; - ntri->Ambient = node->Ambient; - ntri->Diffuse = node->Diffuse; - ntri->Specular = node->Specular; - ntri->asName = node->asName; - ntri->fSquareRadius = node->fSquareRadius; - ntri->fSquareMinRadius = node->fSquareMinRadius; - ntri->bVisible = node->bVisible; // a są jakieś niewidoczne? - ntri->nNext = nRootOfType[GL_TRIANGLES]; - nRootOfType[GL_TRIANGLES] = ntri; // dopisanie z przodu do listy - ntri->iNumVerts = 3; - ntri->Piece->vertices.resize( 3 ); - switch (divide & 3) - { // podzielenie jednego z boków, powstaje wierzchołek D - case 0: // podział AB (0-1) -> ADC i DBC - ntri->Piece->vertices[2] = node->Piece->vertices[2]; // wierzchołek C jest wspólny - ntri->Piece->vertices[1] = node->Piece->vertices[1]; // wierzchołek B przechodzi do nowego - // node->Vertices[1].HalfSet(node->Vertices[0],node->Vertices[1]); //na razie D tak - if (divide & 4) - node->Piece->vertices[1].SetByZ(node->Piece->vertices[0], node->Piece->vertices[1], (divide & 8) ? z1 : z0); - else - node->Piece->vertices[1].SetByX(node->Piece->vertices[0], node->Piece->vertices[1], (divide & 8) ? x1 : x0); - ntri->Piece->vertices[0] = node->Piece->vertices[1]; // wierzchołek D jest wspólny - break; - case 1: // podział BC (1-2) -> ABD i ADC - ntri->Piece->vertices[0] = node->Piece->vertices[0]; // wierzchołek A jest wspólny - ntri->Piece->vertices[2] = node->Piece->vertices[2]; // wierzchołek C przechodzi do nowego - // node->Vertices[2].HalfSet(node->Vertices[1],node->Vertices[2]); //na razie D tak - if (divide & 4) - node->Piece->vertices[2].SetByZ(node->Piece->vertices[1], node->Piece->vertices[2], (divide & 8) ? z1 : z0); - else - node->Piece->vertices[2].SetByX(node->Piece->vertices[1], node->Piece->vertices[2], (divide & 8) ? x1 : x0); - ntri->Piece->vertices[1] = node->Piece->vertices[2]; // wierzchołek D jest wspólny - break; - case 2: // podział CA (2-0) -> ABD i DBC - ntri->Piece->vertices[1] = node->Piece->vertices[1]; // wierzchołek B jest wspólny - ntri->Piece->vertices[2] = node->Piece->vertices[2]; // wierzchołek C przechodzi do nowego - // node->Vertices[2].HalfSet(node->Vertices[2],node->Vertices[0]); //na razie D tak - if (divide & 4) - node->Piece->vertices[2].SetByZ(node->Piece->vertices[2], node->Piece->vertices[0], (divide & 8) ? z1 : z0); - else - node->Piece->vertices[2].SetByX(node->Piece->vertices[2], node->Piece->vertices[0], (divide & 8) ? x1 : x0); - ntri->Piece->vertices[0] = node->Piece->vertices[2]; // wierzchołek D jest wspólny - break; - } - // przeliczenie środków ciężkości obu - node->pCenter = ( node->Piece->vertices[ 0 ].position + node->Piece->vertices[ 1 ].position + node->Piece->vertices[ 2 ].position ) / 3.0; - ntri->pCenter = ( ntri->Piece->vertices[ 0 ].position + ntri->Piece->vertices[ 1 ].position + ntri->Piece->vertices[ 2 ].position ) / 3.0; - RaTriangleDivider(node); // rekurencja, bo nawet na TD raz nie wystarczy - RaTriangleDivider(ntri); -}; - -TGroundNode * TGround::AddGroundNode(cParser *parser) -{ // wczytanie wpisu typu "node" - std::string str, str1, str2, str3, str4, Skin, DriverType, asNodeName; - double tf, r, rmin, tf1, tf3; - int int1; - size_t int2; - bool bError = false; - vector3 pt, front, up, left, pos, tv; - matrix4x4 mat2, mat1, mat; - TGroundNode *tmp1; - TTrack *Track; - std::string token; - parser->getTokens(4); - *parser - >> r - >> rmin - >> asNodeName - >> str; - TGroundNode *tmp = new TGroundNode(); - tmp->asName = (asNodeName == "none" ? "" : asNodeName); - if (r >= 0) - tmp->fSquareRadius = r * r; - tmp->fSquareMinRadius = rmin * rmin; - if (str == "triangles") - tmp->iType = GL_TRIANGLES; - else if (str == "triangle_strip") - tmp->iType = GL_TRIANGLE_STRIP; - else if (str == "triangle_fan") - tmp->iType = GL_TRIANGLE_FAN; - else if (str == "lines") - tmp->iType = GL_LINES; - else if (str == "line_strip") - tmp->iType = GL_LINE_STRIP; - else if (str == "line_loop") - tmp->iType = GL_LINE_LOOP; - else if (str == "model") - tmp->iType = TP_MODEL; - // else if (str=="terrain") tmp->iType=TP_TERRAIN; //tymczasowo do odwołania - else if (str == "dynamic") - tmp->iType = TP_DYNAMIC; - else if (str == "sound") - tmp->iType = TP_SOUND; - else if (str == "track") - tmp->iType = TP_TRACK; - else if (str == "memcell") - tmp->iType = TP_MEMCELL; - else if (str == "eventlauncher") - tmp->iType = TP_EVLAUNCH; - else if (str == "traction") - tmp->iType = TP_TRACTION; - else if (str == "tractionpowersource") - tmp->iType = TP_TRACTIONPOWERSOURCE; - // else if (str=="isolated") tmp->iType=TP_ISOLATED; - else - bError = true; - // WriteLog("-> node "+str+" "+tmp->asName); - if (bError) - { - Error("Scene parse error near " + str); - for (int i = 0; i < 60; ++i) - { // Ra: skopiowanie dalszej części do logu - taka prowizorka, lepsza niż nic - parser->getTokens(); // pobranie linijki tekstu nie działa - *parser >> token; - WriteLog(token); - } - // if (tmp==RootNode) RootNode=NULL; - delete tmp; - return NULL; - } - glm::dvec3 center; - std::vector importedvertices; - - switch (tmp->iType) - { - case TP_TRACTION: - tmp->hvTraction = new TTraction(); - parser->getTokens(); - *parser >> token; - tmp->hvTraction->asPowerSupplyName = token; // nazwa zasilacza - parser->getTokens(3); - *parser - >> tmp->hvTraction->NominalVoltage - >> tmp->hvTraction->MaxCurrent - >> tmp->hvTraction->fResistivity; - if (tmp->hvTraction->fResistivity == 0.01f) // tyle jest w sceneriach [om/km] - tmp->hvTraction->fResistivity = 0.075f; // taka sensowniejsza wartość za - // http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf - tmp->hvTraction->fResistivity *= 0.001f; // teraz [om/m] - parser->getTokens(); - *parser >> token; - // Ra 2014-02: a tutaj damy symbol sieci i jej budowę, np.: - // SKB70-C, CuCd70-2C, KB95-2C, C95-C, C95-2C, YC95-2C, YpC95-2C, YC120-2C - // YpC120-2C, YzC120-2C, YwsC120-2C, YC150-C150, YC150-2C150, C150-C150 - // C120-2C, 2C120-2C, 2C120-2C-1, 2C120-2C-2, 2C120-2C-3, 2C120-2C-4 - if (token.compare("none") == 0) - tmp->hvTraction->Material = 0; - else if (token.compare("al") == 0) - tmp->hvTraction->Material = 2; // 1=aluminiowa, rysuje się na czarno - else - tmp->hvTraction->Material = 1; // 1=miedziana, rysuje się na zielono albo czerwono - parser->getTokens(); - *parser >> tmp->hvTraction->WireThickness; - parser->getTokens(); - *parser >> tmp->hvTraction->DamageFlag; - parser->getTokens(3); - *parser - >> tmp->hvTraction->pPoint1.x - >> tmp->hvTraction->pPoint1.y - >> tmp->hvTraction->pPoint1.z; - tmp->hvTraction->pPoint1 += glm::dvec3{ pOrigin }; - parser->getTokens(3); - *parser - >> tmp->hvTraction->pPoint2.x - >> tmp->hvTraction->pPoint2.y - >> tmp->hvTraction->pPoint2.z; - tmp->hvTraction->pPoint2 += glm::dvec3{ pOrigin }; - parser->getTokens(3); - *parser - >> tmp->hvTraction->pPoint3.x - >> tmp->hvTraction->pPoint3.y - >> tmp->hvTraction->pPoint3.z; - tmp->hvTraction->pPoint3 += glm::dvec3{ pOrigin }; - parser->getTokens(3); - *parser - >> tmp->hvTraction->pPoint4.x - >> tmp->hvTraction->pPoint4.y - >> tmp->hvTraction->pPoint4.z; - tmp->hvTraction->pPoint4 += glm::dvec3{ pOrigin }; - parser->getTokens(); - *parser >> tf1; - tmp->hvTraction->fHeightDifference = - (tmp->hvTraction->pPoint3.y - tmp->hvTraction->pPoint1.y + tmp->hvTraction->pPoint4.y - tmp->hvTraction->pPoint2.y) * 0.5f - tf1; - parser->getTokens(); - *parser >> tf1; - if (tf1 > 0) - tmp->hvTraction->iNumSections = - glm::length((tmp->hvTraction->pPoint1 - tmp->hvTraction->pPoint2)) / tf1; - else - tmp->hvTraction->iNumSections = 0; - parser->getTokens(); - *parser >> tmp->hvTraction->Wires; - parser->getTokens(); - *parser >> tmp->hvTraction->WireOffset; - parser->getTokens(); - *parser >> token; - tmp->bVisible = (token.compare("vis") == 0); - parser->getTokens(); - *parser >> token; - if (token.compare("parallel") == 0) - { // jawne wskazanie innego przęsła, na które może przestawić się pantograf - parser->getTokens(); - *parser >> token; // wypadało by to zapamiętać... - tmp->hvTraction->asParallel = token; - parser->getTokens(); - *parser >> token; // a tu już powinien być koniec - } - if (token.compare("endtraction") != 0) - Error("ENDTRACTION delimiter missing! " + str2 + " found instead."); - tmp->hvTraction->Init(); // przeliczenie parametrów - // if (Global::bLoadTraction) - // tmp->hvTraction->Optimize(); //generowanie DL dla wszystkiego przy wczytywaniu? - tmp->pCenter = interpolate( tmp->hvTraction->pPoint2, tmp->hvTraction->pPoint1, 0.5 ); - break; - case TP_TRACTIONPOWERSOURCE: - parser->getTokens(3); - *parser - >> tmp->pCenter.x - >> tmp->pCenter.y - >> tmp->pCenter.z; - tmp->pCenter += pOrigin; - tmp->psTractionPowerSource = new TTractionPowerSource(tmp); - tmp->psTractionPowerSource->Load(parser); - break; - case TP_MEMCELL: - parser->getTokens(3); - *parser - >> tmp->pCenter.x - >> tmp->pCenter.y - >> tmp->pCenter.z; - tmp->pCenter.RotateY(aRotate.y / 180.0 * M_PI); // Ra 2014-11: uwzględnienie rotacji - tmp->pCenter += pOrigin; - tmp->MemCell = new TMemCell(&tmp->pCenter); - tmp->MemCell->Load(parser); - if (!tmp->asName.empty()) // jest pusta gdy "none" - { // dodanie do wyszukiwarki - if( false == m_trackmap.Add( TP_MEMCELL, tmp->asName, tmp ) ) { - // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność wsteczna) - ErrorLog( "Duplicated memcell: " + tmp->asName ); // to zgłaszać duplikat - } - } - break; - case TP_EVLAUNCH: - parser->getTokens(3); - *parser - >> tmp->pCenter.x - >> tmp->pCenter.y - >> tmp->pCenter.z; - tmp->pCenter.RotateY(aRotate.y / 180.0 * M_PI); // Ra 2014-11: uwzględnienie rotacji - tmp->pCenter += pOrigin; - tmp->EvLaunch = new TEventLauncher(); - tmp->EvLaunch->Load(parser); - break; - case TP_TRACK: - tmp->pTrack = new TTrack(tmp); - if (Global::iWriteLogEnabled & 4) - if (!tmp->asName.empty()) - WriteLog(tmp->asName); - tmp->pTrack->Load(parser, pOrigin, tmp->asName); // w nazwie może być nazwa odcinka izolowanego - if (!tmp->asName.empty()) // jest pusta gdy "none" - { // dodanie do wyszukiwarki - if( false == m_trackmap.Add( TP_TRACK, tmp->asName, tmp ) ) { - // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność wsteczna) - ErrorLog( "Duplicated track: " + tmp->asName ); // to zgłaszać duplikat - } - } - tmp->pCenter = ( - tmp->pTrack->CurrentSegment()->FastGetPoint_0() - + tmp->pTrack->CurrentSegment()->FastGetPoint( 0.5 ) - + tmp->pTrack->CurrentSegment()->FastGetPoint_1() ) - / 3.0; - break; - case TP_SOUND: - parser->getTokens(3); - *parser - >> tmp->pCenter.x - >> tmp->pCenter.y - >> tmp->pCenter.z; - tmp->pCenter.RotateY(aRotate.y / 180.0 * M_PI); // Ra 2014-11: uwzględnienie rotacji - tmp->pCenter += pOrigin; - parser->getTokens(); - *parser >> token; - str = token; - - tmp->tsStaticSound = sound_man->create_text_sound(str); - if (tmp->tsStaticSound) - tmp->tsStaticSound->position(tmp->pCenter).dist(sqrt(tmp->fSquareRadius)); - - if (rmin < 0.0) - rmin = 0.0; // przywrócenie poprawnej wartości, jeśli służyła do wyłączenia efektu Dopplera - parser->getTokens(); - *parser >> token; - break; - case TP_DYNAMIC: - tmp->DynamicObject = new TDynamicObject(); - // tmp->DynamicObject->Load(Parser); - parser->getTokens(3); - *parser - >> str1 // katalog - >> Skin // tekstura wymienna - >> str3; // McZapkie-131102: model w MMD - if (bTrainSet) - { // jeśli pojazd jest umieszczony w składzie - str = asTrainSetTrack; - parser->getTokens(3); - *parser - >> tf1 // Ra: -1 oznacza odwrotne wstawienie, normalnie w składzie 0 - >> DriverType // McZapkie:010303 - w przyszlosci rozne konfiguracje mechanik/pomocnik itp - >> str4; - tf3 = fTrainSetVel; // prędkość - int2 = str4.find("."); // yB: wykorzystuje tutaj zmienna, ktora potem bedzie ladunkiem - if (int2 != std::string::npos) // yB: jesli znalazl kropke, to ja przetwarza jako parametry - { - size_t dlugosc = str4.length(); - int1 = atoi(str4.substr(0, int2).c_str()); // niech sprzegiem bedzie do kropki cos - str4 = str4.substr(int2 + 1, dlugosc - int2); - } - else - { - int1 = atoi(str4.c_str()); - str4 = ""; - } - int2 = 0; // zeruje po wykorzystaniu - if (int1 < 0) - int1 = (-int1) | - ctrain_depot; // sprzęg zablokowany (pojazdy nierozłączalne przy manewrach) - if (tf1 != -1.0) - if (std::fabs(tf1) > 0.5) // maksymalna odległość między sprzęgami - do przemyślenia - int1 = 0; // likwidacja sprzęgu, jeśli odległość zbyt duża - to powinno być - // uwzględniane w fizyce sprzęgów... - TempConnectionType[iTrainSetWehicleNumber] = int1; // wartość dodatnia - } - else - { // pojazd wstawiony luzem - fTrainSetDist = 0; // zerowanie dodatkowego przesunięcia - asTrainName = ""; // puste oznacza jazdę pojedynczego bez rozkładu, "none" jest dla - // składu (trainset) - parser->getTokens(4); - *parser - >> str // track - >> tf1 // Ra: -1 oznacza odwrotne wstawienie - >> DriverType // McZapkie:010303: obsada - >> tf3; // prędkość, niektórzy wpisują tu "3" jako sprzęg, żeby nie było tabliczki - iTrainSetWehicleNumber = 0; - TempConnectionType[iTrainSetWehicleNumber] = 3; // likwidacja tabliczki na końcu? - } - parser->getTokens(); - *parser >> int2; // ilość ładunku - if (int2 > 0) - { // jeżeli ładunku jest więcej niż 0, to rozpoznajemy jego typ - parser->getTokens(); - *parser >> str2; // LoadType - if (str2 == "enddynamic") // idiotoodporność: ładunek bez podanego typu - { - str2 = ""; - int2 = 0; // ilość bez typu się nie liczy jako ładunek - } - } - else - str2 = ""; // brak ladunku - - tmp1 = FindGroundNode(str, TP_TRACK); // poszukiwanie toru - if (tmp1 ? tmp1->pTrack != NULL : false) - { // jeśli tor znaleziony - Track = tmp1->pTrack; - if (!iTrainSetWehicleNumber) // jeśli pierwszy pojazd - if (Track->evEvent0) // jeśli tor ma Event0 - if (fabs(fTrainSetVel) <= 1.0) // a skład stoi - if (fTrainSetDist >= 0.0) // ale może nie sięgać na owy tor - if (fTrainSetDist < 8.0) // i raczej nie sięga - fTrainSetDist = - 8.0; // przesuwamy około pół EU07 dla wstecznej zgodności - // 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 - { - // przesunięcie dla kolejnego, minus bo idziemy w stronę punktu 1 - fTrainSetDist -= tf3; - tmp->pCenter = tmp->DynamicObject->GetPosition(); - // automatically establish permanent connections for couplers which specify them in their definitions - if( TempConnectionType[ iTrainSetWehicleNumber ] ) { - if( tmp->DynamicObject->MoverParameters->Couplers[ ( tf1 == -1.0 ? 0 : 1 ) ].AllowedFlag & coupling::permanent ) { - TempConnectionType[ iTrainSetWehicleNumber ] |= coupling::permanent; - } - } - iTrainSetWehicleNumber++; - } - else - { // LastNode=NULL; - delete tmp; - tmp = NULL; // nie może być tu return, bo trzeba pominąć jeszcze enddynamic - } - } - else - { // gdy tor nie znaleziony - ErrorLog("Missed track: dynamic placed on \"" + tmp->DynamicObject->asTrack + "\""); - delete tmp; - tmp = NULL; // nie może być tu return, bo trzeba pominąć jeszcze enddynamic - } - parser->getTokens(); - *parser >> token; - if (token.compare("destination") == 0) - { // dokąd wagon ma jechać, uwzględniane przy manewrach - parser->getTokens(); - *parser >> token; - if (tmp) - tmp->DynamicObject->asDestination = token; - *parser >> token; - } - if (token.compare("enddynamic") != 0) - Error("enddynamic statement missing"); -/* - if( tmp->DynamicObject->MoverParameters->LightPowerSource.SourceType != TPowerSource::NotDefined ) { - // if the vehicle has defined light source, it can (potentially) emit light, so add it to the light array -*/ - if( ( tmp != nullptr ) - && ( tmp->DynamicObject->MoverParameters->CategoryFlag == 1 ) // trains only - && ( ( tmp->DynamicObject->MoverParameters->SecuritySystem.SystemType != 0 ) - || ( tmp->DynamicObject->MoverParameters->SandCapacity > 0.0 ) ) ) { - // we check for presence of security system or sand load, as a way to determine whether the vehicle is a controllable engine - // NOTE: this isn't 100% precise, e.g. middle EZT module comes with security system, while it has no lights, and some engines - // don't have security systems fitted - m_lights.insert( tmp->DynamicObject ); - } - - break; - case TP_MODEL: { -#ifdef EU07_USE_OLD_TERRAINCODE - 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->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? - } - 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->pCenter = Math3D::vector3( 0.0, 0.0, 0.0 ); // enforce placement in the world center - 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( int i = 1; i < tmp->iCount; ++i ) { // a reszta to submodele - tmp->nNode[ i ].iType = TP_SUBMODEL; - tmp->nNode[ i ].smTerrain = Global::pTerrainCompact->TerrainSquare( i - 1 ); - tmp->nNode[ i ].iFlags = 0x10; // nieprzezroczyste; nie usuwany - tmp->nNode[ i ].bVisible = true; - tmp->nNode[ i ].pCenter = tmp->pCenter; // nie przesuwamy w inne miejsce - } - } - else if( !tmp->asName.empty() ) // jest pusta gdy "none" - { // dodanie do wyszukiwarki - if( false == m_trackmap.Add( TP_MODEL, tmp->asName, tmp ) ) { - // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność wsteczna) - ErrorLog( "Duplicated model: " + tmp->asName ); // to zgłaszać duplikat - } - } -#else - if( rmin < 0 ) { - // legacy leftover: special case, terrain provided as 3d model - tmp->iType = TP_TERRAIN; - tmp->fSquareMinRadius = 0; // to w ogóle potrzebne? - // we ignore center and rotation for terrain; they should be set to 0 anyway - parser->getTokens( 4 ); - - tmp->iFlags = 0x200; // flaga usuwania - tmp->Model = new TAnimModel(); - if( false == tmp->Model->Load( parser, true ) ) { - delete tmp; - tmp = nullptr; - break; - } - tmp->iFlags |= tmp->Model->Flags(); // ustalenie, czy przezroczysty - tmp->pCenter = Math3D::vector3(); // enforce placement in the world center - // jeśli model jest terenem, trzeba utworzyć dodatkowe obiekty - tmp->iCount = tmp->Model->TerrainCount() + 1; // zliczenie submodeli - Global::pTerrainCompact = tmp->Model; - tmp->nNode = new TGroundNode[ tmp->iCount ]; // sztuczne node dla kwadratów - tmp->nNode[ 0 ].iType = TP_MODEL; // pierwszy zawiera model (dla delete) - tmp->nNode[ 0 ].Model = Global::pTerrainCompact; - tmp->nNode[ 0 ].iFlags = 0x200; // nie wyświetlany, ale usuwany - for( auto i = 1; i < tmp->iCount; ++i ) { // a reszta to submodele - tmp->nNode[ i ].iType = TP_SUBMODEL; - tmp->nNode[ i ].smTerrain = Global::pTerrainCompact->TerrainSquare( i - 1 ); - tmp->nNode[ i ].iFlags = 0x10; // nieprzezroczyste; nie usuwany - tmp->nNode[ i ].bVisible = true; - tmp->nNode[ i ].pCenter = tmp->pCenter; // nie przesuwamy w inne miejsce - } - } - else { - // regular 3d model - parser->getTokens( 3 ); - *parser - >> tmp->pCenter.x - >> tmp->pCenter.y - >> tmp->pCenter.z; - parser->getTokens(); - *parser >> tf1; - // OlO_EU&KAKISH-030103: obracanie punktow zaczepien w modelu - tmp->pCenter.RotateY( glm::radians( aRotate.y ) ); - // McZapkie-260402: model tez ma wspolrzedne wzgledne - tmp->pCenter += pOrigin; - - tmp->iFlags = 0x200; // flaga usuwania - tmp->Model = new TAnimModel(); - tmp->Model->RaAnglesSet( aRotate.x, tf1 + aRotate.y, aRotate.z ); // dostosowanie do pochylania linii - if( false == tmp->Model->Load( parser, false ) ) { - // model nie wczytał się - ignorowanie node - delete tmp; - tmp = nullptr; // nie może być tu return - break; // nie może być tu return? - } - tmp->iFlags |= tmp->Model->Flags(); // ustalenie, czy przezroczysty - if( false == tmp->asName.empty() ) { // jest pusta gdy "none" - // dodanie do wyszukiwarki - if( false == m_trackmap.Add( TP_MODEL, tmp->asName, tmp ) ) { - // przy zdublowaniu wskaźnik zostanie podmieniony w drzewku na późniejszy (zgodność wsteczna) - ErrorLog( "Duplicated model: " + tmp->asName ); // to zgłaszać duplikat - } - } - } -#endif - break; - } - // case TP_GEOMETRY : - case GL_TRIANGLES: - case GL_TRIANGLE_STRIP: - case GL_TRIANGLE_FAN: { - - parser->getTokens(); - *parser >> token; - // McZapkie-050702: opcjonalne wczytywanie parametrow materialu (ambient,diffuse,specular) - if( token.compare( "material" ) == 0 ) { - parser->getTokens(); - *parser >> token; - while( token.compare( "endmaterial" ) != 0 ) { - if( token.compare( "ambient:" ) == 0 ) { - parser->getTokens( 3 ); - *parser - >> tmp->Ambient.r - >> tmp->Ambient.g - >> tmp->Ambient.b; - tmp->Ambient /= 255.0f; - } - else if( token.compare( "diffuse:" ) == 0 ) { // Ra: coś jest nie tak, bo w jednej linijce nie działa - parser->getTokens( 3 ); - *parser - >> tmp->Diffuse.r - >> tmp->Diffuse.g - >> tmp->Diffuse.b; - tmp->Diffuse /= 255.0f; - } - else if( token.compare( "specular:" ) == 0 ) { - parser->getTokens( 3 ); - *parser - >> tmp->Specular.r - >> tmp->Specular.g - >> tmp->Specular.b; - tmp->Specular /= 255.0f; - } - else - Error( "Scene material failure!" ); - parser->getTokens(); - *parser >> token; - } - } - if( token.compare( "endmaterial" ) == 0 ) { - parser->getTokens(); - *parser >> token; - } - str = token; - tmp->m_material = GfxRenderer.Fetch_Material( str ); - auto const texturehandle = ( - tmp->m_material != null_handle ? - GfxRenderer.Material( tmp->m_material ).texture1 : - null_handle ); - auto const &texture = ( - texturehandle ? - GfxRenderer.Texture( texturehandle ) : - opengl_texture() ); // dirty workaround for lack of better api - bool const clamps = ( - texturehandle ? - texture.traits.find( 's' ) != std::string::npos : - false ); - bool const clampt = ( - texturehandle ? - texture.traits.find( 't' ) != std::string::npos : - false ); - - tmp->iFlags |= 200; // z usuwaniem - // remainder of legacy 'problend' system -- geometry assigned a texture with '@' in its name is treated as translucent, opaque otherwise - if( texturehandle != null_handle ) { - tmp->iFlags |= ( - ( ( texture.name.find( '@' ) != std::string::npos ) - && ( true == texture.has_alpha ) ) ? - 0x20 : - 0x10 ); - } - else { - tmp->iFlags |= 0x10; - } - - TGroundVertex vertex, vertex1, vertex2; - std::size_t vertexcount{ 0 }; - do { - parser->getTokens( 8, false ); - *parser - >> vertex.position.x - >> vertex.position.y - >> vertex.position.z - >> vertex.normal.x - >> vertex.normal.y - >> vertex.normal.z - >> vertex.texture.s - >> vertex.texture.t; - vertex.position = glm::rotateZ( vertex.position, glm::radians( aRotate.z ) ); - vertex.position = glm::rotateX( vertex.position, glm::radians( aRotate.x ) ); - vertex.position = glm::rotateY( vertex.position, glm::radians( aRotate.y ) ); - vertex.normal = glm::rotateZ( vertex.normal, glm::radians( aRotate.z ) ); - vertex.normal = glm::rotateX( vertex.normal, glm::radians( aRotate.x ) ); - vertex.normal = glm::rotateY( vertex.normal, glm::radians( aRotate.y ) ); - vertex.position += glm::dvec3{ pOrigin }; - if( true == clamps ) { vertex.texture.s = clamp( vertex.texture.s, 0.001f, 0.999f ); } - if( true == clampt ) { vertex.texture.t = clamp( vertex.texture.t, 0.001f, 0.999f ); } - // convert all data to gl_triangles to allow data merge for matching nodes - switch( tmp->iType ) { - case GL_TRIANGLES: { - if( vertexcount == 0 ) { vertex1 = vertex; } - else if( vertexcount == 1 ) { vertex2 = vertex; } - else if( vertexcount >= 2 ) { - if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { - importedvertices.emplace_back( vertex1 ); - importedvertices.emplace_back( vertex2 ); - importedvertices.emplace_back( vertex ); - } - else { - ErrorLog( - "Bad geometry: degenerate triangle encountered" - + ( tmp->asName != "" ? " in node \"" + tmp->asName + "\"" : "" ) - + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); - } - } - ++vertexcount; - if( vertexcount > 2 ) { vertexcount = 0; } // start new triangle if needed - break; - } - case GL_TRIANGLE_FAN: { - if( vertexcount == 0 ) { vertex1 = vertex; } - else if( vertexcount == 1 ) { vertex2 = vertex; } - else if( vertexcount >= 2 ) { - if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { - importedvertices.emplace_back( vertex1 ); - importedvertices.emplace_back( vertex2 ); - importedvertices.emplace_back( vertex ); - vertex2 = vertex; - } - else { - ErrorLog( - "Bad geometry: degenerate triangle encountered" - + ( tmp->asName != "" ? " in node \"" + tmp->asName + "\"" : "" ) - + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); - } - } - ++vertexcount; - break; - } - case GL_TRIANGLE_STRIP: { - if( vertexcount == 0 ) { vertex1 = vertex; } - else if( vertexcount == 1 ) { vertex2 = vertex; } - else if( vertexcount >= 2 ) { - if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { - // swap order every other triangle, to maintain consistent winding - if( vertexcount % 2 == 0 ) { - importedvertices.emplace_back( vertex1 ); - importedvertices.emplace_back( vertex2 ); - } - else { - importedvertices.emplace_back( vertex2 ); - importedvertices.emplace_back( vertex1 ); - } - importedvertices.emplace_back( vertex ); - - vertex1 = vertex2; - vertex2 = vertex; - } - else { - ErrorLog( - "Bad geometry: degenerate triangle encountered" - + ( tmp->asName != "" ? " in node \"" + tmp->asName + "\"" : "" ) - + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); - } - } - ++vertexcount; - break; - } - default: { break; } - } - parser->getTokens(); - *parser >> token; - - } while( token.compare( "endtri" ) != 0 ); - - tmp->iType = GL_TRIANGLES; - tmp->Piece = new piece_node(); - tmp->iNumVerts = importedvertices.size(); - - if( tmp->iNumVerts > 0 ) { - - tmp->Piece->vertices.swap( importedvertices ); - - for( auto const &vertex : tmp->Piece->vertices ) { - tmp->pCenter += vertex.position; - } - tmp->pCenter /= tmp->iNumVerts; - - r = 0; - for( auto const &vertex : tmp->Piece->vertices ) { - tf = glm::length2( vertex.position - glm::dvec3{ tmp->pCenter } ); - if( tf > r ) - r = tf; - } - tmp->fSquareRadius += r; - RaTriangleDivider( tmp ); // Ra: dzielenie trójkątów jest teraz całkiem wydajne - } // koniec wczytywania trójkątów - break; - } - case GL_LINES: - case GL_LINE_STRIP: - case GL_LINE_LOOP: { - parser->getTokens( 4 ); - *parser - >> tmp->Diffuse.r - >> tmp->Diffuse.g - >> tmp->Diffuse.b - >> tmp->fLineThickness; - tmp->Diffuse /= 255.0f; - tmp->fLineThickness = std::min( 30.0, tmp->fLineThickness ); // 30 pix equals rougly width of a signal pole viewed from ~1m away - - TGroundVertex vertex, vertex0, vertex1; - std::size_t vertexcount{ 0 }; - - parser->getTokens(); - *parser >> token; - do { - vertex.position.x = std::atof( token.c_str() ); - parser->getTokens( 2 ); - *parser - >> vertex.position.y - >> vertex.position.z; - vertex.position = glm::rotateZ( vertex.position, glm::radians( aRotate.z ) ); - vertex.position = glm::rotateX( vertex.position, glm::radians( aRotate.x ) ); - vertex.position = glm::rotateY( vertex.position, glm::radians( aRotate.y ) ); - - vertex.position += glm::dvec3{ pOrigin }; - // convert all data to gl_lines to allow data merge for matching nodes - switch( tmp->iType ) { - case GL_LINES: { - importedvertices.emplace_back( vertex ); - break; - } - case GL_LINE_STRIP: { - if( vertexcount > 0 ) { - importedvertices.emplace_back( vertex1 ); - importedvertices.emplace_back( vertex ); - } - vertex1 = vertex; - ++vertexcount; - break; - } - case GL_LINE_LOOP: { - if( vertexcount == 0 ) { - vertex0 = vertex; - vertex1 = vertex; - } - else { - importedvertices.emplace_back( vertex1 ); - importedvertices.emplace_back( vertex ); - } - vertex1 = vertex; - ++vertexcount; - break; - } - default: { break; } - } - parser->getTokens(); - *parser >> token; - } while( token.compare( "endline" ) != 0 ); - // add closing line for the loop - if( ( tmp->iType == GL_LINE_LOOP ) - && ( vertexcount > 2 ) ) { - importedvertices.emplace_back( vertex1 ); - importedvertices.emplace_back( vertex0 ); - } - if( importedvertices.size() % 2 != 0 ) { - ErrorLog( "Lines node specified odd number of vertices, encountered in file \"" + parser->Name() + "\" (line " + std::to_string( parser->Line() - 1 ) + ")" ); - importedvertices.pop_back(); - } - tmp->iType = GL_LINES; - tmp->Piece = new piece_node(); - tmp->iNumPts = (int)importedvertices.size(); - - if( false == importedvertices.empty() ) { - - tmp->Piece->vertices.swap( importedvertices ); - - glm::dvec3 minpoint( std::numeric_limits::max(), 0.0, std::numeric_limits::max() ); - glm::dvec3 maxpoint( std::numeric_limits::lowest(), 0.0, std::numeric_limits::lowest() ); - for( auto const &vertex : tmp->Piece->vertices ) { - tmp->pCenter += vertex.position; - minpoint.x = std::min( minpoint.x, vertex.position.x ); - minpoint.z = std::min( minpoint.z, vertex.position.z ); - maxpoint.x = std::max( maxpoint.x, vertex.position.x ); - maxpoint.z = std::max( maxpoint.z, vertex.position.z ); - } - tmp->pCenter /= tmp->iNumPts; - tmp->m_radius = static_cast( glm::distance( maxpoint, minpoint ) * 0.5 ); - } - break; - } - } - return tmp; -} - -TSubRect * TGround::FastGetSubRect(int iCol, int iRow) -{ - int br, bc, sr, sc; - br = iRow / iNumSubRects; - bc = iCol / iNumSubRects; - sr = iRow - br * iNumSubRects; - sc = iCol - bc * iNumSubRects; - if ((br < 0) || (bc < 0) || (br >= iNumRects) || (bc >= iNumRects)) - return NULL; - return (Rects[bc][br].FastGetSubRect(sc, sr)); -} - -TSubRect * TGround::GetSubRect(int iCol, int iRow) -{ // znalezienie małego kwadratu mapy - int br, bc, sr, sc; - br = iRow / iNumSubRects; // współrzędne kwadratu kilometrowego - bc = iCol / iNumSubRects; - sr = iRow - br * iNumSubRects; // współrzędne wzglęne małego kwadratu - sc = iCol - bc * iNumSubRects; - if ((br < 0) || (bc < 0) || (br >= iNumRects) || (bc >= iNumRects)) - return NULL; // jeśli poza mapą - return (Rects[bc][br].SafeGetSubRect(sc, sr)); // pobranie małego kwadratu -} - -TEvent * TGround::FindEvent(const std::string &asEventName) -{ - if( asEventName.empty() ) { return nullptr; } - - auto const lookup = m_eventmap.find( asEventName ); - return ( - lookup != m_eventmap.end() ? - lookup->second : - nullptr ); -} - -TEvent * TGround::FindEventScan( std::string const &asEventName ) -{ // wyszukanie eventu z opcją utworzenia niejawnego dla komórek skanowanych - auto const lookup = m_eventmap.find( asEventName ); - auto e = ( - lookup != m_eventmap.end() ? - lookup->second : - nullptr ); - if (e) - return e; // jak istnieje, to w porządku - if (asEventName.rfind(":scan") != std::string::npos) // jeszcze może być event niejawny - { // no to szukamy komórki pamięci o nazwie zawartej w evencie - std::string n = asEventName.substr(0, asEventName.length() - 5); // do dwukropka - if( m_trackmap.Find( TP_MEMCELL, n ) != nullptr ) // 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"); - for (int type = 0; type < TP_LAST; ++type) { - for (TGroundNode *Current = nRootOfType[type]; Current != nullptr; Current = Current->nNext) { - - if( type == GL_TRIANGLES ) { Current->InitNormals(); } - - if (Current->iType != TP_DYNAMIC) - { // pojazdów w ogóle nie dotyczy dodawanie do mapy - if( ( type == TP_EVLAUNCH ) - && ( true == Current->EvLaunch->IsGlobal() ) ) { - // dodanie do globalnego obiektu - srGlobal.NodeAdd( Current ); - } -#ifdef EU07_USE_OLD_TERRAINCODE - else if (type == TP_TERRAIN) { - // specjalne przetwarzanie terenu wczytanego z pliku E3D - TGroundRect *gr; - for (int j = 1; j < Current->iCount; ++j) { - // od 1 do końca są zestawy trójkątów - std::string xxxzzz = Current->nNode[j].smTerrain->pName; // pobranie nazwy - gr = GetRect( - ( std::stoi( xxxzzz.substr( 0, 3 )) - 500 ) * 1000, - ( std::stoi( xxxzzz.substr( 3, 3 )) - 500 ) * 1000 ); - gr->nTerrain = Current->nNode + j; // zapamiętanie - } - } -#endif - else { - TSubRect *targetcell { nullptr }; - // test whether we can add the node to a ground cell, or a subcell - if( ( Current->iType != GL_TRIANGLES ) - || ( Current->iFlags & 0x20 ) - || ( Current->fSquareMinRadius != 0.0 ) - || ( Current->fSquareRadius <= 90000.0 ) ) { - // add to sub-rectangle - targetcell = GetSubRect( Current->pCenter.x, Current->pCenter.z ); - } - else { - // dodajemy do kwadratu kilometrowego - targetcell = GetRect( Current->pCenter.x, Current->pCenter.z ); - } - if( targetcell != nullptr ) { - targetcell->NodeAdd( Current ); - } - else { - ErrorLog( "Scenery node" + ( - Current->asName == "" ? - "" : - " \"" + Current->asName + "\"" ) - + " placed in location outside of map bounds (location: " + to_string( glm::dvec3{ Current->pCenter } ) + ")" ); - } - } - } - } - } - for (std::size_t i = 0; i < iNumRects; ++i) - for (std::size_t j = 0; j < iNumRects; ++j) - Rects[i][j].Optimize(); // optymalizacja obiektów w sektorach - - WriteLog("InitNormals OK"); - - InitTracks(); //łączenie odcinków ze sobą i przyklejanie eventów - WriteLog("InitTracks OK"); - - InitTraction(); //łączenie drutów ze sobą - WriteLog("InitTraction OK"); - - InitEvents(); - WriteLog("InitEvents OK"); - - InitLaunchers(); - WriteLog("InitLaunchers OK"); - - WriteLog("FirstInit is done"); -}; - -void TGround::add_event(TEvent *tmp) -{ - if (tmp->Type == tp_Unknown) - delete tmp; - else - { // najpierw sprawdzamy, czy nie ma, a potem dopisujemy - TEvent *found = FindEvent(tmp->asName); - if (found) - { // jeśli znaleziony duplikat - auto const size = tmp->asName.size(); - if( tmp->asName[0] == '#' ) // zawsze jeden znak co najmniej jest - { - delete tmp; - tmp = nullptr; - } // utylizacja duplikatu z krzyżykiem - else if( ( size > 8 ) - && ( tmp->asName.substr( 0, 9 ) == "lineinfo:" )) - // tymczasowo wyjątki - { - delete tmp; - tmp = nullptr; - } // tymczasowa utylizacja duplikatów W5 - else if( ( size > 8 ) - && ( tmp->asName.substr( size - 8 ) == "_warning")) - // tymczasowo wyjątki - { - delete tmp; - tmp = nullptr; - } // tymczasowa utylizacja duplikatu z trąbieniem - else if( ( size > 4 ) - && ( tmp->asName.substr( size - 4 ) == "_shp" )) - // nie podlegają logowaniu - { - delete tmp; - tmp = NULL; - } // tymczasowa utylizacja duplikatu SHP - if (tmp) // jeśli nie został zutylizowany - if (Global::bJoinEvents) - found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków) - else - { - ErrorLog("Duplicated event: " + tmp->asName); - found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków) - found->Type = tp_Ignored; // dezaktywacja pierwotnego - taka proteza na - // wsteczną zgodność - // SafeDelete(tmp); //bezlitośnie usuwamy wszelkie duplikaty, żeby nie - // zaśmiecać drzewka - } - } - if ( nullptr != tmp ) - { // jeśli nie duplikat - tmp->evNext2 = RootEvent; // lista wszystkich eventów (m.in. do InitEvents) - RootEvent = tmp; - if (!found) - { // jeśli nazwa wystąpiła, to do kolejki i wyszukiwarki dodawany jest tylko pierwszy - if( ( RootEvent->Type != tp_Ignored ) - && ( RootEvent->asName.find( "onstart" ) != std::string::npos ) ) { - // event uruchamiany automatycznie po starcie - AddToQuery( RootEvent, NULL ); // dodanie do kolejki - } - m_eventmap.emplace( tmp->asName, tmp ); // dodanie do wyszukiwarki - } - } - } -} - -bool TGround::Init(std::string File) -{ // główne wczytywanie scenerii - if (ToLower(File).substr(0, 7) == "scenery") - File = File.erase(0, 8); // Ra: usunięcie niepotrzebnych znaków - zgodność wstecz z 2003 - WriteLog("Loading scenery from " + File); - Global::pGround = this; - // pTrain=NULL; - pOrigin = aRotate = vector3(0, 0, 0); // zerowanie przesunięcia i obrotu - std::string str; - // int size; - std::string subpath = Global::asCurrentSceneryPath; // "scenery/"; - cParser parser(File, cParser::buffer_FILE, subpath, Global::bLoadTraction); - std::string token; - - std::stack OriginStack; // stos zagnieżdżenia origin - - TGroundNode *LastNode = nullptr; // do użycia w trainset - token = ""; - parser.getTokens(); - parser >> token; - std::size_t processed = 0; - - while (token != "") //(!Parser->EndOfFile) - { - ++processed; - if( processed % 1000 == 0 ) - { - glfwPollEvents(); - UILayer.set_progress( parser.getProgress(), parser.getFullProgress() ); - GfxRenderer.Render(); - } - str = token; - if (str == "node") { - // rozpoznanie węzła - LastNode = AddGroundNode(&parser); - if (LastNode) { - // jeżeli przetworzony poprawnie - switch( LastNode->iType ) { - // validate the new node - case GL_TRIANGLES: { - if( true == LastNode->Piece->vertices.empty() ) { - SafeDelete( LastNode ); - } - break; - } - case TP_TRACTION: { - if( false == Global::bLoadTraction ) { - // usuwamy druty, jeśli wyłączone - SafeDelete( LastNode ); - } - break; - } -#ifndef EU07_SCENERY_EDITOR - case TP_TERRAIN: { - // convert legacy terrain model to a series of triangle nodes, to take advantage of camera-centric render and geometry merging - // NOTE: this leaves us with a large model we don't use loaded and taking space. it'll sort of solve itself when the binary scenery file is in place - convert_terrain( LastNode ); - SafeDelete( LastNode ); - Global::pTerrainCompact = nullptr; - } -#endif - default: { - break; - } - } - - if( LastNode ) { - // dopiero na koniec dopisujemy do tablic - if( LastNode->iType != TP_DYNAMIC ) { - // jeśli nie jest pojazdem ostatni dodany dołączamy na końcu nowego - LastNode->nNext = nRootOfType[ LastNode->iType ]; - // ustawienie nowego na początku listy - nRootOfType[ LastNode->iType ] = LastNode; - } - else { // jeśli jest pojazdem - if( ( LastNode->DynamicObject->Mechanik != nullptr ) - && ( LastNode->DynamicObject->Mechanik->Primary() ) ) { - // jeśli jest głównym (pasażer nie jest) - nTrainSetDriver = LastNode; // pojazd, któremu zostanie wysłany rozkład - } - LastNode->nNext = nRootDynamic; - nRootDynamic = LastNode; // dopisanie z przodu do listy - - if( nTrainSetNode != nullptr ) { - // jeżeli istnieje wcześniejszy TP_DYNAMIC - nTrainSetNode->DynamicObject->AttachPrev( - LastNode->DynamicObject, - TempConnectionType[ iTrainSetWehicleNumber - 2 ] ); - } - nTrainSetNode = LastNode; // ostatnio wczytany - - if( TempConnectionType[ iTrainSetWehicleNumber - 1 ] == 0 ) // jeśli sprzęg jest zerowy, to wysłać rozkład do składu - { // powinien też tu wchodzić, gdy pojazd bez trainset - if( nTrainSetDriver ) // pojazd, któremu zostanie wysłany rozkład - { // wysłanie komendy "Timetable" ustawia odpowiedni tryb jazdy - nTrainSetDriver->DynamicObject->Mechanik->DirectionInitial(); - nTrainSetDriver->DynamicObject->Mechanik->PutCommand( - "Timetable:" + asTrainName, - fTrainSetVel, 0, - nullptr ); - nTrainSetDriver = nullptr; // a przy "endtrainset" już wtedy nie potrzeba - } - } - } - } - } - else - { - ErrorLog("Bad node: node parsing error, encountered in file \"" + parser.Name() + "\" (line " + std::to_string( parser.Line() - 1 ) + ")"); - // break; - } - } - else if (str == "trainset") - { - iTrainSetWehicleNumber = 0; - nTrainSetNode = NULL; - nTrainSetDriver = NULL; // pojazd, któremu zostanie wysłany rozkład - bTrainSet = true; - parser.getTokens(); - parser >> token; - asTrainName = token; // McZapkie: rodzaj+nazwa pociagu w SRJP - parser.getTokens(); - parser >> token; - asTrainSetTrack = token; //ścieżka startowa - parser.getTokens(2); - parser >> fTrainSetDist >> fTrainSetVel; // przesunięcie i prędkość - } - else if (str == "endtrainset") - { // McZapkie-110103: sygnaly konca pociagu ale tylko dla pociagow rozkladowych - if (nTrainSetNode) // trainset bez dynamic się sypał - { // powinien też tu wchodzić, gdy pojazd bez trainset - if (nTrainSetDriver) // pojazd, któremu zostanie wysłany rozkład - { // wysłanie komendy "Timetable" ustawia odpowiedni tryb jazdy - nTrainSetDriver->DynamicObject->Mechanik->DirectionInitial(); - nTrainSetDriver->DynamicObject->Mechanik->PutCommand("Timetable:" + asTrainName, - fTrainSetVel, 0, NULL); - } - } - if( LastNode ) { - // ostatni wczytany obiekt - if( LastNode->iType == TP_DYNAMIC ) { - // o ile jest pojazdem (na ogół jest, ale kto wie...) - if( ( iTrainSetWehicleNumber > 0 ) - && ( TempConnectionType[ iTrainSetWehicleNumber - 1 ] == 0 ) ) { - // jeśli ostatni pojazd ma sprzęg 0 to założymy mu końcówki blaszane - // (jak AI się odpali, to sobie poprawi) - LastNode->DynamicObject->RaLightsSet( -1, 2 + 32 + 64 ); - } - } - } - bTrainSet = false; - fTrainSetVel = 0; - nTrainSetNode = nTrainSetDriver = nullptr; - iTrainSetWehicleNumber = 0; - } - else if (str == "event") - { - TEvent *tmp = new TEvent(); - tmp->Load(&parser, &pOrigin); - add_event(tmp); - } - else if (str == "lua") - { - parser.getTokens(); - std::string file; - parser >> file; - m_lua.interpret(subpath + file); - } - else if (str == "rotate") - { - // parser.getTokens(3); - // parser >> aRotate.x >> aRotate.y >> aRotate.z; //Ra: to potrafi dawać błędne rezultaty // ??? how so TODO: investigate - parser.getTokens(); - parser >> aRotate.x; - parser.getTokens(); - parser >> aRotate.y; - parser.getTokens(); - parser >> aRotate.z; - } - else if (str == "origin") - { - Math3D::vector3 offset; - parser.getTokens(3); - parser - >> offset.x - >> offset.y - >> offset.z; - // sumowanie całkowitego przesunięcia - OriginStack.emplace( - offset + ( - OriginStack.empty() ? - Math3D::vector3() : - OriginStack.top() ) ); - pOrigin = OriginStack.top(); - } - else if (str == "endorigin") - { - OriginStack.pop(); - pOrigin = ( OriginStack.empty() ? - Math3D::vector3() : - OriginStack.top() ); - } - else if (str == "atmo") // TODO: uporzadkowac gdzie maja byc parametry mgly! - { // Ra: ustawienie parametrów OpenGL przeniesione do FirstInit - WriteLog("Scenery atmo definition"); - parser.getTokens(3); -/* - // disabled, no longer used - parser - >> Global::AtmoColor[0] - >> Global::AtmoColor[1] - >> Global::AtmoColor[2]; -*/ - parser.getTokens(2); - parser - >> Global::fFogStart - >> Global::fFogEnd; - if (Global::fFogEnd > 0.0) - { // ostatnie 3 parametry są opcjonalne - parser.getTokens(3); - parser - >> Global::FogColor[0] - >> Global::FogColor[1] - >> Global::FogColor[2]; - } - parser.getTokens(); - parser >> token; - if( token != "endatmo" ) { - // optional overcast parameter - // NOTE: parameter system needs some decent replacement, but not worth the effort if we're moving to built-in editor - Global::Overcast = clamp( std::stof( token ), 0.0f, 1.0f ); - } - while (token.compare("endatmo") != 0) - { // a kolejne parametry są pomijane - parser.getTokens(); - parser >> token; - } - } - else if (str == "time") - { - WriteLog("Scenery time definition"); - parser.getTokens(); - parser >> token; - - cParser timeparser( token ); - timeparser.getTokens( 2, false, ":" ); - auto &time = simulation::Time.data(); - timeparser - >> time.wHour - >> time.wMinute; - - // NOTE: we ignore old sunrise and sunset definitions, as they're now calculated dynamically - - while (token.compare("endtime") != 0) - { - parser.getTokens(); - parser >> token; - } - } - else if (str == "light") - { // Ra: ustawianie światła przeniesione do FirstInit - WriteLog("Scenery light definition"); - parser.getTokens(3, false); - parser - >> Global::DayLight.direction.x - >> Global::DayLight.direction.y - >> Global::DayLight.direction.z; - Global::DayLight.direction = glm::normalize(Global::DayLight.direction); - parser.getTokens(9, false); - - do { - parser.getTokens(); - parser >> token; - } while (token.compare("endlight") != 0); - } - else if (str == "camera") - { - vector3 xyz, abc; - xyz = abc = vector3(0, 0, 0); // wartości domyślne, bo nie wszystie muszą być - int i = -1, into = -1; // do której definicji kamery wstawić - WriteLog("Scenery camera definition"); - do - { // opcjonalna siódma liczba określa numer kamery, a kiedyś były tylko 3 - parser.getTokens(); - parser >> token; - switch (++i) - { // kiedyś camera miało tylko 3 współrzędne - case 0: - xyz.x = atof(token.c_str()); - break; - case 1: - xyz.y = atof(token.c_str()); - break; - case 2: - xyz.z = atof(token.c_str()); - break; - case 3: - abc.x = atof(token.c_str()); - break; - case 4: - abc.y = atof(token.c_str()); - break; - case 5: - abc.z = atof(token.c_str()); - break; - case 6: - into = atoi(token.c_str()); // takie sobie, bo można wpisać -1 - } - } while (token.compare("endcamera") != 0); - if (into < 0) - into = ++Global::iCameraLast; - if ((into >= 0) && (into < 10)) - { // przepisanie do odpowiedniego miejsca w tabelce - Global::FreeCameraInit[ into ] = xyz; - Global::FreeCameraInitAngle[ into ] = - Math3D::vector3( - DegToRad( abc.x ), - DegToRad( abc.y ), - DegToRad( abc.z ) ); - Global::iCameraLast = into; // numer ostatniej - } - } - else if (str == "sky") - { // youBy - niebo z pliku - WriteLog("Scenery sky definition"); - parser.getTokens(); - parser >> token; - std::string SkyTemp = token; - if (Global::asSky == "1") - Global::asSky = SkyTemp; - do - { // pożarcie dodatkowych parametrów - parser.getTokens(); - parser >> token; - } while (token.compare("endsky") != 0); - WriteLog(Global::asSky.c_str()); - } - else if (str == "firstinit") - FirstInit(); - else if (str == "description") - { - do - { - parser.getTokens(); - parser >> token; - } while (token.compare("enddescription") != 0); - } - else if (str == "test") - { // wypisywanie treści po przetworzeniu - WriteLog("---> Parser test:"); - do - { - parser.getTokens(); - parser >> token; - WriteLog(token.c_str()); - } while (token.compare("endtest") != 0); - WriteLog("---> End of parser test."); - } - else if (str == "config") - { // możliwość przedefiniowania parametrów w scenerii - Global::ConfigParse(parser); // parsowanie dodatkowych ustawień - } - else if (str != "") { - // pomijanie od nierozpoznanej komendy do jej zakończenia - if ((token.length() > 2) && (atof(token.c_str()) == 0.0)) { - // jeśli nie liczba, to spróbować pominąć komendę - WriteLog( "Unrecognized command: \"" + str + "\" encountered in file \"" + parser.Name() + "\" (line " + std::to_string( parser.Line() - 1 ) + ")" ); - str = "end" + str; - do - { - parser.getTokens(); - token = ""; - parser >> token; - } while ((token != "") && (token.compare(str.c_str()) != 0)); - } - else { - // jak liczba to na pewno błąd - ErrorLog( "Unrecognized command: \"" + str + "\" encountered in file \"" + parser.Name() + "\" (line " + std::to_string( parser.Line() - 1 ) + ")" ); - } - } - - token = ""; - parser.getTokens(); - parser >> token; - } - - if (!bInitDone) - FirstInit(); // jeśli nie było w scenerii -#ifdef EU07_USE_OLD_TERRAINCODE - if (Global::pTerrainCompact) - TerrainWrite(); // Ra: teraz można zapisać teren w jednym pliku -#endif - Global::iPause &= ~0x10; // koniec pauzy wczytywania - return true; -} - -bool TGround::InitEvents() -{ //łączenie eventów z pozostałymi obiektami - TGroundNode *tmp, *trk; - std::string cellastext; - int i; - for (TEvent *Current = RootEvent; Current; Current = Current->evNext2) - { - switch (Current->Type) - { - case tp_AddValues: // sumowanie wartości - case tp_UpdateValues: // zmiana wartości - tmp = FindGroundNode(Current->asNodeName, TP_MEMCELL); // nazwa komórki powiązanej z eventem - if (tmp) - { // McZapkie-100302 - if (Current->iFlags & (conditional_trackoccupied | conditional_trackfree)) - { // jeśli chodzi o zajetosc toru (tor może być inny, niż wpisany w komórce) - trk = FindGroundNode(Current->asNodeName, TP_TRACK); // nazwa toru ta sama, co nazwa komórki - if (trk) - Current->Params[9].asTrack = trk->pTrack; - if (!Current->Params[9].asTrack) - ErrorLog("Bad event: track \"" + Current->asNodeName + "\" referenced in event \"" + Current->asName + "\" doesn't exist"); - } - Current->Params[4].nGroundNode = tmp; - Current->Params[5].asMemCell = tmp->MemCell; // komórka do aktualizacji - if (Current->iFlags & (conditional_memcompare)) - Current->Params[9].asMemCell = tmp->MemCell; // komórka do badania warunku - if (!tmp->MemCell->asTrackName - .empty()) // tor powiązany z komórką powiązaną z eventem - { // tu potrzebujemy wskaźnik do komórki w (tmp) - trk = FindGroundNode(tmp->MemCell->asTrackName, TP_TRACK); - if (trk) - Current->Params[6].asTrack = trk->pTrack; - else - ErrorLog("Bad memcell: track \"" + tmp->MemCell->asTrackName + "\" referenced in memcell \"" + tmp->asName + "\" doesn't exist"); - } - else - Current->Params[6].asTrack = NULL; - } - else - { // nie ma komórki, to nie będzie działał poprawnie - Current->Type = tp_Ignored; // deaktywacja - ErrorLog("Bad event: event \"" + Current->asName + "\" cannot find memcell \"" + Current->asNodeName + "\""); - } - break; - case tp_LogValues: // skojarzenie z memcell - if (Current->asNodeName.empty()) - { // brak skojarzenia daje logowanie wszystkich - Current->Params[9].asMemCell = NULL; - break; - } - case tp_GetValues: - case tp_WhoIs: - tmp = FindGroundNode(Current->asNodeName, TP_MEMCELL); - if (tmp) - { - Current->Params[8].nGroundNode = tmp; - Current->Params[9].asMemCell = tmp->MemCell; - if (Current->Type == tp_GetValues) // jeśli odczyt komórki - if (tmp->MemCell->IsVelocity()) // a komórka zawiera komendę SetVelocity albo - // ShuntVelocity - Current->bEnabled = false; // to event nie będzie dodawany do kolejki - } - else - { // nie ma komórki, to nie będzie działał poprawnie - Current->Type = tp_Ignored; // deaktywacja - ErrorLog("Bad event: event \"" + Current->asName + "\" cannot find memcell \"" + Current->asNodeName + "\""); - } - break; - case tp_CopyValues: // skopiowanie komórki do innej - tmp = FindGroundNode(Current->asNodeName, TP_MEMCELL); // komórka docelowa - if (tmp) - { - Current->Params[4].nGroundNode = tmp; - Current->Params[5].asMemCell = tmp->MemCell; // komórka docelowa - if (!tmp->MemCell->asTrackName - .empty()) // tor powiązany z komórką powiązaną z eventem - { // tu potrzebujemy wskaźnik do komórki w (tmp) - trk = FindGroundNode(tmp->MemCell->asTrackName, TP_TRACK); - if (trk) - Current->Params[6].asTrack = trk->pTrack; - else - ErrorLog("Bad memcell: track \"" + tmp->MemCell->asTrackName + "\" referenced in memcell \"" + tmp->asName + "\" doesn't exists"); - } - else - Current->Params[6].asTrack = NULL; - } - else - ErrorLog("Bad event: copyvalues event \"" + Current->asName + "\" cannot find memcell \"" + Current->asNodeName + "\""); - cellastext = Current->Params[ 9 ].asText; - SafeDeleteArray(Current->Params[9].asText); // usunięcie nazwy komórki - tmp = FindGroundNode( cellastext, TP_MEMCELL); // komórka źódłowa - if (tmp != nullptr ) { - Current->Params[8].nGroundNode = tmp; - Current->Params[9].asMemCell = tmp->MemCell; // komórka źródłowa - } - else - ErrorLog("Bad event: copyvalues event \"" + Current->asName + "\" cannot find memcell \"" + cellastext + "\""); - break; - case tp_Animation: // animacja modelu - tmp = FindGroundNode(Current->asNodeName, TP_MODEL); // egzemplarza modelu do animowania - if (tmp) - { - cellastext = Current->Params[9].asText; // skopiowanie nazwy submodelu do bufora roboczego - SafeDeleteArray(Current->Params[9].asText); // usunięcie nazwy submodelu - if (Current->Params[0].asInt == 4) - Current->Params[9].asModel = tmp->Model; // model dla całomodelowych animacji - else - { // standardowo przypisanie submodelu - Current->Params[9].asAnimContainer = tmp->Model->GetContainer(cellastext); // submodel - if (Current->Params[9].asAnimContainer) - { - Current->Params[9].asAnimContainer->WillBeAnimated(); // oflagowanie - // animacji - if (!Current->Params[9] - .asAnimContainer->Event()) // nie szukać, gdy znaleziony - Current->Params[9].asAnimContainer->EventAssign( - FindEvent(Current->asNodeName + "." + cellastext + ":done")); - } - } - } - else - ErrorLog("Bad event: animation event \"" + Current->asName + "\" cannot find model \"" + Current->asNodeName + "\""); - Current->asNodeName = ""; - break; - case tp_Lights: // zmiana świeteł modelu - tmp = FindGroundNode(Current->asNodeName, TP_MODEL); - if (tmp) - Current->Params[9].asModel = tmp->Model; - else - ErrorLog("Bad event: lights event \"" + Current->asName + "\" cannot find model \"" + Current->asNodeName + "\""); - Current->asNodeName = ""; - break; - case tp_Visible: // ukrycie albo przywrócenie obiektu - tmp = FindGroundNode(Current->asNodeName, TP_MODEL); // najpierw model - if (!tmp) - tmp = FindGroundNode(Current->asNodeName, TP_TRACTION); // może druty? - if (!tmp) - tmp = FindGroundNode(Current->asNodeName, TP_TRACK); // albo tory? - if (tmp) - Current->Params[9].nGroundNode = tmp; - else - ErrorLog("Bad event: 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 event: switch event \"" + Current->asName + "\" cannot find track \"" + Current->asNodeName + "\""); - Current->asNodeName = ""; - break; - case tp_Sound: // odtworzenie dźwięku - tmp = FindGroundNode(Current->asNodeName, TP_SOUND); - if (tmp) - Current->Params[9].tsTextSound = tmp->tsStaticSound; - else - ErrorLog("Bad event: sound event \"" + Current->asName + "\" cannot find static sound \"" + Current->asNodeName + "\""); - Current->asNodeName = ""; - break; - case tp_TrackVel: // ustawienie prędkości na torze - if (!Current->asNodeName.empty()) - { - tmp = FindGroundNode(Current->asNodeName, TP_TRACK); - if (tmp) - { - tmp->pTrack->iAction |= - 0x200; // flaga zmiany prędkości toru jest istotna dla skanowania - Current->Params[9].asTrack = tmp->pTrack; - } - else - ErrorLog("Bad event: track velocity event \"" + Current->asName + "\" cannot find track \"" + Current->asNodeName + "\""); - } - Current->asNodeName = ""; - break; - case tp_DynVel: // komunikacja z pojazdem o konkretnej nazwie - if (Current->asNodeName == "activator") - Current->Params[9].asDynamic = NULL; - else - { - tmp = FindGroundNode(Current->asNodeName, TP_DYNAMIC); - if (tmp) - Current->Params[9].asDynamic = tmp->DynamicObject; - else - Error("Bad event: vehicle velocity event \"" + Current->asName + "\" cannot find vehicle \"" + Current->asNodeName + "\""); - } - Current->asNodeName = ""; - break; - case tp_Multiple: - if (Current->Params[9].asText != NULL) - { // przepisanie nazwy do bufora - cellastext = Current->Params[ 9 ].asText; - SafeDeleteArray(Current->Params[9].asText); - Current->Params[9].asPointer = NULL; // zerowanie wskaźnika, aby wykryć brak obeiktu - } - else - cellastext = ""; - if (Current->iFlags & (conditional_trackoccupied | conditional_trackfree)) - { // jeśli chodzi o zajetosc toru - tmp = FindGroundNode(cellastext, TP_TRACK); - if (tmp) - Current->Params[9].asTrack = tmp->pTrack; - if (!Current->Params[9].asTrack) - { - ErrorLog( "Bad event: multi-event \"" + Current->asName + "\" cannot find track \"" + cellastext + "\"" ); - Current->iFlags &= ~(conditional_trackoccupied | conditional_trackfree); // zerowanie flag - } - } - else if (Current->iFlags & (conditional_memstring | conditional_memval1 | conditional_memval2)) - { // jeśli chodzi o komorke pamieciową - tmp = FindGroundNode(cellastext, TP_MEMCELL); - if (tmp) - Current->Params[9].asMemCell = tmp->MemCell; - if (!Current->Params[9].asMemCell) - { - ErrorLog( "Bad event: multi-event \"" + Current->asName + "\" cannot find memory cell \"" + cellastext + "\"" ); - Current->iFlags &= ~(conditional_memstring | conditional_memval1 | conditional_memval2); - } - } - for (i = 0; i < 8; ++i) - { - if (Current->Params[i].asText != NULL) - { - cellastext = Current->Params[ i ].asText; - SafeDeleteArray(Current->Params[i].asText); - Current->Params[i].asEvent = FindEvent(cellastext); - if( !Current->Params[ i ].asEvent ) { - // Ra: tylko w logu informacja o braku - if( ( Current->Params[ i ].asText == NULL ) - || ( std::string( Current->Params[ i ].asText ).substr( 0, 5 ) != "none_" ) ) { - ErrorLog( "Bad event: multi-event \"" + Current->asName + "\" cannot find event \"" + cellastext + "\"" ); - } - } - } - } - break; - case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource) - if (!Current->asNodeName.empty()) - { - tmp = FindGroundNode(Current->asNodeName, TP_TRACTIONPOWERSOURCE); // podłączenie zasilacza - if (tmp) - Current->Params[9].psPower = tmp->psTractionPowerSource; - else - ErrorLog("Bad event: voltage event \"" + Current->asName + "\" cannot find power source \"" + Current->asNodeName + "\""); - } - Current->asNodeName = ""; - break; - case tp_Message: // wyświetlenie komunikatu - break; - } - if (Current->fDelay < 0) - AddToQuery(Current, NULL); - } - for (TGroundNode *Current = nRootOfType[TP_MEMCELL]; Current; Current = Current->nNext) - { // Ra: eventy komórek pamięci, wykonywane po wysłaniu komendy do zatrzymanego pojazdu - Current->MemCell->AssignEvents(FindEvent(Current->asName + ":sent")); - } - return true; -} - -void TGround::InitTracks() -{ //łączenie torów ze sobą i z eventami - TGroundNode *Current, *Model; - TTrack *tmp; // znaleziony tor - TTrack *Track; - int iConnection; - - for (Current = nRootOfType[TP_TRACK]; Current; Current = Current->nNext) { - - Track = Current->pTrack; - // assign track events - Track->AssignEvents( - FindEvent( Track->asEvent0Name ), - FindEvent( Track->asEvent1Name ), - FindEvent( Track->asEvent2Name ) ); - Track->AssignallEvents( - FindEvent( Track->asEventall0Name ), - FindEvent( Track->asEventall1Name ), - FindEvent( Track->asEventall2Name ) ); - if( ( Global::iHiddenEvents & 1 ) - && ( false == Current->asName.empty() ) ) { - // jeśli podana jest nazwa torów, można szukać eventów skojarzonych przez nazwę - Track->AssignEvents( - FindEvent( Current->asName + ":event0" ), - FindEvent( Current->asName + ":event1" ), - FindEvent( Current->asName + ":event2" ) ); - Track->AssignallEvents( - FindEvent( Current->asName + ":eventall0" ), - FindEvent( Current->asName + ":eventall1" ), - FindEvent( Current->asName + ":eventall2" ) ); - } - - switch (Track->eType) - { - case tt_Table: // obrotnicę też łączymy na starcie z innymi torami - Model = FindGroundNode(Current->asName, TP_MODEL); // szukamy modelu o tej samej nazwie - // if (tmp) //mamy model, trzeba zapamiętać wskaźnik do jego animacji - { // jak coś pójdzie źle, to robimy z tego normalny tor - // Track->ModelAssign(tmp->Model->GetContainer(NULL)); //wiązanie toru z modelem - // obrotnicy - 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; - } - std::string const name = Track->IsolatedName(); // pobranie nazwy odcinka izolowanego - if (!name.empty()) // jeśli została zwrócona nazwa - Track->IsolatedEventsAssign(FindEvent(name + ":busy"), FindEvent(name + ":free")); - if (Current->asName.substr(0, 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 - m_trackmap.Add( TP_MEMCELL, p->asName, Current ); - Current->nNext = - nRootOfType[TP_MEMCELL]; // to nie powinno tutaj być, bo robi się śmietnik - nRootOfType[TP_MEMCELL] = Current; - p->pMemCell = Current->MemCell; // wskaźnik komóki przekazany do odcinka izolowanego - } - p = p->Next(); - } - // 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; - std::string name; - for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext) - { // podłączenie do zasilacza, żeby można było sumować prąd kilku pojazdów - // a jednocześnie z jednego miejsca zmieniać napięcie eventem - // wykonywane najpierw, żeby można było logować podłączenie 2 zasilaczy do jednego drutu - // izolator zawieszony na przęśle jest ma być osobnym odcinkiem drutu o długości ok. 1m, - // podłączonym do zasilacza o nazwie "*" (gwiazka); "none" nie będzie odpowiednie - Traction = nCurrent->hvTraction; - nTemp = FindGroundNode(Traction->asPowerSupplyName, TP_TRACTIONPOWERSOURCE); - if (nTemp) // jak zasilacz znaleziony - Traction->PowerSet(nTemp->psTractionPowerSource); // to podłączyć do przęsła - else if (Traction->asPowerSupplyName != "*") // gwiazdka dla przęsła z izolatorem - if (Traction->asPowerSupplyName != "none") // dopuszczamy na razie brak podłączenia? - { // logowanie błędu i utworzenie zasilacza o domyślnej zawartości - ErrorLog("Missed TractionPowerSource: " + Traction->asPowerSupplyName); - nTemp = new TGroundNode(); - nTemp->iType = TP_TRACTIONPOWERSOURCE; - nTemp->asName = Traction->asPowerSupplyName; - nTemp->psTractionPowerSource = new TTractionPowerSource(nTemp); - nTemp->psTractionPowerSource->Init(Traction->NominalVoltage, Traction->MaxCurrent); - nTemp->nNext = nRootOfType[nTemp->iType]; // ostatni dodany dołączamy na końcu - // nowego - nRootOfType[nTemp->iType] = nTemp; // ustawienie nowego na początku listy - } - } - for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext) - { - Traction = nCurrent->hvTraction; - if (!Traction->hvNext[0]) // tylko jeśli jeszcze nie podłączony - { - tmp = FindTraction(Traction->pPoint1, iConnection, nCurrent); - switch (iConnection) - { - case 0: - Traction->Connect(0, tmp, 0); - break; - case 1: - Traction->Connect(0, tmp, 1); - break; - } - if (Traction->hvNext[0]) // jeśli został podłączony - if (Traction->psSection && tmp->psSection) // tylko przęsło z izolatorem może nie - // mieć zasilania, bo ma 2, trzeba - // sprawdzać sąsiednie - if (Traction->psSection != - tmp->psSection) // połączone odcinki mają różne zasilacze - { // to może być albo podłączenie podstacji lub kabiny sekcyjnej do sekcji, albo - // błąd - if (Traction->psSection->bSection && !tmp->psSection->bSection) - { //(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji - tmp->PowerSet(Traction->psSection); // zastąpienie wskazaniem sekcji - } - else if (!Traction->psSection->bSection && tmp->psSection->bSection) - { //(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji - Traction->PowerSet(tmp->psSection); // zastąpienie wskazaniem sekcji - } - else // jeśli obie to sekcje albo obie podstacje, to będzie błąd - ErrorLog("Bad power: at " + - to_string(Traction->pPoint1.x, 2, 6) + " " + - to_string(Traction->pPoint1.y, 2, 6) + " " + - to_string(Traction->pPoint1.z, 2, 6)); - } - } - if (!Traction->hvNext[1]) // tylko jeśli jeszcze nie podłączony - { - tmp = FindTraction(Traction->pPoint2, iConnection, nCurrent); - switch (iConnection) - { - case 0: - Traction->Connect(1, tmp, 0); - break; - case 1: - Traction->Connect(1, tmp, 1); - break; - } - if (Traction->hvNext[1]) // jeśli został podłączony - if (Traction->psSection && tmp->psSection) // tylko przęsło z izolatorem może nie - // mieć zasilania, bo ma 2, trzeba - // sprawdzać sąsiednie - if (Traction->psSection != tmp->psSection) - { // to może być albo podłączenie podstacji lub kabiny sekcyjnej do sekcji, albo - // błąd - if (Traction->psSection->bSection && !tmp->psSection->bSection) - { //(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji - tmp->PowerSet(Traction->psSection); // zastąpienie wskazaniem sekcji - } - else if (!Traction->psSection->bSection && tmp->psSection->bSection) - { //(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji - Traction->PowerSet(tmp->psSection); // zastąpienie wskazaniem sekcji - } - else // jeśli obie to sekcje albo obie podstacje, to będzie błąd - ErrorLog("Bad power: at " + - to_string(Traction->pPoint2.x, 2, 6) + " " + - to_string(Traction->pPoint2.y, 2, 6) + " " + - to_string(Traction->pPoint2.z, 2, 6)); - } - } - } - iConnection = 0; // teraz będzie licznikiem końców - for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext) - { // operacje mające na celu wykrywanie bieżni wspólnych i łączenie przęseł naprążania - if (nCurrent->hvTraction->WhereIs()) // oznakowanie przedostatnich przęseł - { // poszukiwanie bieżni wspólnej dla przedostatnich przęseł, również w celu połączenia - // zasilania - // to się nie sprawdza, bo połączyć się mogą dwa niezasilane odcinki jako najbliższe - // sobie - // nCurrent->hvTraction->hvParallel=TractionNearestFind(nCurrent->pCenter,0,nCurrent); - // //szukanie najbliższego przęsła - // trzeba by zliczać końce, a potem wpisać je do tablicy, aby sukcesywnie podłączać do - // zasilaczy - nCurrent->hvTraction->iTries = 5; // oznaczanie końcowych - ++iConnection; - } - if (nCurrent->hvTraction->fResistance[0] == 0.0) - { - nCurrent->hvTraction - ->ResistanceCalc(); // obliczanie przęseł w segmencie z bezpośrednim zasilaniem - // ErrorLog("Section "+nCurrent->hvTraction->asPowerSupplyName+" connected"); //jako - // niby błąd będzie bardziej widoczne - nCurrent->hvTraction->iTries = 0; // nie potrzeba mu szukać zasilania - } - // if (!Traction->hvParallel) //jeszcze utworzyć pętle z bieżni wspólnych - } - int zg = 0; // zgodność kierunku przęseł, tymczasowo iterator do tabeli końców - TGroundNode **nEnds = new TGroundNode *[iConnection]; // końców jest ok. 10 razy mniej niż - // wszystkich przęseł (Quark: 216) - for (nCurrent = nRootOfType[TP_TRACTION]; nCurrent; nCurrent = nCurrent->nNext) - { //łączenie bieżni wspólnych, w tym oznaczanie niepodanych jawnie - Traction = nCurrent->hvTraction; - if (!Traction->asParallel.empty()) // będzie wskaźnik na inne przęsło - if ((Traction->asParallel == "none") || - (Traction->asParallel == "*")) // jeśli nieokreślone - Traction->iLast = - 2; // jakby przedostatni - niech po prostu szuka (iLast już przeliczone) - else if (!Traction->hvParallel) // jeśli jeszcze nie został włączony w kółko - { - nTemp = FindGroundNode(Traction->asParallel, TP_TRACTION); - if (nTemp) - { // o ile zostanie znalezione przęsło o takiej nazwie - if (!nTemp->hvTraction - ->hvParallel) // jeśli tamten jeszcze nie ma wskaźnika bieżni wspólnej - Traction->hvParallel = - nTemp->hvTraction; // wpisać siebie i dalej dać mu wskaźnik zwrotny - else // a jak ma, to albo dołączyć się do kółeczka - Traction->hvParallel = - nTemp->hvTraction->hvParallel; // przjąć dotychczasowy wskaźnik od niego - nTemp->hvTraction->hvParallel = - Traction; // i na koniec ustawienie wskaźnika zwrotnego - } - if (!Traction->hvParallel) - ErrorLog("Missed overhead: " + Traction->asParallel); // logowanie braku - } - if (Traction->iTries > 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; - - for (Current = nRootOfType[TP_EVLAUNCH]; Current; Current = Current->nNext) - { - EventLauncher = Current->EvLaunch; - if (EventLauncher->iCheckMask != 0) - if (EventLauncher->asMemCellName != "none") - { // jeśli jest powiązana komórka pamięci - tmp = FindGroundNode(EventLauncher->asMemCellName, TP_MEMCELL); - if (tmp) - EventLauncher->MemCell = tmp->MemCell; // jeśli znaleziona, dopisać - else - WriteLog("Cannot find Memory Cell for Event Launcher"); - } - else - EventLauncher->MemCell = NULL; - EventLauncher->Event1 = (EventLauncher->asEvent1Name != "none") ? - FindEvent(EventLauncher->asEvent1Name) : - NULL; - EventLauncher->Event2 = (EventLauncher->asEvent2Name != "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 *tmp; - iConnection = -1; - TSubRect *sr; - // najpierw szukamy w okolicznych segmentach - int c = GetColFromX(Point.x); - int r = GetRowFromZ(Point.z); - if ((sr = FastGetSubRect(c, r)) != NULL) // 75% torów jest w tym samym sektorze - if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL) - return tmp; - int i, x, y; - for (i = 1; i < 9; - ++i) // sektory w kolejności odległości, 4 jest tu wystarczające, 9 na wszelki wypadek - { // niemal wszystkie podłączone tory znajdują się w sąsiednich 8 sektorach - x = SectorOrder[i].x; - y = SectorOrder[i].y; - if ((sr = FastGetSubRect(c + y, r + x)) != NULL) - if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL) - return tmp; - if (x) - if ((sr = FastGetSubRect(c + y, r - x)) != NULL) - if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL) - return tmp; - if (y) - if ((sr = FastGetSubRect(c - y, r + x)) != NULL) - if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL) - return tmp; - if ((sr = FastGetSubRect(c - y, r - x)) != NULL) - if ((tmp = sr->FindTrack(&Point, iConnection, Exclude->pTrack)) != NULL) - return tmp; - } - return NULL; -} - -TTraction * TGround::FindTraction(glm::dvec3 const &Point, int &iConnection, TGroundNode *Exclude) -{ // wyszukiwanie innego przęsła kończącego się w (Point) - TTraction *tmp; - iConnection = -1; - TSubRect *sr; - // najpierw szukamy w okolicznych segmentach - int c = GetColFromX(Point.x); - int r = GetRowFromZ(Point.z); - if ((sr = FastGetSubRect(c, r)) != NULL) // większość będzie w tym samym sektorze - if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL) - return tmp; - int i, x, y; - for (i = 1; i < 9; - ++i) // sektory w kolejności odległości, 4 jest tu wystarczające, 9 na wszelki wypadek - { // wszystkie przęsła powinny zostać znajdować się w sąsiednich 8 sektorach - x = SectorOrder[i].x; - y = SectorOrder[i].y; - if ((sr = FastGetSubRect(c + y, r + x)) != NULL) - if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL) - return tmp; - if (x & y) - { - if ((sr = FastGetSubRect(c + y, r - x)) != NULL) - if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL) - return tmp; - if ((sr = FastGetSubRect(c - y, r + x)) != NULL) - if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL) - return tmp; - } - if ((sr = FastGetSubRect(c - y, r - x)) != NULL) - if ((tmp = sr->FindTraction(Point, iConnection, Exclude->hvTraction)) != NULL) - return tmp; - } - return NULL; -}; - -TTraction * TGround::TractionNearestFind(glm::dvec3 &p, int dir, TGroundNode *n) -{ // wyszukanie najbliższego do (p) przęsła o tej samej nazwie sekcji (ale innego niż podłączone) - // oraz zasilanego z kierunku (dir) - TGroundNode *nCurrent, *nBest = NULL; - int i, j, k, zg; - double d, dist = 200.0 * 200.0; //[m] odległość graniczna - // najpierw szukamy w okolicznych segmentach - int c = GetColFromX(n->pCenter.x); - int r = GetRowFromZ(n->pCenter.z); - TSubRect *sr; - for (i = -1; i <= 1; ++i) // przeglądamy 9 najbliższych sektorów - for (j = -1; j <= 1; ++j) // - if ((sr = FastGetSubRect(c + i, r + j)) != NULL) // o ile w ogóle sektor jest - for (nCurrent = sr->nRenderWires; nCurrent; nCurrent = nCurrent->nNext3) - if (nCurrent->iType == TP_TRACTION) - if (nCurrent->hvTraction->psSection == - n->hvTraction->psSection) // jeśli ta sama sekcja - if (nCurrent != n) // ale nie jest tym samym - if (nCurrent->hvTraction != - n->hvTraction - ->hvNext[0]) // ale nie jest bezpośrednio podłączonym - if (nCurrent->hvTraction != n->hvTraction->hvNext[1]) - if (nCurrent->hvTraction->psPower - [k = (glm::dot( - n->hvTraction->vParametric, - nCurrent->hvTraction->vParametric) >= 0 ? - dir ^ 1 : - dir)]) // ma zasilanie z odpowiedniej - // strony - if (nCurrent->hvTraction->fResistance[k] >= - 0.0) //żeby się nie propagowały jakieś ujemne - { // znaleziony kandydat do połączenia - d = glm::length2( p - glm::dvec3{ nCurrent->pCenter } ); // kwadrat odległości środków - if (dist > d) - { // zapamiętanie nowego najbliższego - dist = d; // nowy rekord odległości - nBest = nCurrent; - zg = k; // z którego końca brać wskaźnik - // zasilacza - } - } - if (nBest) // jak znalezione przęsło z zasilaniem, to podłączenie "równoległe" - { - n->hvTraction->ResistanceCalc(dir, nBest->hvTraction->fResistance[zg], - nBest->hvTraction->psPower[zg]); - // 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 : nullptr); -}; - -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 ) ) { - // eventy AddValues trzeba wykonywać natychmiastowo, inaczej kolejka może zgubić jakieś dodawanie - // Ra: kopiowanie wykonania tu jest bez sensu, lepiej by było wydzielić funkcję - // wykonującą eventy i ją wywołać - if( EventConditon( Event ) ) { // teraz mogą być warunki do tych eventów - Event->Params[ 5 ].asMemCell->UpdateValues( - Event->Params[ 0 ].asText, Event->Params[ 1 ].asdouble, - Event->Params[ 2 ].asdouble, Event->iFlags ); - if( Event->Params[ 6 ].asTrack ) { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla - // wszystkich 'dynamic' na danym torze - for( auto dynamic : Event->Params[ 6 ].asTrack->Dynamics ) { - Event->Params[ 5 ].asMemCell->PutCommand( - dynamic->Mechanik, - &Event->Params[ 4 ].nGroundNode->pCenter ); - } - //if (DebugModeFlag) - WriteLog( - "EVENT EXECUTED" + ( Node ? ( " by " + Node->asName ) : "" ) + ": AddValues & Track command ( " - + std::string( Event->Params[ 0 ].asText ) + " " - + std::to_string( Event->Params[ 1 ].asdouble ) + " " - + std::to_string( Event->Params[ 2 ].asdouble ) + " )" ); - } - //else if (DebugModeFlag) - WriteLog( - "EVENT EXECUTED" + ( Node ? ( " by " + Node->asName ) : "" ) + ": AddValues ( " - + std::string( Event->Params[ 0 ].asText ) + " " - + std::to_string( Event->Params[ 1 ].asdouble ) + " " - + std::to_string( Event->Params[ 2 ].asdouble ) + " )" ); - } - // jeśli jest kolejny o takiej samej nazwie, to idzie do kolejki (and if there's no joint event it'll be set to null and processing will end here) - do { - Event = Event->evJoined; - // NOTE: we could've received a new event from joint event above, so we need to check conditions just in case and discard the bad events - // TODO: refactor this arrangement, it's hardly optimal - } while( ( Event != nullptr ) - && ( ( false == Event->bEnabled ) - || ( Event->iQueued > 0 ) ) ); - } - if( Event != nullptr ) { - // standardowe dodanie do kolejki - ++Event->iQueued; // zabezpieczenie przed podwójnym dodaniem do kolejki - WriteLog( "EVENT ADDED TO QUEUE" + ( Node ? ( " by " + Node->asName ) : "" ) + ": " + Event->asName ); - Event->fStartTime = std::abs( Event->fDelay ) + Timer::GetTime(); // czas od uruchomienia scenerii - if( Event->fRandomDelay > 0.0 ) { - // doliczenie losowego czasu opóźnienia - Event->fStartTime += Event->fRandomDelay * Random( 10000 ) * 0.0001; - } - if( QueryRootEvent != nullptr ) { - TEvent::AddToQuery( Event, QueryRootEvent ); - } - else { - QueryRootEvent = Event; - QueryRootEvent->evNext = nullptr; - } - } - } - } - return true; -} - -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 = Random(); - WriteLog("Random integer: " + std::to_string(rprobability) + " / " + std::to_string(e->Params[10].asdouble)); - return (e->Params[10].asdouble > rprobability); - } - else if (e->iFlags & conditional_memcompare) - { // porównanie wartości - if( nullptr == e->Params[9].asMemCell ) { - - ErrorLog( "Event " + e->asName + " trying conditional_memcompare with nonexistent memcell" ); - return true; // though this is technically error, we report success to maintain backward compatibility - } - if (tmpEvent->Params[9].asMemCell->Compare( ( e->Params[ 10 ].asText != nullptr ? e->Params[10].asText : "" ), e->Params[11].asdouble, e->Params[12].asdouble, e->iFlags) ) { - //logowanie spełnionych warunków - LogComment = e->Params[9].asMemCell->Text() + " " + - to_string(e->Params[9].asMemCell->Value1(), 2, 8) + " " + - to_string(tmpEvent->Params[9].asMemCell->Value2(), 2, 8) + - " = "; - if (TestFlag(e->iFlags, conditional_memstring)) - LogComment += std::string(tmpEvent->Params[10].asText); - else - LogComment += "*"; - if (TestFlag(tmpEvent->iFlags, conditional_memval1)) - LogComment += " " + to_string(tmpEvent->Params[11].asdouble, 2, 8); - else - LogComment += " *"; - if (TestFlag(tmpEvent->iFlags, conditional_memval2)) - LogComment += " " + to_string(tmpEvent->Params[12].asdouble, 2, 8); - else - LogComment += " *"; - WriteLog(LogComment); - return true; - } - //else if (Global::iWriteLogEnabled && DebugModeFlag) //zawsze bo to bardzo istotne w debugowaniu scenariuszy - else - { // nie zgadza się, więc sprawdzmy, co - LogComment = e->Params[9].asMemCell->Text() + " " + - to_string(e->Params[9].asMemCell->Value1(), 2, 8) + " " + - to_string(tmpEvent->Params[9].asMemCell->Value2(), 2, 8) + - " != "; - if (TestFlag(e->iFlags, conditional_memstring)) - LogComment += (tmpEvent->Params[10].asText); - else - LogComment += "*"; - if (TestFlag(tmpEvent->iFlags, conditional_memval1)) - LogComment += " " + to_string(tmpEvent->Params[11].asdouble, 2, 8); - else - LogComment += " *"; - if (TestFlag(tmpEvent->iFlags, conditional_memval2)) - LogComment += " " + to_string(tmpEvent->Params[12].asdouble, 2, 8); - else - LogComment += " *"; - WriteLog(LogComment); - } - } - return false; -}; - -bool TGround::CheckQuery() -{ // sprawdzenie kolejki eventów oraz wykonanie tych, którym czas minął - TLocation loc; - int i; - while( ( QueryRootEvent != nullptr ) - && ( QueryRootEvent->fStartTime < Timer::GetTime() ) ) - { // eventy są posortowana wg czasu wykonania - tmpEvent = QueryRootEvent; // wyjęcie eventu z kolejki - if (QueryRootEvent->evJoined) // jeśli jest kolejny o takiej samej nazwie - { // to teraz on będzie następny do wykonania - QueryRootEvent = QueryRootEvent->evJoined; // następny będzie ten doczepiony - QueryRootEvent->evNext = tmpEvent->evNext; // pamiętając o następnym z kolejki - QueryRootEvent->fStartTime = tmpEvent->fStartTime; // czas musi być ten sam, bo nie jest aktualizowany - QueryRootEvent->Activator = tmpEvent->Activator; // pojazd aktywujący - QueryRootEvent->iQueued = 1; - // w sumie można by go dodać normalnie do kolejki, ale trzeba te połączone posortować wg czasu wykonania - } - else // a jak nazwa jest unikalna, to kolejka idzie dalej - QueryRootEvent = QueryRootEvent->evNext; // NULL w skrajnym przypadku - if (tmpEvent->bEnabled) - { // w zasadzie te wyłączone są skanowane i nie powinny się nigdy w kolejce znaleźć - --tmpEvent->iQueued; // teraz moze być ponownie dodany do kolejki - WriteLog( "EVENT LAUNCHED" + ( tmpEvent->Activator ? ( " by " + tmpEvent->Activator->asName ) : "" ) + ": " + tmpEvent->asName ); - switch (tmpEvent->Type) - { - case tp_CopyValues: // skopiowanie wartości z innej komórki - tmpEvent->Params[5].asMemCell->UpdateValues( - tmpEvent->Params[9].asMemCell->Text(), - tmpEvent->Params[9].asMemCell->Value1(), - tmpEvent->Params[9].asMemCell->Value2(), - tmpEvent->iFlags // flagi określają, co ma być skopiowane - ); - // break; //żeby się wysłało do torów i nie było potrzeby na AddValues * 0 0 - case tp_AddValues: // różni się jedną flagą od UpdateValues - case tp_UpdateValues: - if (EventConditon(tmpEvent)) - { // teraz mogą być warunki do tych eventów - if (tmpEvent->Type != tp_CopyValues) // dla CopyValues zrobiło się wcześniej - tmpEvent->Params[5].asMemCell->UpdateValues( - tmpEvent->Params[0].asText, - tmpEvent->Params[1].asdouble, - tmpEvent->Params[2].asdouble, - tmpEvent->iFlags); - if (tmpEvent->Params[6].asTrack) - { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla - // wszystkich 'dynamic' na danym torze - for( auto dynamic : tmpEvent->Params[ 6 ].asTrack->Dynamics ) { - tmpEvent->Params[ 5 ].asMemCell->PutCommand( - dynamic->Mechanik, - &tmpEvent->Params[ 4 ].nGroundNode->pCenter ); - } - //if (DebugModeFlag) - WriteLog("Type: UpdateValues & Track command - " + - tmpEvent->Params[5].asMemCell->Text() + " " + - std::to_string( tmpEvent->Params[ 5 ].asMemCell->Value1() ) + " " + - std::to_string( tmpEvent->Params[ 5 ].asMemCell->Value2() ) ); - } - else //if (DebugModeFlag) - WriteLog("Type: UpdateValues - " + - tmpEvent->Params[5].asMemCell->Text() + " " + - std::to_string( tmpEvent->Params[ 5 ].asMemCell->Value1() ) + " " + - std::to_string( tmpEvent->Params[ 5 ].asMemCell->Value2() ) ); - } - 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: - WriteLog(tmpEvent->asNodeName.c_str()); - 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(); - break; - case -1: - tmpEvent->Params[9].tsTextSound->play(); - 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 != ( ( ( tmpEvent->iFlags & ( conditional_else << i ) ) != 0 ) ) ) { - if( tmpEvent->Params[ i ].asEvent != tmpEvent ) - AddToQuery( tmpEvent->Params[ i ].asEvent, tmpEvent->Activator ); // normalnie dodać - else { - // jeśli ma być rekurencja to musi mieć sensowny okres powtarzania - if( tmpEvent->fDelay >= 5.0 ) { - AddToQuery( tmpEvent, tmpEvent->Activator ); - } - } - } - } - } - if (Global::iMultiplayer) // dajemy znać do serwera o wykonaniu - if ((tmpEvent->iFlags & conditional_anyelse) == - 0) // jednoznaczne tylko, gdy nie było else - { - if (tmpEvent->Activator) - 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 - // TODO: define and recognize individual request types - auto const owner = ( - ( ( tmpEvent->Activator->Mechanik != nullptr ) && ( tmpEvent->Activator->Mechanik->Primary() ) ) ? - tmpEvent->Activator->Mechanik : - tmpEvent->Activator->ctOwner ); - auto const consistbrakelevel = ( - owner != nullptr ? - owner->fReady : - -1.0 ); - auto const collisiondistance = ( - owner != nullptr ? - owner->TrackBlock() : - -1.0 ); - - tmpEvent->Params[ 9 ].asMemCell->UpdateValues( - tmpEvent->Activator->MoverParameters->TypeName, // typ pojazdu - consistbrakelevel, - collisiondistance, - tmpEvent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) ); - - WriteLog( - "whois request (" + to_string( tmpEvent->iFlags ) + ") " - + "[name: " + tmpEvent->Activator->MoverParameters->TypeName + "], " - + "[consist brake level: " + to_string( consistbrakelevel, 2 ) + "], " - + "[obstacle distance: " + to_string( collisiondistance, 2 ) + " m]" ); - } - else { - // jeśli parametry ładunku - tmpEvent->Params[ 9 ].asMemCell->UpdateValues( - tmpEvent->Activator->MoverParameters->LoadType, // nazwa ładunku - tmpEvent->Activator->MoverParameters->Load, // aktualna ilość - tmpEvent->Activator->MoverParameters->MaxLoad, // maksymalna ilość - tmpEvent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) ); - - WriteLog( - "whois request (" + to_string( tmpEvent->iFlags ) + ") " - + "[load type: " + tmpEvent->Activator->MoverParameters->LoadType + "], " - + "[current load: " + to_string( tmpEvent->Activator->MoverParameters->Load, 2 ) + "], " - + "[max load: " + to_string( tmpEvent->Activator->MoverParameters->MaxLoad, 2 ) + "]" ); - } - } - else if (tmpEvent->iFlags & update_memadd) - { // jeśli miejsce docelowe pojazdu - tmpEvent->Params[ 9 ].asMemCell->UpdateValues( - tmpEvent->Activator->asDestination, // adres docelowy - tmpEvent->Activator->DirectionGet(), // kierunek pojazdu względem czoła składu (1=zgodny,-1=przeciwny) - tmpEvent->Activator->MoverParameters->Power, // moc pojazdu silnikowego: 0 dla wagonu - tmpEvent->iFlags & (update_memstring | update_memval1 | update_memval2)); - - WriteLog( - "whois request (" + to_string( tmpEvent->iFlags ) + ") " - + "[destination: " + tmpEvent->Activator->asDestination + "], " - + "[direction: " + to_string( tmpEvent->Activator->DirectionGet() ) + "], " - + "[engine power: " + to_string( tmpEvent->Activator->MoverParameters->Power, 2 ) + "]" ); - } - else if (tmpEvent->Activator->Mechanik) - if (tmpEvent->Activator->Mechanik->Primary()) - { // tylko jeśli ktoś tam siedzi - nie powinno dotyczyć pasażera! - tmpEvent->Params[ 9 ].asMemCell->UpdateValues( - tmpEvent->Activator->Mechanik->TrainName(), - tmpEvent->Activator->Mechanik->StationCount() - tmpEvent->Activator->Mechanik->StationIndex(), // ile przystanków do końca - tmpEvent->Activator->Mechanik->IsStop() ? 1 : - 0, // 1, gdy ma tu zatrzymanie - tmpEvent->iFlags); - WriteLog("Train detected: " + tmpEvent->Activator->Mechanik->TrainName()); - } - break; - } - case tp_LogValues: // zapisanie zawartości komórki pamięci do logu - if (tmpEvent->Params[9].asMemCell) // jeśli była podana nazwa komórki - WriteLog("Memcell \"" + tmpEvent->asNodeName + "\": " + - tmpEvent->Params[9].asMemCell->Text() + " " + - std::to_string(tmpEvent->Params[9].asMemCell->Value1()) + " " + - std::to_string(tmpEvent->Params[9].asMemCell->Value2())); - else // lista wszystkich - for (TGroundNode *Current = nRootOfType[TP_MEMCELL]; Current; - Current = Current->nNext) - WriteLog("Memcell \"" + Current->asName + "\": " + - Current->MemCell->Text() + " " + std::to_string(Current->MemCell->Value1()) + " " + - std::to_string(Current->MemCell->Value2())); - break; - case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource) - if (tmpEvent->Params[9].psPower) - { // na razie takie chamskie ustawienie napięcia zasilania - WriteLog("type: Voltage"); - tmpEvent->Params[9].psPower->VoltageSet(tmpEvent->Params[0].asdouble); - } - case tp_Friction: // zmiana tarcia na scenerii - { // na razie takie chamskie ustawienie napięcia zasilania - WriteLog("type: Friction"); - Global::fFriction = (tmpEvent->Params[0].asdouble); - } - break; - case tp_Message: // wyświetlenie komunikatu - break; - case tp_Lua: - ((lua::eventhandler_t)tmpEvent->Params[0].asPointer)(tmpEvent, tmpEvent->Activator); - break; - } // switch (tmpEvent->Type) - } // if (tmpEvent->bEnabled) - } // while - return true; -} - -void TGround::UpdatePhys(double dt, int iter) -{ // aktualizacja fizyki stałym krokiem: dt=krok czasu [s], dt*iter=czas od ostatnich przeliczeń - for (TGroundNode *Current = nRootOfType[TP_TRACTIONPOWERSOURCE]; Current; - Current = Current->nNext) - Current->psTractionPowerSource->Update(dt * iter); // zerowanie sumy prądów -}; - -bool TGround::Update(double dt, int iter) -{ // aktualizacja animacji krokiem FPS: dt=krok czasu [s], dt*iter=czas od ostatnich przeliczeń - if (dt == 0.0) - { // jeśli załączona jest pauza, to tylko obsłużyć ruch w kabinie trzeba - return true; - } - // Ra: w zasadzie to trzeba by utworzyć oddzielną listę taboru do liczenia fizyki - // na którą by się zapisywały wszystkie pojazdy będące w ruchu - // pojazdy stojące nie potrzebują aktualizacji, chyba że np. ktoś im zmieni nastawę hamulca - // oddzielną listę można by zrobić na pojazdy z napędem, najlepiej posortowaną wg typu napędu - if (iter > 1) // ABu: ponizsze wykonujemy tylko jesli wiecej niz jedna iteracja - { // pierwsza iteracja i wyznaczenie stalych: - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - { // - Current->DynamicObject->MoverParameters->ComputeConstans(); - Current->DynamicObject->CoupleDist(); - Current->DynamicObject->UpdateForce(dt, dt, false); - } - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - Current->DynamicObject->FastUpdate(dt); - // pozostale iteracje - for (int i = 1; i < (iter - 1); ++i) // jeśli iter==5, to wykona się 3 razy - { - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - Current->DynamicObject->UpdateForce(dt, dt, false); - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - Current->DynamicObject->FastUpdate(dt); - } - // ABu 200205: a to robimy tylko raz, bo nie potrzeba więcej - // Winger 180204 - pantografy - double dt1 = dt * iter; // całkowity czas - UpdatePhys(dt1, 1); - TAnimModel::AnimUpdate(dt1); // wykonanie zakolejkowanych animacji - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - { // Ra: zmienić warunek na sprawdzanie pantografów w jednej zmiennej: czy pantografy i czy - // podniesione - if (Current->DynamicObject->MoverParameters->EnginePowerSource.SourceType == - CurrentCollector) - GetTraction(Current->DynamicObject); // poszukiwanie drutu dla pantografów - Current->DynamicObject->UpdateForce(dt, dt1, true); //,true); - } - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - Current->DynamicObject->Update(dt, dt1); // Ra 2015-01: tylko tu przelicza sieć - // trakcyjną - } - else - { // jezeli jest tylko jedna iteracja - UpdatePhys(dt, 1); - TAnimModel::AnimUpdate(dt); // wykonanie zakolejkowanych animacji - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - { - if (Current->DynamicObject->MoverParameters->EnginePowerSource.SourceType == - CurrentCollector) - GetTraction(Current->DynamicObject); - Current->DynamicObject->MoverParameters->ComputeConstans(); - Current->DynamicObject->CoupleDist(); - Current->DynamicObject->UpdateForce(dt, dt, true); //,true); - } - for (TGroundNode *Current = nRootDynamic; Current; Current = Current->nNext) - Current->DynamicObject->Update(dt, dt); // Ra 2015-01: tylko tu przelicza sieć trakcyjną - } - if (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; -}; - -// updates scene lights array -void -TGround::Update_Lights() { - - m_lights.update(); -} - -void -TGround::Update_Hidden() { - - // rednerowanie globalnych (nie za często?) - for( TGroundNode *node = srGlobal.nRenderHidden; node; node = node->nNext3 ) { - node->RenderHidden(); - } - - // render events and sounds from sectors near enough to the viewer - auto const range = 2750.0; // audible range of 100 db sound - int const camerax = static_cast( std::floor( Global::pCameraPosition.x / 1000.0 ) + iNumRects / 2 ); - int const cameraz = static_cast( std::floor( Global::pCameraPosition.z / 1000.0 ) + iNumRects / 2 ); - int const segmentcount = 2 * static_cast( std::ceil( range / 1000.0 ) ); - int const originx = std::max( 0, camerax - segmentcount / 2 ); - int const originz = std::max( 0, cameraz - segmentcount / 2 ); - - for( int column = originx; column <= originx + segmentcount; ++column ) { - for( int row = originz; row <= originz + segmentcount; ++row ) { - - auto &cell = Rects[ column ][ row ]; - - for( int subcellcolumn = 0; subcellcolumn < iNumSubRects; ++subcellcolumn ) { - for( int subcellrow = 0; subcellrow < iNumSubRects; ++subcellrow ) { - auto subcell = cell.FastGetSubRect( subcellcolumn, subcellrow ); - if( subcell == nullptr ) { continue; } - // renderowanie obiektów aktywnych a niewidocznych - for( auto node = subcell->nRenderHidden; node; node = node->nNext3 ) { - node->RenderHidden(); - } - // jeszcze dźwięki pojazdów by się przydały, również niewidocznych - // TODO: move to sound renderer - subcell->RenderSounds(); - } - } - } - } - -} - -// Winger 170204 - szukanie trakcji nad pantografami -bool TGround::GetTraction(TDynamicObject *model) -{ // aktualizacja drutu zasilającego dla każdego pantografu, żeby odczytać napięcie - // jeśli pojazd się nie porusza, to nie ma sensu przeliczać tego więcej niż raz - double fRaParam; // parametr równania parametrycznego odcinka drutu - double fVertical; // odległość w pionie; musi być w zasięgu ruchu "pionowego" pantografu - double fHorizontal; // odległość w bok; powinna być mniejsza niż pół szerokości pantografu - glm::dvec3 vLeft, vUp, vFront, dwys; - glm::dvec3 pant0; - glm::dvec3 vParam; // współczynniki równania parametrycznego drutu - glm::dvec3 vStyk; // punkt przebicia drutu przez płaszczyznę ruchu pantografu - glm::dvec3 vGdzie; // wektor położenia drutu względem pojazdu - vFront = glm::make_vec3(model->VectorFront().getArray()); // wektor normalny dla płaszczyzny ruchu pantografu - vUp = glm::make_vec3(model->VectorUp().getArray()); // wektor pionu pudła (pochylony od pionu na przechyłce) - vLeft = glm::make_vec3(model->VectorLeft().getArray()); // wektor odległości w bok (odchylony od poziomu na przechyłce) - auto const position = model->GetPosition(); // współrzędne środka pojazdu - dwys = glm::dvec3( position.x, position.y, position.z ); - TAnimPant *p; // wskaźnik do obiektu danych pantografu - for (int k = 0; k < model->iAnimType[ANIM_PANTS]; ++k) - { // pętla po pantografach - p = model->pants[k].fParamPants; - if (k ? model->MoverParameters->PantRearUp : model->MoverParameters->PantFrontUp) - { // jeśli pantograf podniesiony - pant0 = dwys + (vLeft * p->vPos.z) + (vUp * p->vPos.y) + (vFront * p->vPos.x); - if (p->hvPowerWire) - { // jeżeli znamy drut z poprzedniego przebiegu - int n = 30; //żeby się nie zapętlił - while (p->hvPowerWire) - { // powtarzane aż do znalezienia odpowiedniego odcinka na liście dwukierunkowej - // obliczamy wyraz wolny równania płaszczyzny (to miejsce nie jest odpowienie) - vParam = p->hvPowerWire->vParametric; // współczynniki równania parametrycznego - fRaParam = -glm::dot(pant0, vFront); - // podstawiamy równanie parametryczne drutu do równania płaszczyzny pantografu - // vFront.x*(t1x+t*vParam.x)+vFront.y*(t1y+t*vParam.y)+vFront.z*(t1z+t*vParam.z)+fRaDist=0; - fRaParam = -(glm::dot(p->hvPowerWire->pPoint1, vFront) + fRaParam) / - glm::dot(vParam, vFront); - if (fRaParam < - -0.001) // histereza rzędu 7cm na 70m typowego przęsła daje 1 promil - p->hvPowerWire = p->hvPowerWire->hvNext[0]; - else if (fRaParam > 1.001) - p->hvPowerWire = p->hvPowerWire->hvNext[1]; - else if (p->hvPowerWire->iLast & 3) - { // dla ostatniego i przedostatniego przęsła wymuszamy szukanie innego - p->hvPowerWire = NULL; // nie to, że nie ma, ale trzeba sprawdzić inne - // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu - break; - } - else if (p->hvPowerWire->hvParallel) - { // jeśli przęsło tworzy bieżnię wspólną, to trzeba sprawdzić pozostałe - p->hvPowerWire = NULL; // nie to, że nie ma, ale trzeba sprawdzić inne - // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu - break; // tymczasowo dla bieżni wspólnych poszukiwanie po całości - } - else - { // jeśli t jest w przedziale, wyznaczyć odległość wzdłuż wektorów vUp i vLeft - vStyk = p->hvPowerWire->pPoint1 + fRaParam * vParam; // punkt styku - // płaszczyzny z drutem - // (dla generatora łuku - // el.) - vGdzie = vStyk - pant0; // wektor - // odległość w pionie musi być w zasięgu ruchu "pionowego" pantografu - fVertical = glm::dot( - vGdzie, vUp); // musi się mieścić w przedziale ruchu pantografu - // odległość w bok powinna być mniejsza niż pół szerokości pantografu - fHorizontal = std::fabs(glm::dot(vGdzie, vLeft)) - - p->fWidth; // to się musi mieścić w przedziale zależnym od - // szerokości pantografu - // jeśli w pionie albo w bok jest za daleko, to dany drut jest nieużyteczny - if (fHorizontal > 0) // 0.635 dla AKP-1 AKP-4E - { // drut wyszedł poza zakres roboczy, ale jeszcze jest nabieżnik - - // pantograf się unosi bez utraty prądu - if (fHorizontal > p->fWidthExtra) // czy wyszedł za nabieżnik - { - p->hvPowerWire = NULL; // dotychczasowy drut nie liczy się - // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu - } - else - { // problem jest, gdy nowy drut jest wyżej, wtedy pantograf odłącza się - // od starego, a na podniesienie do nowego potrzebuje czasu - p->PantTraction = - fVertical + - 0.15 * fHorizontal / p->fWidthExtra; // na razie liniowo na - // nabieżniku, dokładność - // poprawi się później - // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu - } - } - else - { // po wyselekcjonowaniu drutu, przypisać go do toru, żeby nie trzeba było - // szukać - // dla 3 końcowych przęseł sprawdzić wszystkie dostępne przęsła - // bo mogą być umieszczone równolegle nad torem - połączyć w pierścień - // najlepiej, jakby odcinki równoległe były oznaczone we wpisach - // WriteLog("Drut: "+AnsiString(fHorizontal)+" "+AnsiString(fVertical)); - p->PantTraction = fVertical; - // p->fHorizontal=fHorizontal; //zapamiętanie położenia drutu - break; // koniec pętli, aktualny drut pasuje - } - } - if (--n <= 0) // coś za długo to szukanie trwa - p->hvPowerWire = NULL; - } - } - if (!p->hvPowerWire) // else nie, bo mógł zostać wyrzucony - { // poszukiwanie po okolicznych sektorach - int c = GetColFromX(dwys.x) + 1; - int r = GetRowFromZ(dwys.z) + 1; - TSubRect *tmp; - TGroundNode *node; - p->PantTraction = 5.0; // taka za duża wartość - for (int j = r - 2; j <= r; j++) - for (int i = c - 2; i <= c; i++) - { // poszukiwanie po najbliższych sektorach niewiele da przy większym - // zagęszczeniu - tmp = FastGetSubRect(i, j); - if (tmp) - { // dany sektor może nie mieć nic w środku - for (node = tmp->nRenderWires; node; - node = node->nNext3) // następny z grupy - if (node->iType == - TP_TRACTION) // w grupie tej są druty oraz inne linie - { - vParam = - node->hvTraction - ->vParametric; // współczynniki równania parametrycznego - fRaParam = -glm::dot(pant0, vFront); - auto const paramfrontdot = glm::dot( vParam, vFront ); - fRaParam = - -( glm::dot( node->hvTraction->pPoint1, vFront ) + fRaParam ) - / ( paramfrontdot != 0.0 ? paramfrontdot : 0.001 ); // div0 trap - if ((fRaParam >= -0.001) ? (fRaParam <= 1.001) : false) - { // jeśli tylko jest w przedziale, wyznaczyć odległość wzdłuż - // wektorów vUp i vLeft - vStyk = node->hvTraction->pPoint1 + - fRaParam * vParam; // punkt styku płaszczyzny z - // drutem (dla generatora łuku - // el.) - vGdzie = vStyk - pant0; // wektor - fVertical = glm::dot( - 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 = std::fabs(glm::dot(vGdzie, vLeft)) - - p->fWidth; // i do tego jeszcze - // wejdzie pod ślizg - if (fHorizontal <= 0.0) // 0.635 dla AKP-1 AKP-4E - { - p->PantWys = - -1.0; // ujemna liczba oznacza połamanie - p->hvPowerWire = NULL; // bo inaczej się zasila - // w nieskończoność z - // połamanego - // p->fHorizontal=fHorizontal; //zapamiętanie - // położenia drutu - if (model->MoverParameters->EnginePowerSource - .CollectorParameters.CollectorsNo > - 0) // liczba pantografów - --model->MoverParameters->EnginePowerSource - .CollectorParameters - .CollectorsNo; // teraz będzie - // mniejsza - if (DebugModeFlag) - ErrorLog( - "Pant. break: at " + - to_string(pant0.x, 2, 7) + - " " + - to_string(pant0.y, 2, 7) + - " " + - to_string(pant0.z, 2, 7)); - } - } - else if (fVertical < p->PantTraction) // ale niżej, niż - // poprzednio - // znaleziony - { - fHorizontal = - std::fabs(glm::dot(vGdzie, vLeft)) - p->fWidth; - if (fHorizontal <= 0.0) // 0.635 dla AKP-1 AKP-4E - { // to się musi mieścić w przedziale zaleznym od - // szerokości pantografu - p->hvPowerWire = - node->hvTraction; // jakiś znaleziony - p->PantTraction = - fVertical; // zapamiętanie nowej wysokości - // p->fHorizontal=fHorizontal; //zapamiętanie - // położenia drutu - } - else if (fHorizontal < - p->fWidthExtra) // czy zmieścił się w - // zakresie nabieżnika? - { // problem jest, gdy nowy drut jest wyżej, wtedy - // pantograf odłącza się od starego, a na - // podniesienie do nowego potrzebuje czasu - fVertical += - 0.15 * fHorizontal / - p->fWidthExtra; // korekta wysokości o - // nabieżnik - drut nad - // nabieżnikiem jest - // geometrycznie jakby nieco - // wyżej - if (fVertical < - p->PantTraction) // gdy po korekcie jest - // niżej, niż poprzednio - // znaleziony - { // gdyby to wystarczyło, to możemy go uznać - p->hvPowerWire = - node->hvTraction; // może być - p->PantTraction = - fVertical; // na razie liniowo na - // nabieżniku, dokładność - // poprawi się później - // p->fHorizontal=fHorizontal; - // //zapamiętanie położenia drutu - } - } - } - } // warunek na parametr drutu <0;1> - } // pętla po drutach - } // sektor istnieje - } // pętla po sektorach - } // koniec poszukiwania w sektorach - if (!p->hvPowerWire) // jeśli drut nie znaleziony - if (!Global::bLiveTraction) // ale można oszukiwać - model->pants[k].fParamPants->PantTraction = 1.4; // to dajemy coś tam dla picu - } // koniec obsługi podniesionego - else - p->hvPowerWire = NULL; // pantograf opuszczony - } - // if (model->fWahaczeAmpMoverParameters->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; -}; - -#ifdef _WIN32 -//--------------------------------------------------------------------------- -void TGround::Navigate(std::string const &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); -}; -#endif -//-------------------------------- -void TGround::WyslijEvent(const std::string &e, const std::string &d) -{ // Ra: jeszcze do wyczyszczenia -#ifdef _WIN32 - DaneRozkaz r; - r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); - r.iComm = 2; // 2 - event - size_t 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 = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura - cData.cbData = (DWORD)(12 + i + j); // 8+dwa liczniki i dwa zera kończące - cData.lpData = &r; - Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); - CommLog( Now() + " " + std::to_string(r.iComm) + " " + e + " sent" ); -#endif -}; -//--------------------------------------------------------------------------- -void TGround::WyslijUszkodzenia(const std::string &t, char fl) -{ // wysłanie informacji w postaci pojedynczego tekstu -#ifdef _WIN32 - DaneRozkaz r; - r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); - r.iComm = 13; // numer komunikatu - size_t 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 = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura - cData.cbData = (DWORD)(11 + i); // 8+licznik i zero kończące - cData.lpData = &r; - Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); - CommLog( Now() + " " + std::to_string(r.iComm) + " " + t + " sent"); -#endif -}; -//--------------------------------------------------------------------------- -void TGround::WyslijString(const std::string &t, int n) -{ // wysłanie informacji w postaci pojedynczego tekstu -#ifdef _WIN32 - DaneRozkaz r; - r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); - r.iComm = n; // numer komunikatu - size_t i = t.length(); - r.cString[0] = char(i); - strcpy(r.cString + 1, t.c_str()); // z zerem kończącym - COPYDATASTRUCT cData; - cData.dwData = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura - cData.cbData = (DWORD)(10 + i); // 8+licznik i zero kończące - cData.lpData = &r; - Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); - CommLog( Now() + " " + std::to_string(r.iComm) + " " + t + " sent"); -#endif -}; -//--------------------------------------------------------------------------- -void TGround::WyslijWolny(const std::string &t) -{ // Ra: jeszcze do wyczyszczenia -#ifdef _WIN32 - WyslijString(t, 4); // tor wolny -#endif -}; -//-------------------------------- -void TGround::WyslijNamiary(TGroundNode *t) -{ // wysłanie informacji o pojeździe - (float), długość ramki będzie zwiększana w miarę potrzeby -#ifdef _WIN32 - // WriteLog("Wysylam pojazd"); - DaneRozkaz r; - r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); - r.iComm = 7; // 7 - dane pojazdu - int i = 32; - size_t 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 = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura - cData.cbData = (DWORD)(10 + i + j); // 8+licznik i zero kończące - cData.lpData = &r; - // WriteLog("Ramka gotowa"); - Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); - // WriteLog("Ramka poszla!"); - CommLog( Now() + " " + std::to_string(r.iComm) + " " + t->asName + " sent"); -#endif -}; -// -void TGround::WyslijObsadzone() -{ // wysłanie informacji o pojeździe -#ifdef _WIN32 - DaneRozkaz2 r; - r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); - r.iComm = 12; // kod 12 - for (int i=0; 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, "none"); - 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, "none"); - strcpy(r.cString + 64 * i + 48, "none"); - i++; - } - - COPYDATASTRUCT cData; - cData.dwData = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura - cData.cbData = 8 + 1984; // 8+licznik i zero kończące - cData.lpData = &r; - // WriteLog("Ramka gotowa"); - Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); - CommLog( Now() + " " + std::to_string(r.iComm) + " obsadzone" + " sent"); -#endif -} - -//-------------------------------- -void TGround::WyslijParam(int nr, int fl) -{ // wysłanie parametrów symulacji w ramce (nr) z flagami (fl) -#ifdef _WIN32 - DaneRozkaz r; - r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); - 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 = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura - cData.cbData = 12 + i; // 12+rozmiar danych - cData.lpData = &r; - Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); -#endif -}; - -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -void TGround::RadioStop(vector3 pPosition) -{ // zatrzymanie pociągów w okolicy - TGroundNode *node; - TSubRect *tmp; - int c = GetColFromX(pPosition.x); - int r = GetRowFromZ(pPosition.z); - int i, j; - int n = 2 * iNumSubRects; // przeglądanie czołgowe okolicznych torów w kwadracie 4km×4km - for (j = r - n; j <= r + n; j++) - for (i = c - n; i <= c + n; i++) - if ((tmp = FastGetSubRect(i, j)) != NULL) - for (node = tmp->nRootNode; node != NULL; node = node->nNext2) - if (node->iType == TP_TRACK) - node->pTrack->RadioStop(); // przekazanie do każdego toru w każdym segmencie -}; - -TDynamicObject * TGround::DynamicNearest(vector3 pPosition, double distance, bool mech) -{ // wyszukanie pojazdu najbliższego względem (pPosition) - TGroundNode *node; - TSubRect *tmp; - TDynamicObject *dyn = NULL; - int c = GetColFromX(pPosition.x); - int r = GetRowFromZ(pPosition.z); - int i, j; - double sqm = distance * distance, sqd; // maksymalny promien poszukiwań do kwadratu - for (j = r - 1; j <= r + 1; j++) // plus dwa zewnętrzne sektory, łącznie 9 - for (i = c - 1; i <= c + 1; i++) - if ((tmp = FastGetSubRect(i, j)) != NULL) - for (node = tmp->nRootNode; node; node = node->nNext2) // następny z sektora - if (node->iType == TP_TRACK) // Ra: przebudować na użycie tabeli torów? - for( auto dynamic : node->pTrack->Dynamics ) { - if( mech ? ( dynamic->Mechanik != nullptr ) : true ) { - // czy ma mieć obsadę - if( ( sqd = SquareMagnitude( dynamic->GetPosition() - pPosition ) ) < sqm ) { - sqm = sqd; // nowa odległość - dyn = dynamic; // nowy lider - } - } - } - return dyn; -}; -TDynamicObject * TGround::CouplerNearest(vector3 pPosition, double distance, bool mech) -{ // wyszukanie pojazdu, którego sprzęg jest najbliżej względem (pPosition) - TGroundNode *node; - TSubRect *tmp; - TDynamicObject *dyn = NULL; - int c = GetColFromX(pPosition.x); - int r = GetRowFromZ(pPosition.z); - int i, j; - double sqm = distance * distance, sqd; // maksymalny promien poszukiwań do kwadratu - for (j = r - 1; j <= r + 1; j++) // plus dwa zewnętrzne sektory, łącznie 9 - for (i = c - 1; i <= c + 1; i++) - if ((tmp = FastGetSubRect(i, j)) != NULL) - for (node = tmp->nRootNode; node; node = node->nNext2) // następny z sektora - if (node->iType == TP_TRACK) // Ra: przebudować na użycie tabeli torów? - for( auto dynamic : node->pTrack->Dynamics ) { - if( mech ? ( dynamic->Mechanik != nullptr ) : true ) { - // czy ma mieć obsadę - if( ( sqd = SquareMagnitude( dynamic->HeadPosition() - pPosition ) ) < sqm ) { - sqm = sqd; // nowa odległość - dyn = dynamic; // nowy lider - } - if( ( sqd = SquareMagnitude( dynamic->RearPosition() - pPosition ) ) < sqm ) { - sqm = sqd; // nowa odległość - dyn = dynamic; // nowy lider - } - } - } - return dyn; -}; -//--------------------------------------------------------------------------- -void TGround::DynamicRemove(TDynamicObject *dyn) -{ // Ra: usunięcie pojazdów ze scenerii (gdy dojadą na koniec i nie sa potrzebne) - TDynamicObject *d = dyn->Prev(); - if (d) // jeśli coś jest z przodu - DynamicRemove(d); // zaczynamy od tego z przodu - else - { // jeśli mamy już tego na początku - TGroundNode **n, *node; - d = dyn; // od pierwszego - while (d) - { - if (d->MyTrack) - d->MyTrack->RemoveDynamicObject(d); // usunięcie z toru o ile nie usunięty - n = &nRootDynamic; // lista pojazdów od początku - // node=NULL; //nie znalezione - while (*n ? (*n)->DynamicObject != d : false) - { // usuwanie z listy pojazdów - n = &((*n)->nNext); // sprawdzenie kolejnego pojazdu na liście - } - if ((*n)->DynamicObject == d) - { // jeśli znaleziony - node = (*n); // zapamiętanie węzła, aby go usunąć - (*n) = node->nNext; // pominięcie na liście - Global::TrainDelete(d); - // remove potential entries in the light array - m_lights.remove( d ); - d = d->Next(); // przejście do kolejnego pojazdu, póki jeszcze jest - delete node; // usuwanie fizyczne z pamięci - } - else - d = NULL; // coś nie tak! - } - } -}; - -//--------------------------------------------------------------------------- - -void TGround::TerrainRead(std::string const &f){ - // Ra: wczytanie trójkątów terenu z pliku E3D -}; - -//--------------------------------------------------------------------------- - -void TGround::TerrainWrite() -{ // Ra: zapisywanie trójkątów terenu do pliku E3D - // TODO: re-implement -/* - if (Global::pTerrainCompact->TerrainLoaded()) - return; // jeśli zostało wczytane, to nie ma co dalej robić - if (Global::asTerrainModel.empty()) - return; - // Trójkąty są zapisywane kwadratami kilometrowymi. - // Kwadrat 500500 jest na środku (od 0.0 do 1000.0 na OX oraz OZ). - // Ewentualnie w numerowaniu kwadratów uwzględnic wpis //$g. - // Trójkąty są grupowane w submodele wg tekstury. - // Triangle_strip oraz triangle_fan są rozpisywane na pojedyncze trójkąty, - // chyba że dla danej tekstury wychodzi tylko jeden submodel. - TModel3d *m = new TModel3d(); // wirtualny model roboczy z oddzielnymi submodelami - TSubModel *sk; // wskaźnik roboczy na submodel kwadratu - // Zliczamy kwadraty z trójkątami, ilość tekstur oraz wierzchołków. - // Ilość kwadratów i ilość tekstur określi ilość submodeli. - // int sub=0; //całkowita ilość submodeli - // int ver=0; //całkowita ilość wierzchołków - int i, j, k; // indeksy w pętli - TGroundNode *Current; - basic_vertex *ver; // trójkąty - TSubModel::iInstance = 0; // pozycja w tabeli wierzchołków liczona narastająco - for (i = 0; i < iNumRects; ++i) // pętla po wszystkich kwadratach kilometrowych - for (j = 0; j < iNumRects; ++j) - if (Rects[i][j].iNodeCount) - { // o ile są jakieś trójkąty w środku - sk = new TSubModel(); // nowy submodel dla kawadratu - // numer kwadratu XXXZZZ, przy czym X jest ujemne - XXX rośnie na wschód, ZZZ rośnie - // na północ - sk->NameSet( std::to_string(1000 * (500 + i - iNumRects / 2) + (500 + j - iNumRects / 2)).c_str() ); // nazwa=numer kwadratu - m->AddTo(NULL, sk); // dodanie submodelu dla kwadratu - for (Current = Rects[i][j].nRootNode; Current; Current = Current->nNext2) - if (Current->TextureID) - switch (Current->iType) - { // pętla po trójkątach - zliczanie wierzchołków, dodaje submodel dla - // każdej tekstury - case GL_TRIANGLES: - Current->iVboPtr = sk->TriangleAdd( - m, Current->TextureID, - Current->iNumVerts); // zwiększenie ilości trójkątów w submodelu - m->iNumVerts += - Current->iNumVerts; // zwiększenie całkowitej ilości wierzchołków - break; - case GL_TRIANGLE_STRIP: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty - break; - case GL_TRIANGLE_FAN: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty - break; - } - for (Current = Rects[i][j].nRootNode; Current; Current = Current->nNext2) - if (Current->TextureID) - switch (Current->iType) - { // pętla po trójkątach - dopisywanie wierzchołków - case GL_TRIANGLES: - // //wskaźnik na początek - ver = sk->TrianglePtr(Current->TextureID, Current->iVboPtr, - Current->Ambient, Current->Diffuse, - Current->Specular); // wskaźnik na początek - Current->iVboPtr = -1; // bo to było tymczasowo używane - for (k = 0; k < Current->iNumVerts; ++k) - { // przepisanie współrzędnych - ver[ k ].position = Current->Vertices[ k ].position; - ver[ k ].normal = Current->Vertices[ k ].normal; - ver[ k ].texture = Current->Vertices[ k ].texture; - } - break; - case GL_TRIANGLE_STRIP: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty - break; - case GL_TRIANGLE_FAN: // na razie nie, bo trzeba przerabiać na pojedyncze trójkąty - break; - } - } - m->SaveToBinFile(strdup(("models/" + Global::asTerrainModel).c_str())); -*/ -}; - -//--------------------------------------------------------------------------- - -void TGround::TrackBusyList() -{ // wysłanie informacji o wszystkich zajętych odcinkach - TGroundNode *Current; - for (Current = nRootOfType[TP_TRACK]; Current; Current = Current->nNext) - if (!Current->asName.empty()) // musi być nazwa - if( false == Current->pTrack->Dynamics.empty() ) - 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 std::string t) -{ // wysłanie informacji o odcinku izolowanym (t) - // Ra 2014-06: do wyszukania użyć drzewka nazw - TIsolated *Current; - for (Current = TIsolated::Root(); Current; Current = Current->Next()) - if (Current->asName == t) // wyszukiwanie odcinka o nazwie (t) - if (Current->Busy()) // sprawdź zajetość - { - 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) - TGroundNode *node; - int n = 2 * iNumSubRects; //(2*==2km) promień wyświetlanej mapy w sektorach - int c = GetColFromX(gdzie.x); // sektory wg dotychczasowej pozycji kamery - int r = GetRowFromZ(gdzie.z); - TSubRect *tmp; - int i, j; - // renderowanie czołgowe dla obiektów aktywnych a niewidocznych - for (j = r - n; j <= r + n; j++) - for (i = c - n; i <= c + n; i++) - if ((tmp = FastGetSubRect(i, j)) != NULL) - { // tylko dźwięki interesują - for (node = tmp->nRenderHidden; node; node = node->nNext3) - node->RenderHidden(); - tmp->RenderSounds(); // dźwięki pojazdów by się przydało wyłączyć - } -}; -//--------------------------------------------------------------------------- diff --git a/Ground.h b/Ground.h deleted file mode 100644 index 2dc5cc1a..00000000 --- a/Ground.h +++ /dev/null @@ -1,352 +0,0 @@ -/* -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/. -*/ - -#pragma once - -#include -#include "GL/glew.h" -#include "openglgeometrybank.h" -#include "VBO.h" -#include "Classes.h" -#include "ResourceManager.h" -#include "material.h" -#include "dumb3d.h" -#include "Float3d.h" -#include "Names.h" -#include "lightarray.h" -#include "lua.h" - -typedef int TGroundNodeType; -// Ra: zmniejszone liczby, aby zrobić tabelkę i zoptymalizować wyszukiwanie -const int TP_MODEL = 10; -/* -const int TP_MESH = 11; // Ra: specjalny obiekt grupujący siatki dla tekstury -const int TP_DUMMYTRACK = 12; // Ra: zdublowanie obiektu toru dla rozdzielenia tekstur -*/ -const int TP_TERRAIN = 13; // Ra: specjalny model dla terenu -const int TP_DYNAMIC = 14; -const int TP_SOUND = 15; -const int TP_TRACK = 16; -/* -const int TP_GEOMETRY=17; -*/ -const int TP_MEMCELL = 18; -const int TP_EVLAUNCH = 19; // MC -const int TP_TRACTION = 20; -const int TP_TRACTIONPOWERSOURCE = 21; // MC -/* -const int TP_ISOLATED=22; //Ra -*/ -const int TP_SUBMODEL = 22; // Ra: submodele terenu -const int TP_LAST = 25; // rozmiar tablicy - -struct DaneRozkaz -{ // struktura komunikacji z EU07.EXE - int iSygn; // sygnatura 'EU07' - int iComm; // rozkaz/status (kod ramki) - union - { - float fPar[62]; - int iPar[62]; - char cString[248]; // upakowane stringi - }; -}; - -struct DaneRozkaz2 -{ // struktura komunikacji z EU07.EXE - int iSygn; // sygnatura 'EU07' - int iComm; // rozkaz/status (kod ramki) - union - { - float fPar[496]; - int iPar[496]; - char cString[1984]; // upakowane stringi - }; -}; - -struct TGroundVertex -{ - glm::dvec3 position; - glm::vec3 normal; - glm::vec2 texture; - - void HalfSet(const TGroundVertex &v1, const TGroundVertex &v2) - { // wyliczenie współrzędnych i mapowania punktu na środku odcinka v1<->v2 - interpolate_( v1, v2, 0.5 ); - } - void SetByX(const TGroundVertex &v1, const TGroundVertex &v2, double x) - { // wyliczenie współrzędnych i mapowania punktu na odcinku v1<->v2 - interpolate_( v1, v2, ( x - v1.position.x ) / ( v2.position.x - v1.position.x ) ); - } - void SetByZ(const TGroundVertex &v1, const TGroundVertex &v2, double z) - { // wyliczenie współrzędnych i mapowania punktu na odcinku v1<->v2 - interpolate_( v1, v2, ( z - v1.position.z ) / ( v2.position.z - v1.position.z ) ); - } - void interpolate_( const TGroundVertex &v1, const TGroundVertex &v2, double factor ) { - position = interpolate( v1.position, v2.position, factor ); - normal = interpolate( v1.normal, v2.normal, static_cast( factor ) ); - texture = interpolate( v1.texture, v2.texture, static_cast( factor ) ); - } -}; - -// ground node holding single, unique piece of 3d geometry. TBD, TODO: unify this with basic 3d model node -struct piece_node { - std::vector vertices; - geometry_handle geometry { 0,0 }; // geometry prepared for drawing -}; - -// obiekt scenerii -class TGroundNode { - - friend class opengl_renderer; - -public: - TGroundNode *nNext; // lista wszystkich w scenerii, ostatni na początku - TGroundNode *nNext2; // lista obiektów w sektorze - TGroundNode *nNext3; // lista obiektów renderowanych we wspólnym cyklu - std::string asName; // nazwa (nie zawsze ma znaczenie) - TGroundNodeType iType; // typ obiektu - union - { // Ra: wskażniki zależne od typu - zrobić klasy dziedziczone zamiast - void *Pointer; // do przypisywania NULL - TSubModel *smTerrain; // modele terenu (kwadratow kilometrowych) - TAnimModel *Model; // model z animacjami - TDynamicObject *DynamicObject; // pojazd - piece_node *Piece; // non-instanced piece of geometry - TTrack *pTrack; // trajektoria ruchu - TMemCell *MemCell; // komórka pamięci - TEventLauncher *EvLaunch; // wyzwalacz zdarzeń - TTraction *hvTraction; // drut zasilający - TTractionPowerSource *psTractionPowerSource; // zasilanie drutu (zaniedbane w sceneriach) - sound *tsStaticSound; // dźwięk przestrzenny - TGroundNode *nNode; // obiekt renderujący grupowo ma tu wskaźnik na listę obiektów - }; - Math3D::vector3 pCenter; // współrzędne środka do przydzielenia sektora - float m_radius { 0.0f }; // bounding radius of geometry stored in the node. TODO: reuse bounding_area struct for radius and center - glm::dvec3 m_rootposition; // position of the ground (sub)rectangle holding the node, in the 3d world - // visualization-related data - // TODO: wrap these in a struct, when cleaning objects up - double fSquareMinRadius; // kwadrat widoczności od - double fSquareRadius; // kwadrat widoczności do - union - { - int iNumVerts; // dla trójkątów - int iNumPts; // dla linii - int iCount; // dla terenu - }; - int iFlags; // tryb przezroczystości: 0x10-nieprz.,0x20-przezroczysty,0x30-mieszany - material_handle m_material; // główna (jedna) tekstura obiektu - glm::vec3 - Ambient{ 1.0f, 1.0f, 1.0f }, - Diffuse{ 1.0f, 1.0f, 1.0f }, - Specular{ 1.0f, 1.0f, 1.0f }; // oświetlenie - double fLineThickness; // McZapkie-120702: grubosc linii - bool bVisible; - - TGroundNode(); - TGroundNode(TGroundNodeType t); - ~TGroundNode(); - - void InitNormals(); - void RenderHidden(); // obsługa dźwięków i wyzwalaczy zdarzeń -}; - -struct bounding_area { - - glm::vec3 center; // mid point of the rectangle - float radius { 0.0f }; // radius of the bounding sphere -}; - -class TSubRect : /*public Resource,*/ public CMesh -{ // sektor składowy kwadratu kilometrowego - public: - bounding_area m_area; - unsigned int m_framestamp { 0 }; // id of last rendered gfx frame - int iTracks = 0; // ilość torów w (tTracks) - TTrack **tTracks = nullptr; // tory do renderowania pojazdów - protected: - TTrack *tTrackAnim = nullptr; // obiekty do przeliczenia animacji - public: - TGroundNode *nRootNode = nullptr; // wszystkie obiekty w sektorze, z wyjątkiem renderujących i pojazdów (nNext2) - TGroundNode *nRenderHidden = nullptr; // lista obiektów niewidocznych, "renderowanych" również z tyłu (nNext3) - TGroundNode *nRenderRect = nullptr; // z poziomu sektora - nieprzezroczyste (nNext3) - TGroundNode *nRenderRectAlpha = nullptr; // z poziomu sektora - przezroczyste (nNext3) - TGroundNode *nRenderWires = nullptr; // z poziomu sektora - druty i inne linie (nNext3) - TGroundNode *nRender = nullptr; // indywidualnie - nieprzezroczyste (nNext3) - TGroundNode *nRenderMixed = nullptr; // indywidualnie - nieprzezroczyste i przezroczyste (nNext3) - TGroundNode *nRenderAlpha = nullptr; // indywidualnie - przezroczyste (nNext3) -#ifdef EU07_SCENERY_EDITOR - std::deque< TGroundNode* > m_memcells; // collection of memcells present in the sector -#endif - int iNodeCount = 0; // licznik obiektów, do pomijania pustych sektorów - public: - void LoadNodes(); // utworzenie VBO sektora - public: - virtual ~TSubRect(); - virtual void NodeAdd(TGroundNode *Node); // dodanie obiektu do sektora na etapie rozdzielania na sektory - void Sort(); // optymalizacja obiektów w sektorze (sortowanie wg tekstur) - TTrack * FindTrack(vector3 *Point, int &iConnection, TTrack *Exclude); - TTraction * FindTraction(glm::dvec3 const &Point, int &iConnection, TTraction *Exclude); - bool RaTrackAnimAdd(TTrack *t); // zgłoszenie toru do animacji - void RaAnimate( unsigned int const Framestamp ); // przeliczenie animacji torów - void RenderSounds(); // dźwięki pojazdów z niewidocznych sektorów -}; - -// Ra: trzeba sprawdzić wydajność siatki -const int iNumSubRects = 5; // na ile dzielimy kilometr -const int iNumRects = 500; -const int iTotalNumSubRects = iNumRects * iNumSubRects; -const double fHalfTotalNumSubRects = iTotalNumSubRects / 2.0; -const double fSubRectSize = 1000.0 / iNumSubRects; -const double fRectSize = fSubRectSize * iNumSubRects; - -class TGroundRect : public TSubRect -{ // kwadrat kilometrowy - // obiekty o niewielkiej ilości wierzchołków będą renderowane stąd - // Ra: 2012-02 doszły submodele terenu - friend class opengl_renderer; - -private: - TSubRect *pSubRects { nullptr }; - - void Init(); - -public: - virtual ~TGroundRect(); - // pobranie wskaźnika do małego kwadratu, utworzenie jeśli trzeba - TSubRect * SafeGetSubRect(int iCol, int iRow) { - if( !pSubRects ) { - // utworzenie małych kwadratów - Init(); - } - return pSubRects + iRow * iNumSubRects + iCol; // zwrócenie właściwego - }; - // pobranie wskaźnika do małego kwadratu, bez tworzenia jeśli nie ma - TSubRect *FastGetSubRect(int iCol, int iRow) const { - return ( - pSubRects ? - pSubRects + iRow * iNumSubRects + iCol : - nullptr); }; - void NodeAdd( TGroundNode *Node ); // dodanie obiektu do sektora na etapie rozdzielania na sektory - // compares two provided nodes, returns true if their content can be merged - bool mergeable( TGroundNode const &Left, TGroundNode const &Right ); - // optymalizacja obiektów w sektorach - void Optimize() { - if( pSubRects ) { - for( int i = iNumSubRects * iNumSubRects - 1; i >= 0; --i ) { - // optymalizacja obiektów w sektorach - pSubRects[ i ].Sort(); } } }; - - TGroundNode *nTerrain { nullptr }; // model terenu z E3D - użyć nRootMesh? -}; - -class TGround -{ - friend class opengl_renderer; - - TGroundRect Rects[ iNumRects ][ iNumRects ]; // mapa kwadratów kilometrowych - TSubRect srGlobal; // zawiera obiekty globalne (na razie wyzwalacze czasowe) - TGroundNode *nRootDynamic = nullptr; // lista pojazdów - TGroundNode *nRootOfType[ TP_LAST ]; // tablica grupująca obiekty, przyspiesza szukanie - TEvent *RootEvent = nullptr; // lista zdarzeń - TEvent *QueryRootEvent = nullptr, - *tmpEvent = nullptr; - typedef std::unordered_map event_map; - event_map m_eventmap; - TNames m_trackmap; - light_array m_lights; // collection of dynamic light sources present in the scene - lua m_lua; - - vector3 pOrigin; - vector3 aRotate; - bool bInitDone = false; - - private: // metody prywatne - bool EventConditon(TEvent *e); - - public: - bool bDynamicRemove = false; // czy uruchomić procedurę usuwania pojazdów - - TGround(); - ~TGround(); - void Free(); - bool Init( std::string File ); - void FirstInit(); - void InitTracks(); - void InitTraction(); - bool InitEvents(); - bool InitLaunchers(); - TTrack * FindTrack(vector3 Point, int &iConnection, TGroundNode *Exclude); - TTraction * FindTraction(glm::dvec3 const &Point, int &iConnection, TGroundNode *Exclude); - TTraction * TractionNearestFind(glm::dvec3 &p, int dir, TGroundNode *n); - TGroundNode * AddGroundNode(cParser *parser); - void UpdatePhys(double dt, int iter); // aktualizacja fizyki stałym krokiem - bool Update(double dt, int iter); // aktualizacja przesunięć zgodna z FPS - void Update_Lights(); // updates scene lights array - void Update_Hidden(); // updates invisible elements of the scene - bool AddToQuery(TEvent *Event, TDynamicObject *Node); - bool GetTraction(TDynamicObject *model); - bool CheckQuery(); - TGroundNode * DynamicFindAny(std::string const &Name); - TGroundNode * DynamicFind(std::string const &Name); - void DynamicList(bool all = false); - TGroundNode * FindGroundNode(std::string asNameToFind, TGroundNodeType iNodeType); - TGroundRect * GetRect( double x, double z ); - TSubRect * GetSubRect( int iCol, int iRow ); - inline - TSubRect * GetSubRect(double x, double z) { - return GetSubRect(GetColFromX(x), GetRowFromZ(z)); }; - TSubRect * FastGetSubRect( int iCol, int iRow ); - inline - TSubRect * FastGetSubRect( double x, double z ) { - return FastGetSubRect( GetColFromX( x ), GetRowFromZ( z ) ); }; - inline - int GetRowFromZ(double z) { - return (int)(z / fSubRectSize + fHalfTotalNumSubRects); }; - inline - int GetColFromX(double x) { - return (int)(x / fSubRectSize + fHalfTotalNumSubRects); }; - TEvent * FindEvent(const std::string &asEventName); - TEvent * FindEventScan(const std::string &asEventName); - void TrackJoin(TGroundNode *Current); - - private: - // convert tp_terrain model to a series of triangle nodes - void convert_terrain( TGroundNode const *Terrain ); - void convert_terrain( TSubModel const *Submodel ); - void RaTriangleDivider(TGroundNode *node); -#ifdef _WIN32 - void Navigate(std::string const &ClassName, UINT Msg, WPARAM wParam, LPARAM lParam); -#endif - - public: - void WyslijEvent(const std::string &e, const std::string &d); - void WyslijString(const std::string &t, int n); - void WyslijWolny(const std::string &t); - void WyslijNamiary(TGroundNode *t); - void WyslijParam(int nr, int fl); - void WyslijUszkodzenia(const std::string &t, char fl); - void WyslijObsadzone(); // -> skladanie wielu pojazdow - void RadioStop(vector3 pPosition); - TDynamicObject * DynamicNearest(vector3 pPosition, double distance = 20.0, - bool mech = false); - TDynamicObject * CouplerNearest(vector3 pPosition, double distance = 20.0, - bool mech = false); - void DynamicRemove(TDynamicObject *dyn); - void TerrainRead(std::string const &f); - void TerrainWrite(); - void TrackBusyList(); - void IsolatedBusyList(); - void IsolatedBusy(const std::string t); - void Silence(vector3 gdzie); - - void add_event(TEvent *event); -}; - -//--------------------------------------------------------------------------- diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index c53b59f3..2d84007d 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -706,6 +706,7 @@ public: headlight_upper = 0x04, headlight_right = 0x10, redmarker_right = 0x20, + rearendsignals = 0x40 }; int ScndInMain{ 0 }; /*zaleznosc bocznika od nastawnika*/ bool MBrake = false; /*Czy jest hamulec reczny*/ @@ -778,6 +779,7 @@ public: double Ftmax = 0.0; /*- dla lokomotyw z silnikami indukcyjnymi -*/ double eimc[26]; + bool EIMCLogForce; // static std::vector const eimc_labels; /*-dla wagonow*/ double MaxLoad = 0.0; /*masa w T lub ilosc w sztukach - ladownosc*/ @@ -820,6 +822,7 @@ public: double AccN = 0.0; //przyspieszenie normalne w [m/s^2] double AccV = 0.0; double nrot = 0.0; + double WheelFlat = 0.0; /*! rotacja kol [obr/s]*/ double EnginePower = 0.0; /*! chwilowa moc silnikow*/ double dL = 0.0; double Fb = 0.0; double Ff = 0.0; /*przesuniecie, sila hamowania i tarcia*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 418b20de..a0c3419f 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -3455,7 +3455,7 @@ void TMoverParameters::UpdatePipePressure(double dt) temp = 0.0; // odetnij else temp = 1.0; // połącz - Pipe->Flow(temp * Hamulec->GetPF(temp * PipePress, dt, Vel) + GetDVc(dt)); + Pipe->Flow( temp * Hamulec->GetPF( temp * PipePress, dt, Vel ) + GetDVc( dt ) ); if (ASBType == 128) Hamulec->ASB(int(SlippingWheels)); @@ -3751,14 +3751,23 @@ void TMoverParameters::ComputeTotalForce(double dt, double dt1, bool FullVer) Sign(nrot * M_PI * WheelDiameter - V) * Adhesive(RunningTrack.friction) * TotalMassxg, dt, nrot); - Fwheels = Sign(nrot * M_PI * WheelDiameter - V) * TotalMassxg * Adhesive(RunningTrack.friction); + Fwheels = Sign(temp_nrot * M_PI * WheelDiameter - V) * TotalMassxg * Adhesive(RunningTrack.friction); if (Fwheels*Sign(V)>0) { - FTrain = Fwheels - Fb; + FTrain = Fwheels + Fb*Sign(V); + } + else if (FTrain*Sign(V)>0) + { + Fb = FTrain*Sign(V) - Fwheels*Sign(V); } else { - Fb = FTrain - Fwheels; + Fb = -Fwheels*Sign(V); + FTrain = 0; + } + if (nrot < 0.1) + { + WheelFlat = sqrt(sqr(WheelFlat) + abs(Fwheels) / NAxles*Vel*0.000002); } if (Sign(nrot * M_PI * WheelDiameter - V)*Sign(temp_nrot * M_PI * WheelDiameter - V) < 0) { @@ -4648,7 +4657,7 @@ double TMoverParameters::TractionForce(double dt) { PosRatio = -Sign(V) * DirAbsolute * eimv[eimv_Fr] / (eimc[eimc_p_Fh] * - Max0R(dtrans / MaxBrakePress[0], AnPos) /*dizel_fill*/); + Max0R(Max0R(dtrans,0.01) / MaxBrakePress[0], AnPos) /*dizel_fill*/); } else PosRatio = 0; @@ -4688,22 +4697,14 @@ double TMoverParameters::TractionForce(double dt) dmoment = eimv[eimv_Fful]; // NOTE: the commands to operate the sandbox are likely to conflict with other similar ai decisions // TODO: gather these in single place so they can be resolved together - if( ( std::abs( ( PosRatio + 9.66 * dizel_fill ) * dmoment * 100 ) > 0.95 * Adhesive( RunningTrack.friction ) * TotalMassxg ) ) { - PosRatio = 0; - tmp = 4; - } // przeciwposlizg - if( ( std::abs( ( PosRatio + 9.80 * dizel_fill ) * dmoment * 100 ) > 0.95 * Adhesive( RunningTrack.friction ) * TotalMassxg ) ) { - PosRatio = 0; - tmp = 9; - } // przeciwposlizg if( ( SlippingWheels ) ) { PosRatio = 0; tmp = 9; - Sandbox( true, range::local ); + Sandbox( true, range::unit ); } // przeciwposlizg else { // switch sandbox off - Sandbox( false, range::local ); + Sandbox( false, range::unit ); } dizel_fill += Max0R(Min0R(PosRatio - dizel_fill, 0.1), -0.1) * 2 * @@ -4740,6 +4741,13 @@ double TMoverParameters::TractionForce(double dt) -Sign(V) * (DirAbsolute)*std::min( eimc[eimc_p_Ph] * 3.6 / (Vel != 0.0 ? Vel : 0.001), std::min(-eimc[eimc_p_Fh] * dizel_fill, eimv[eimv_FMAXMAX])); + double pr = dizel_fill; + if (EIMCLogForce) + pr = -log(1 - 4 * pr) / log(5); + eimv[eimv_Fr] = + -Sign(V) * (DirAbsolute)*std::min( + eimc[eimc_p_Ph] * 3.6 / (Vel != 0.0 ? Vel : 0.001), + std::min(-eimc[eimc_p_Fh] * pr, eimv[eimv_FMAXMAX])); //*Min0R(1,(Vel-eimc[eimc_p_Vh0])/(eimc[eimc_p_Vh1]-eimc[eimc_p_Vh0])) } else @@ -4751,9 +4759,13 @@ double TMoverParameters::TractionForce(double dt) eimv[eimv_Fmax] = eimv[eimv_Fful] * dizel_fill; // else // eimv[eimv_Fmax]:=Min0R(eimc[eimc_p_F0]*dizel_fill,eimv[eimv_Fful]); + double pr = dizel_fill; + if (EIMCLogForce) + pr = log(1 + 4 * pr) / log(5); + eimv[eimv_Fr] = eimv[eimv_Fful] * pr; } - eimv[eimv_ks] = eimv[eimv_Fmax] / eimv[eimv_FMAXMAX]; + eimv[eimv_ks] = eimv[eimv_Fr] / eimv[eimv_FMAXMAX]; eimv[eimv_df] = eimv[eimv_ks] * eimc[eimc_s_dfmax]; eimv[eimv_fp] = DirAbsolute * enrot * eimc[eimc_s_p] + eimv[eimv_df]; // do przemyslenia dzialanie pp z tmpV @@ -4924,7 +4936,7 @@ double TMoverParameters::v2n(void) n = V / (M_PI * WheelDiameter); // predkosc obrotowa wynikajaca z liniowej [obr/s] deltan = n - nrot; //"pochodna" prędkości obrotowej if (SlippingWheels) - if (std::abs(deltan) < 0.01) + if (std::abs(deltan) < 0.001) SlippingWheels = false; // wygaszenie poslizgu if (SlippingWheels) // nie ma zwiazku z predkoscia liniowa V { // McZapkie-221103: uszkodzenia kol podczas poslizgu @@ -6923,7 +6935,7 @@ void TMoverParameters::LoadFIZ_Brake( std::string const &line ) { } if( true == extract_value( AirLeakRate, "AirLeakRate", line, "" ) ) { - // the parameter is provided in form of a multiplier, where 1.0 means the default rate of 0.001 + // the parameter is provided in form of a multiplier, where 1.0 means the default rate of 0.01 AirLeakRate *= 0.01; } } @@ -7149,6 +7161,7 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { if( asb == "Manual" ) { ASBType = 1; } else if( asb == "Automatic" ) { ASBType = 2; } + else if (asb == "Yes") { ASBType = 128; } } else { @@ -7397,6 +7410,7 @@ void TMoverParameters::LoadFIZ_Engine( std::string const &Input ) { extract_value( eimc[ eimc_p_Imax ], "Imax", Input, "" ); extract_value( eimc[ eimc_p_abed ], "abed", Input, "" ); extract_value( eimc[ eimc_p_eped ], "edep", Input, "" ); + EIMCLogForce = ( extract_value( "eimclf", Input ) == "Yes" ); Flat = ( extract_value( "Flat", Input ) == "1" ); @@ -7826,10 +7840,13 @@ bool TMoverParameters::CheckLocomotiveParameters(bool ReadyFlag, int Dir) BrakeCtrlPos = static_cast( Handle->GetPos( bh_NP ) ); else BrakeCtrlPos = static_cast( Handle->GetPos( bh_RP ) ); +/* + // NOTE: disabled and left up to the driver, if there's any MainSwitch( false ); PantFront( true ); PantRear( true ); MainSwitch( true ); +*/ ActiveDir = 0; // Dir; //nastawnik kierunkowy - musi być ustawiane osobno! DirAbsolute = ActiveDir * CabNo; // kierunek jazdy względem sprzęgów LimPipePress = CntrlPipePress; diff --git a/McZapkie/Oerlikon_ESt.cpp b/McZapkie/Oerlikon_ESt.cpp index ef433d64..8ae37298 100644 --- a/McZapkie/Oerlikon_ESt.cpp +++ b/McZapkie/Oerlikon_ESt.cpp @@ -435,8 +435,6 @@ void TNESt3::ForceEmptiness() Miedzypoj->CreatePress(0); CntrlRes->CreatePress(0); - BrakeStatus = 0; - ValveRes->Act(); BrakeRes->Act(); Miedzypoj->Act(); diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index 939c462b..cf366a89 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -413,6 +413,7 @@ void TBrake::ForceEmptiness() { ValveRes->CreatePress(0); BrakeRes->CreatePress(0); + ValveRes->Act(); BrakeRes->Act(); } @@ -757,6 +758,17 @@ double TESt::GetCRP() return CntrlRes->P(); } +void TESt::ForceEmptiness() { + + ValveRes->CreatePress( 0 ); + BrakeRes->CreatePress( 0 ); + CntrlRes->CreatePress( 0 ); + + ValveRes->Act(); + BrakeRes->Act(); + CntrlRes->Act(); +} + //---EP2--- void TEStEP2::Init( double const PP, double const HPP, double const LPP, double const BP, int const BDF ) @@ -1462,9 +1474,15 @@ double TEStED::GetPF( double const PP, double const dt, double const Vel ) // powtarzacz — podwojny zawor zwrotny temp = Max0R(LoadC * BCP / temp * Min0R(Max0R(1 - EDFlag, 0), 1), LBP); + double speed = 1; + if ((ASBP < 0.1) && ((BrakeStatus & b_asb) == b_asb)) + { + temp = 0; + speed = 3; + } if ((BrakeCyl->P() > temp)) - dv = -PFVd(BrakeCyl->P(), 0, 0.02 * SizeBC, temp) * dt; + dv = -PFVd(BrakeCyl->P(), 0, 0.02 * SizeBC * speed, temp) * dt; else if ((BrakeCyl->P() < temp)) dv = PFVa(BVP, BrakeCyl->P(), 0.02 * SizeBC, temp) * dt; else @@ -1721,6 +1739,17 @@ double TCV1::GetCRP() return CntrlRes->P(); } +void TCV1::ForceEmptiness() { + + ValveRes->CreatePress( 0 ); + BrakeRes->CreatePress( 0 ); + CntrlRes->CreatePress( 0 ); + + ValveRes->Act(); + BrakeRes->Act(); + CntrlRes->Act(); +} + //---CV1-L-TR--- void TCV1L_TR::SetLBP( double const P ) @@ -1995,7 +2024,8 @@ double TKE::GetPF( double const PP, double const dt, double const Vel ) // luzowanie CH // temp:=Max0R(BCP,LBP); IMP = Max0R(IMP / temp, Max0R(LBP, ASBP * int((BrakeStatus & b_asb) == b_asb))); - + if ((ASBP < 0.1) && ((BrakeStatus & b_asb) == b_asb)) + IMP = 0; // luzowanie CH if ((BCP > IMP + 0.005) || (Max0R(ImplsRes->P(), 8 * LBP) < 0.25)) dv = PFVd(BCP, 0, 0.05, IMP) * dt; @@ -2090,6 +2120,21 @@ void TKE::SetLBP( double const P ) LBP = P; } +void TKE::ForceEmptiness() { + + ValveRes->CreatePress( 0 ); + BrakeRes->CreatePress( 0 ); + CntrlRes->CreatePress( 0 ); + ImplsRes->CreatePress( 0 ); + Brak2Res->CreatePress( 0 ); + + ValveRes->Act(); + BrakeRes->Act(); + CntrlRes->Act(); + ImplsRes->Act(); + Brak2Res->Act(); +} + //---KRANY--- double TDriverHandle::GetPF(double const i_bcp, double PP, double HP, double dt, double ep) diff --git a/McZapkie/hamulce.h b/McZapkie/hamulce.h index d7c3690e..6033168c 100644 --- a/McZapkie/hamulce.h +++ b/McZapkie/hamulce.h @@ -260,6 +260,7 @@ class TESt : public TBrake { void CheckReleaser(double dt); //odluzniacz double CVs(double BP); //napelniacz sterujacego double BVs(double BCP); //napelniacz pomocniczego + void ForceEmptiness() /*override*/; // wymuszenie bycia pustym inline TESt(double i_mbp, double i_bcr, double i_bcd, double i_brc, int i_bcn, int i_BD, int i_mat, int i_ba, int i_nbpa) : TBrake( i_mbp, i_bcr, i_bcd, i_brc, i_bcn, i_BD, i_mat, i_ba, i_nbpa) @@ -410,6 +411,7 @@ public: void CheckState( double const BCP, double &dV1 ); double CVs( double const BP ); double BVs( double const BCP ); + void ForceEmptiness() /*override*/; // wymuszenie bycia pustym inline TCV1(double i_mbp, double i_bcr, double i_bcd, double i_brc, int i_bcn, int i_BD, int i_mat, int i_ba, int i_nbpa) : TBrake( i_mbp, i_bcr, i_bcd, i_brc, i_bcn, i_BD, i_mat, i_ba, i_nbpa) @@ -482,6 +484,7 @@ class TKE : public TBrake { //Knorr Einheitsbauart — jeden do wszystkiego void PLC( double const mass ); //wspolczynnik cisnienia przystawki wazacej void SetLP( double const TM, double const LM, double const TBP ); //parametry przystawki wazacej void SetLBP( double const P ); //cisnienie z hamulca pomocniczego + void ForceEmptiness() /*override*/; // wymuszenie bycia pustym inline TKE(double i_mbp, double i_bcr, double i_bcd, double i_brc, int i_bcn, int i_BD, int i_mat, int i_ba, int i_nbpa) : TBrake( i_mbp, i_bcr, i_bcd, i_brc, i_bcn, i_BD, i_mat, i_ba, i_nbpa) diff --git a/McZapkie/mctools.cpp b/McZapkie/mctools.cpp index 2e2da932..ac34b13d 100644 --- a/McZapkie/mctools.cpp +++ b/McZapkie/mctools.cpp @@ -13,6 +13,17 @@ Copyright (C) 2007-2014 Maciej Cierniak */ #include "stdafx.h" #include "mctools.h" + +#include +#include +#ifndef WIN32 +#include +#endif + +#ifdef WIN32 +#define stat _stat +#endif + #include "Globals.h" /*================================================*/ @@ -281,3 +292,12 @@ bool FileExists( std::string const &Filename ) { std::ifstream file( fn ); return( true == file.is_open() ); } + +// returns time of last modification for specified file +time_t +last_modified( std::string const &Filename ) { + + struct stat filestat; + if( ::stat( Filename.c_str(), &filestat ) == 0 ) { return filestat.st_mtime; } + else { return 0; } +} diff --git a/McZapkie/mctools.h b/McZapkie/mctools.h index 1110e5f6..d0d11aa3 100644 --- a/McZapkie/mctools.h +++ b/McZapkie/mctools.h @@ -158,3 +158,6 @@ bool extract_value( bool &Variable, std::string const &Key, std::string const &Input, std::string const &Default ); bool FileExists( std::string const &Filename ); + +// returns time of last modification for specified file +time_t last_modified( std::string const &Filename ); diff --git a/MemCell.cpp b/MemCell.cpp index 29339a83..10679459 100644 --- a/MemCell.cpp +++ b/MemCell.cpp @@ -16,26 +16,11 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "MemCell.h" -#include "Globals.h" +#include "simulation.h" #include "Logs.h" -#include "usefull.h" -#include "Driver.h" -#include "Event.h" -#include "parser.h" -//--------------------------------------------------------------------------- +TMemCell::TMemCell( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {} -TMemCell::TMemCell(vector3 *p) -{ - fValue1 = fValue2 = 0; - vPosition = - p ? *p : vector3(0, 0, 0); // ustawienie współrzędnych, bo do TGroundNode nie ma dostępu - bCommand = false; // komenda wysłana - OnSent = NULL; -} -void TMemCell::Init() -{ -} void TMemCell::UpdateValues( std::string const &szNewText, double const fNewValue1, double const fNewValue2, int const CheckMask ) { if (CheckMask & update_memadd) @@ -103,10 +88,12 @@ TCommandType TMemCell::CommandCheck() bool TMemCell::Load(cParser *parser) { std::string token; - parser->getTokens(1, false); // case sensitive - *parser >> szText; - parser->getTokens( 2, false ); + parser->getTokens( 6, false ); *parser + >> m_area.center.x + >> m_area.center.y + >> m_area.center.z + >> szText >> fValue1 >> fValue2; parser->getTokens(); @@ -121,7 +108,7 @@ bool TMemCell::Load(cParser *parser) return true; } -void TMemCell::PutCommand(TController *Mech, vector3 *Loc) +void TMemCell::PutCommand( TController *Mech, glm::dvec3 const *Loc ) { // wysłanie zawartości komórki do AI if (Mech) Mech->PutCommand(szText, fValue1, fValue2, Loc); @@ -150,11 +137,6 @@ bool TMemCell::Compare( std::string const &szTestText, double const fTestValue1, && ( !TestFlag( CheckMask, conditional_memval2 ) || ( fValue2 == fTestValue2 ) ) ); }; -bool TMemCell::Render() -{ - return true; -} - bool TMemCell::IsVelocity() { // sprawdzenie, czy event odczytu tej komórki ma być do skanowania, czy do kolejkowania if (eCommand == cm_SetVelocity) @@ -169,11 +151,38 @@ void TMemCell::StopCommandSent() if (!bCommand) return; bCommand = false; - if (OnSent) // jeśli jest event - Global::AddToQuery(OnSent, NULL); + if( OnSent ) { + // jeśli jest event + simulation::Events.AddToQuery( OnSent, nullptr ); + } }; void TMemCell::AssignEvents(TEvent *e) { // powiązanie eventu OnSent = e; }; + + + +// legacy method, initializes traction after deserialization from scenario file +void +memory_table::InitCells() { + + for( auto *cell : m_items ) { + // Ra: eventy komórek pamięci, wykonywane po wysłaniu komendy do zatrzymanego pojazdu + cell->AssignEvents( simulation::Events.FindEvent( cell->name() + ":sent" ) ); + } +} + +// legacy method, sends content of all cells to the log +void +memory_table::log_all() { + + for( auto *cell : m_items ) { + + WriteLog( "Memcell \"" + cell->name() + "\": [" + + cell->Text() + "] [" + + to_string( cell->Value1(), 2 ) + "] [" + + to_string( cell->Value2(), 2 ) + "]" ); + } +} diff --git a/MemCell.h b/MemCell.h index a4384b3f..2bbaa159 100644 --- a/MemCell.h +++ b/MemCell.h @@ -10,52 +10,67 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "Classes.h" +#include "scenenode.h" #include "dumb3d.h" +#include "Names.h" -class TMemCell -{ - private: - Math3D::vector3 vPosition; - std::string szText; - double fValue1; - double fValue2; - TCommandType eCommand; - bool bCommand; // czy zawiera komendę dla zatrzymanego AI - TEvent *OnSent; // event dodawany do kolejki po wysłaniu komendy zatrzymującej skład - public: +class TMemCell : public editor::basic_node { + +public: std::string asTrackName; // McZapkie-100302 - zeby nazwe toru na ktory jest Putcommand wysylane pamietac - TMemCell( Math3D::vector3 *p ); - void Init(); - void UpdateValues( std::string const &szNewText, double const fNewValue1, double const fNewValue2, int const CheckMask ); - bool Load(cParser *parser); - void PutCommand( TController *Mech, Math3D::vector3 *Loc ); - bool Compare( std::string const &szTestText, double const fTestValue1, double const fTestValue2, int const CheckMask ); - bool Render(); - inline std::string const &Text() { return szText; } - inline double Value1() - { - return fValue1; - }; - inline double Value2() - { - return fValue2; - }; - inline Math3D::vector3 Position() - { - return vPosition; - }; - inline TCommandType Command() - { - return eCommand; - }; - inline bool StopCommand() - { - return bCommand; - }; + + TMemCell( scene::node_data const &Nodedata ); + + void + UpdateValues( std::string const &szNewText, double const fNewValue1, double const fNewValue2, int const CheckMask ); + bool + Load(cParser *parser); + void + PutCommand( TController *Mech, glm::dvec3 const *Loc ); + bool + Compare( std::string const &szTestText, double const fTestValue1, double const fTestValue2, int const CheckMask ); + std::string const & + Text() const { + return szText; } + double + Value1() const { + return fValue1; }; + double + Value2() const { + return fValue2; }; + TCommandType + Command() const { + return eCommand; }; + bool + StopCommand() const { + return bCommand; }; void StopCommandSent(); TCommandType CommandCheck(); bool IsVelocity(); void AssignEvents(TEvent *e); + +private: + // content + std::string szText; + double fValue1 { 0.0 }; + double fValue2 { 0.0 }; + // other + TCommandType eCommand { cm_Unknown }; + bool bCommand { false }; // czy zawiera komendę dla zatrzymanego AI + TEvent *OnSent { nullptr }; // event dodawany do kolejki po wysłaniu komendy zatrzymującej skład +}; + + + +class memory_table : public basic_table { + +public: + // legacy method, initializes traction after deserialization from scenario file + void + InitCells(); + // legacy method, sends content of all cells to the log + void + log_all(); }; //--------------------------------------------------------------------------- diff --git a/Model3d.cpp b/Model3d.cpp index a16c7512..1bfc6196 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -988,11 +988,11 @@ TSubModel::create_geometry( std::size_t &Dataoffset, geometrybank_handle const & // data offset is used to determine data offset of each submodel into single shared geometry bank // (the offsets are part of legacy system which we now need to work around for backward compatibility) - if( Child ) Child->create_geometry( Dataoffset, Bank ); if( false == Vertices.empty() ) { + tVboPtr = static_cast( Dataoffset ); Dataoffset += Vertices.size(); // conveniently all relevant custom node types use GL_POINTS, or we'd have to determine the type on individual basis @@ -1003,67 +1003,33 @@ TSubModel::create_geometry( std::size_t &Dataoffset, geometrybank_handle const & m_geometry = GfxRenderer.Insert( Vertices, Bank, type ); } - if( Next ) - Next->create_geometry( Dataoffset, Bank ); -} - -// places contained geometry in provided ground node -void -TSubModel::convert( TGroundNode &Groundnode ) const { - - Groundnode.asName = pName; - Groundnode.Ambient = f4Ambient; - Groundnode.Diffuse = f4Diffuse; - Groundnode.Specular = f4Specular; - Groundnode.m_material = m_material; - Groundnode.iFlags = ( - ( true == GfxRenderer.Material( m_material ).has_alpha ) ? - 0x20 : - 0x10 ); - - if( m_geometry == null_handle ) { return; } - - std::size_t vertexcount { 0 }; - std::vector importedvertices; - TGroundVertex vertex, vertex1, vertex2; - for( auto const &sourcevertex : GfxRenderer.Vertices( m_geometry ) ) { - vertex.position = sourcevertex.position; - vertex.normal = sourcevertex.normal; - vertex.texture = sourcevertex.texture; - if( vertexcount == 0 ) { vertex1 = vertex; } - else if( vertexcount == 1 ) { vertex2 = vertex; } - else if( vertexcount >= 2 ) { - if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { - importedvertices.emplace_back( vertex1 ); - importedvertices.emplace_back( vertex2 ); - importedvertices.emplace_back( vertex ); + if( m_geometry != 0 ) { + // calculate bounding radius while we're at it + // NOTE: doesn't take into account transformation hierarchy TODO: implement it + float squaredradius { 0.f }; + // if this happens to be root node it may already have non-squared radius of the largest child + // since we're comparing squared radii, we need to square it back for correct results + m_boundingradius *= m_boundingradius; + for( auto const &vertex : GfxRenderer.Vertices( m_geometry ) ) { + squaredradius = static_cast( glm::length2( vertex.position ) ); + if( squaredradius > m_boundingradius ) { + m_boundingradius = squaredradius; } } - ++vertexcount; - if( vertexcount > 2 ) { vertexcount = 0; } // start new triangle if needed - } - if( Groundnode.Piece == nullptr ) { - Groundnode.Piece = new piece_node(); - } - Groundnode.iNumVerts = importedvertices.size(); - if( Groundnode.iNumVerts > 0 ) { - - Groundnode.Piece->vertices.swap( importedvertices ); - - for( auto const &vertex : Groundnode.Piece->vertices ) { - Groundnode.pCenter += vertex.position; + if( m_boundingradius > 0.f ) { m_boundingradius = std::sqrt( m_boundingradius ); } + // adjust overall radius if needed + // NOTE: the method to access root submodel is... less than ideal + auto *root { this }; + while( root->Parent != nullptr ) { + root = root->Parent; } - Groundnode.pCenter /= Groundnode.iNumVerts; - - double r { 0.0 }; - double tf; - for( auto const &vertex : Groundnode.Piece->vertices ) { - tf = glm::length2( vertex.position - glm::dvec3{ Groundnode.pCenter } ); - if( tf > r ) - r = tf; - } - Groundnode.fSquareRadius += r; + root->m_boundingradius = std::max( + root->m_boundingradius, + m_boundingradius ); } + + if( Next ) + Next->create_geometry( Dataoffset, Bank ); } void TSubModel::ColorsSet( glm::vec3 const &Ambient, glm::vec3 const &Diffuse, glm::vec3 const &Specular ) @@ -1151,7 +1117,8 @@ TModel3d::~TModel3d() { if (iFlags & 0x0200) { // wczytany z pliku tekstowego, submodele sprzątają same - Root = nullptr; + SafeDelete( Root ); +// Root = nullptr; } else { // wczytano z pliku binarnego (jest właścicielem tablic) @@ -1639,7 +1606,7 @@ void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, } } else { - ErrorLog( "Bad model: reference to non-existent texture index in sub-model" + ( pName.empty() ? "" : " \"" + pName + "\"" ) ); + ErrorLog( "Bad model: reference to nonexistent texture index in sub-model" + ( pName.empty() ? "" : " \"" + pName + "\"" ) ); m_material = null_handle; } } @@ -1750,18 +1717,11 @@ void TModel3d::Init() return; // operacje zostały już wykonane if (Root) { - if (iFlags & 0x0200) // jeśli wczytano z pliku tekstowego - { // jest jakiś dziwny błąd, że obkręcany ma być tylko ostatni submodel - // głównego łańcucha - // TSubModel *p=Root; - // do - //{p->InitialRotate(true); //ostatniemu należy się konwersja układu - // współrzędnych - // p=p->NextGet(); - //} - // while (p->NextGet()) - // Root->InitialRotate(false); //a poprzednim tylko optymalizacja - Root->InitialRotate(true); // argumet określa, czy wykonać pierwotny obrót + if (iFlags & 0x0200) { + // jeśli wczytano z pliku tekstowego jest jakiś dziwny błąd, + // że obkręcany ma być tylko ostatni submodel głównego łańcucha + // argumet określa, czy wykonać pierwotny obrót + Root->InitialRotate(true); } iFlags |= Root->FlagsCheck() | 0x8000; // flagi całego modelu if (iNumVerts) { @@ -1779,11 +1739,6 @@ void TModel3d::Init() } }; -void TModel3d::BreakHierarhy() -{ - Error("Not implemented yet :("); -}; - //----------------------------------------------------------------------------- // 2012-02 funkcje do tworzenia terenu z E3D //----------------------------------------------------------------------------- diff --git a/Model3d.h b/Model3d.h index 76c00b48..490d9679 100644 --- a/Model3d.h +++ b/Model3d.h @@ -50,14 +50,18 @@ enum TAnimType // rodzaj animacji at_Undefined = 0x800000FF // animacja chwilowo nieokreślona }; +namespace scene { +class shape_node; +} + class TModel3d; -class TGroundNode; class TSubModel { // klasa submodelu - pojedyncza siatka, punkt świetlny albo grupa punktów friend class opengl_renderer; friend class TModel3d; // temporary workaround. TODO: clean up class content/hierarchy friend class TDynamicObject; // temporary etc + friend class scene::shape_node; // temporary etc public: enum normalization { @@ -130,10 +134,8 @@ private: public: // chwilowo float3 v_TransVector { 0.0f, 0.0f, 0.0f }; -/* - basic_vertex *Vertices; // roboczy wskaźnik - wczytanie T3D do VBO -*/ vertex_array Vertices; + float m_boundingradius { 0 }; size_t iAnimOwner{ 0 }; // roboczy numer egzemplarza, który ustawił animację TAnimType b_aAnim{ at_None }; // kody animacji oddzielnie, bo zerowane public: @@ -163,9 +165,6 @@ public: TSubModel * NextGet() { return Next; }; TSubModel * ChildGet() { return Child; }; int TriangleAdd(TModel3d *m, material_handle tex, int tri); -/* - basic_vertex * TrianglePtr(int tex, int pos, glm::vec3 const &Ambient, glm::vec3 const &Diffuse, glm::vec3 const &Specular ); -*/ void SetRotate(float3 vNewRotateAxis, float fNewAngle); void SetRotateXYZ(vector3 vNewAngles); void SetRotateXYZ(float3 vNewAngles); @@ -215,8 +214,6 @@ public: std::vector&); void serialize_geometry( std::ostream &Output ) const; // places contained geometry in provided ground node - void convert( TGroundNode &Groundnode ) const; - }; class TModel3d : public CMesh @@ -236,6 +233,11 @@ private: std::string asBinary; // nazwa pod którą zapisać model binarny std::string m_filename; public: + float bounding_radius() const { + return ( + Root ? + Root->m_boundingradius : + 0.f ); } inline TSubModel * GetSMRoot() { return (Root); }; TModel3d(); ~TModel3d(); @@ -246,7 +248,6 @@ public: void LoadFromBinFile(std::string const &FileName, bool dynamic); bool LoadFromFile(std::string const &FileName, bool dynamic); void SaveToBinFile(std::string const &FileName); - void BreakHierarhy(); int Flags() const { return iFlags; }; void Init(); std::string NameGet() { return m_filename; }; diff --git a/Names.h b/Names.h index 2a3addb6..1af3e4ca 100644 --- a/Names.h +++ b/Names.h @@ -13,55 +13,52 @@ http://mozilla.org/MPL/2.0/. #include template -class TNames { +class basic_table { public: -// types: - -// constructors: - TNames() = default; - -// destructor: - -// methods: - // dodanie obiektu z wskaźnikiem. updates data field if the object already exists. returns true for insertion, false for update +// destructor + ~basic_table() { + for( auto *item : m_items ) { + delete item; } } +// methods + // adds provided item to the collection. returns: true if there's no duplicate with the same name, false otherwise bool - Add( int const Type, std::string const &Name, Type_ Data ) { - - auto lookup = find_map( Type ).emplace( Name, Data ); - if( lookup.second == false ) { - // record already exists, update it - lookup.first->second = Data; - return false; - } - else { - // new record inserted, bail out + insert( Type_ *Item ) { + m_items.emplace_back( Item ); + auto const itemname = Item->name(); + if( ( true == itemname.empty() ) || ( itemname == "none" ) ) { return true; } - } - // returns pointer associated with provided label, or nullptr if there's no match - Type_ - Find( int const Type, std::string const &Name ) { + auto const itemhandle { m_items.size() - 1 }; + // add item name to the map + auto mapping = m_itemmap.emplace( itemname, itemhandle ); + if( true == mapping.second ) { + return true; + } + // cell with this name already exists; update mapping to point to the new one, for backward compatibility + mapping.first->second = itemhandle; + return false; } + // locates item with specified name. returns pointer to the item, or nullptr + Type_ * + find( std::string const &Name ) { + auto lookup = m_itemmap.find( Name ); + return ( + lookup != m_itemmap.end() ? + m_items[ lookup->second ] : + nullptr ); } - auto const &map = find_map( Type ); - auto const lookup = map.find( Name ); - if( lookup != map.end() ) { return lookup->second; } - else { return nullptr; } - } +protected: +// types + using type_sequence = std::deque; + using type_map = std::unordered_map; +// members + type_sequence m_items; + type_map m_itemmap; -private: -// types: - typedef std::unordered_map type_map; - typedef std::unordered_map typemap_map; +public: + // data access + type_sequence & + sequence() { + return m_items; } -// methods: - // returns database stored with specified type key; creates new database if needed. - type_map & - find_map( int const Type ) { - - return m_maps.emplace( Type, type_map() ).first->second; - } - -// members: - typemap_map m_maps; // list of object maps of types specified so far }; diff --git a/Segment.cpp b/Segment.cpp index 24efc10e..1a3a50bb 100644 --- a/Segment.cpp +++ b/Segment.cpp @@ -59,8 +59,8 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M CPointIn = NewCPointIn; Point2 = NewPoint2; // poprawienie przechyłki - fRoll1 = DegToRad(fNewRoll1); // Ra: przeliczone jest bardziej przydatne do obliczeń - fRoll2 = DegToRad(fNewRoll2); + fRoll1 = glm::radians(fNewRoll1); // Ra: przeliczone jest bardziej przydatne do obliczeń + fRoll2 = glm::radians(fNewRoll2); if (Global::bRollFix) { // Ra: poprawianie przechyłki // Przechyłka powinna być na środku wewnętrznej szyny, a standardowo jest w osi @@ -78,7 +78,7 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M CPointOut.y += w1; // prosty ma wektory jednostkowe pOwner->MovedUp1(w1); // zwrócić trzeba informację o podwyższeniu podsypki } - if (fRoll2 != 0.0) + if (fRoll2 != 0.f) { double w2 = std::abs(std::sin(fRoll2) * 0.75); // 0.5*w2+0.0325; //0.75m dla 1.435 Point2.y += w2; // modyfikacja musi być przed policzeniem dalszych parametrów @@ -87,9 +87,9 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M // zwrócić trzeba informację o podwyższeniu podsypki } } + // kąt w planie, żeby nie liczyć wielokrotnie // Ra: ten kąt jeszcze do przemyślenia jest - fDirection = -atan2(Point2.x - Point1.x, - Point2.z - Point1.z); // kąt w planie, żeby nie liczyć wielokrotnie + fDirection = -std::atan2(Point2.x - Point1.x, Point2.z - Point1.z); bCurve = bIsCurve; if (bCurve) { // przeliczenie współczynników wielomianu, będzie mniej mnożeń i można policzyć pochodne @@ -101,10 +101,9 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M else fLength = (Point1 - Point2).Length(); fStep = fNewStep; - if (fLength <= 0) - { - ErrorLog( "Bad geometry: zero length spline \"" + pOwner->NameGet() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); - // MessageBox(0,"Length<=0","TSegment::Init",MB_OK); + if (fLength <= 0) { + + ErrorLog( "Bad track: zero length spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); fLength = 0.01; // crude workaround TODO: fix this properly /* return false; // zerowe nie mogą być @@ -206,7 +205,7 @@ double TSegment::GetTFromS(double const s) const // Newton's method failed. If this happens, increase iterations or // tolerance or integration accuracy. // return -1; //Ra: tu nigdy nie dojdzie - ErrorLog( "Bad geometry: shape estimation failed for spline \"" + pOwner->NameGet() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); + ErrorLog( "Bad track: shape estimation failed for spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); // MessageBox(0,"Too many iterations","GetTFromS",MB_OK); return fTime; }; @@ -324,23 +323,28 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) const } }; -void TSegment::RaPositionGet(double const fDistance, Math3D::vector3 &p, Math3D::vector3 &a) const -{ // ustalenie pozycji osi na torze, przechyłki, pochylenia i kierunku jazdy - if (bCurve) - { // można by wprowadzić uproszczony wzór dla okręgów płaskich - double t = GetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t) - p = RaInterpolate(t); - a.x = (1.0 - t) * fRoll1 + (t)*fRoll2; // przechyłka w danym miejscu (zmienia się liniowo) +// ustalenie pozycji osi na torze, przechyłki, pochylenia i kierunku jazdy +void TSegment::RaPositionGet(double const fDistance, Math3D::vector3 &p, Math3D::vector3 &a) const { + + if (bCurve) { + // można by wprowadzić uproszczony wzór dla okręgów płaskich + auto const t = GetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t) + p = FastGetPoint( t ); + // przechyłka w danym miejscu (zmienia się liniowo) + a.x = interpolate( fRoll1, fRoll2, t ); // pochodna jest 3*A*t^2+2*B*t+C - a.y = atan(t * (t * 3.0 * vA.y + vB.y + vB.y) + vC.y); // pochylenie krzywej (w pionie) - a.z = -atan2(t * (t * 3.0 * vA.x + vB.x + vB.x) + vC.x, - t * (t * 3.0 * vA.z + vB.z + vB.z) + vC.z); // kierunek krzywej w planie + auto const tangent = t * ( t * 3.0 * vA + vB + vB ) + vC; + // pochylenie krzywej (w pionie) + a.y = std::atan( tangent.y ); + // kierunek krzywej w planie + a.z = -std::atan2( tangent.x, tangent.z ); } - else - { // wyliczenie dla odcinka prostego jest prostsze - double t = fDistance / fLength; // zerowych torów nie ma - p = ((1.0 - t) * Point1 + (t)*Point2); - a.x = (1.0 - t) * fRoll1 + (t)*fRoll2; // przechyłka w danym miejscu (zmienia się liniowo) + else { + // wyliczenie dla odcinka prostego jest prostsze + auto const t = fDistance / fLength; // zerowych torów nie ma + p = FastGetPoint( t ); + // przechyłka w danym miejscu (zmienia się liniowo) + a.x = interpolate( fRoll1, fRoll2, t ); a.y = fStoop; // pochylenie toru prostego a.z = fDirection; // kierunek toru w planie } @@ -355,7 +359,7 @@ Math3D::vector3 TSegment::FastGetPoint(double const t) const interpolate( Point1, Point2, t ) ); } -bool TSegment::RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, const vector6 *ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale, int iSkip, int iEnd, double fOffsetX, Math3D::vector3 **p, bool bRender) +bool TSegment::RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, const basic_vertex *ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale, int iSkip, int iEnd, float fOffsetX, glm::vec3 **p, bool bRender) { // generowanie trójkątów dla odcinka trajektorii ruchu // standardowo tworzy triangle_strip dla prostego albo ich zestaw dla łuku // po modyfikacji - dla ujemnego (iNumShapePoints) w dodatkowych polach tabeli @@ -365,13 +369,14 @@ bool TSegment::RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, if( !fTsBuffer ) return false; // prowizoryczne zabezpieczenie przed wysypem - ustalić faktyczną przyczynę - Math3D::vector3 pos1, pos2, dir, parallel1, parallel2, pt, norm; - double s, step, fOffset, tv1, tv2, t, fEnd; + glm::vec3 pos1, pos2, dir, parallel1, parallel2, pt, norm; + float s, step, fOffset, tv1, tv2, t, fEnd; bool const trapez = iNumShapePoints < 0; // sygnalizacja trapezowatości iNumShapePoints = std::abs( iNumShapePoints ); - fTextureLength *= Texturescale; + float const texturelength = fTextureLength * Texturescale; + float const texturescale = Texturescale; - double m1, jmm1, m2, jmm2; // pozycje względne na odcinku 0...1 (ale nie parametr Beziera) + float m1, jmm1, m2, jmm2; // pozycje względne na odcinku 0...1 (ale nie parametr Beziera) step = fStep; tv1 = 1.0; // Ra: to by można było wyliczać dla odcinka, wyglądało by lepiej s = fStep * iSkip; // iSkip - ile odcinków z początku pominąć @@ -379,9 +384,14 @@ bool TSegment::RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, t = fTsBuffer[ i ]; // tabela wattości t dla segmentów // BUG: length of spline can be 0, we should skip geometry generation for such cases fOffset = 0.1 / fLength; // pierwsze 10cm - pos1 = FastGetPoint( t ); // wektor początku segmentu - dir = FastGetDirection( t, fOffset ); // wektor kierunku - parallel1 = Normalize( Math3D::vector3( -dir.z, 0.0, dir.x ) ); // wektor poprzeczny + pos1 = glm::dvec3{ FastGetPoint( t ) - Origin }; // wektor początku segmentu + dir = glm::dvec3{ FastGetDirection( t, fOffset ) }; // wektor kierunku + parallel1 = glm::vec3{ -dir.z, 0.f, dir.x }; // wektor poprzeczny + if( glm::length2( parallel1 ) == 0.f ) { + // temporary workaround for malformed situations with control points placed above endpoints + parallel1 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } }; + } + parallel1 = glm::normalize( parallel1 ); if( iEnd == 0 ) iEnd = iSegCount; fEnd = fLength * double( iEnd ) / double( iSegCount ); @@ -406,26 +416,31 @@ bool TSegment::RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, while( tv1 < 0.0 ) { tv1 += 1.0; } - tv2 = tv1 - step / fTextureLength; // mapowanie na końcu segmentu + tv2 = tv1 - step / texturelength; // mapowanie na końcu segmentu t = fTsBuffer[ i ]; // szybsze od GetTFromS(s); - pos2 = FastGetPoint( t ); - dir = FastGetDirection( t, fOffset ); // nowy wektor kierunku - parallel2 = Normalize( Math3D::vector3( -dir.z, 0.0, dir.x ) ); // wektor poprzeczny + pos2 = glm::dvec3{ FastGetPoint( t ) - Origin }; + dir = glm::dvec3{ FastGetDirection( t, fOffset ) }; // nowy wektor kierunku + parallel2 = glm::vec3{ -dir.z, 0.f, dir.x }; // wektor poprzeczny + if( glm::length2( parallel2 ) == 0.f ) { + // temporary workaround for malformed situations with control points placed above endpoints + parallel2 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } }; + } + parallel2 = glm::normalize( parallel2 ); if( trapez ) { for( int j = 0; j < iNumShapePoints; ++j ) { - pt = parallel1 * ( jmm1 * ( ShapePoints[ j ].x - fOffsetX ) + m1 * ShapePoints[ j + iNumShapePoints ].x ) + pos1; - pt.y += jmm1 * ShapePoints[ j ].y + m1 * ShapePoints[ j + iNumShapePoints ].y; - pt -= Origin; - norm = ( jmm1 * ShapePoints[ j ].n.x + m1 * ShapePoints[ j + iNumShapePoints ].n.x ) * parallel1; - norm.y += jmm1 * ShapePoints[ j ].n.y + m1 * ShapePoints[ j + iNumShapePoints ].n.y; + pt = parallel1 * ( jmm1 * ( ShapePoints[ j ].position.x - fOffsetX ) + m1 * ShapePoints[ j + iNumShapePoints ].position.x ) + pos1; + pt.y += jmm1 * ShapePoints[ j ].position.y + m1 * ShapePoints[ j + iNumShapePoints ].position.y; +// pt -= Origin; + norm = ( jmm1 * ShapePoints[ j ].normal.x + m1 * ShapePoints[ j + iNumShapePoints ].normal.x ) * parallel1; + norm.y += jmm1 * ShapePoints[ j ].normal.y + m1 * ShapePoints[ j + iNumShapePoints ].normal.y; if( bRender ) { // skrzyżowania podczas łączenia siatek mogą nie renderować poboczy, ale potrzebować punktów Output.emplace_back( - glm::vec3 { pt.x, pt.y, pt.z }, - glm::vec3 { norm.x, norm.y, norm.z }, - glm::vec2 { ( jmm1 * ShapePoints[ j ].z + m1 * ShapePoints[ j + iNumShapePoints ].z ) / Texturescale, tv1 } ); + pt, + glm::normalize( norm ), + glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } ); } if( p ) // jeśli jest wskaźnik do tablicy if( *p ) @@ -435,17 +450,17 @@ bool TSegment::RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, ( *p )++; } // zapamiętanie brzegu jezdni // dla trapezu drugi koniec ma inne współrzędne - pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].x - fOffsetX ) + m2 * ShapePoints[ j + iNumShapePoints ].x ) + pos2; - pt.y += jmm2 * ShapePoints[ j ].y + m2 * ShapePoints[ j + iNumShapePoints ].y; - pt -= Origin; - norm = ( jmm1 * ShapePoints[ j ].n.x + m1 * ShapePoints[ j + iNumShapePoints ].n.x ) * parallel2; - norm.y += jmm1 * ShapePoints[ j ].n.y + m1 * ShapePoints[ j + iNumShapePoints ].n.y; + pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX ) + m2 * ShapePoints[ j + iNumShapePoints ].position.x ) + pos2; + pt.y += jmm2 * ShapePoints[ j ].position.y + m2 * ShapePoints[ j + iNumShapePoints ].position.y; +// pt -= Origin; + norm = ( jmm1 * ShapePoints[ j ].normal.x + m1 * ShapePoints[ j + iNumShapePoints ].normal.x ) * parallel2; + norm.y += jmm1 * ShapePoints[ j ].normal.y + m1 * ShapePoints[ j + iNumShapePoints ].normal.y; if( bRender ) { // skrzyżowania podczas łączenia siatek mogą nie renderować poboczy, ale potrzebować punktów Output.emplace_back( - glm::vec3 { pt.x, pt.y, pt.z }, - glm::vec3 { norm.x, norm.y, norm.z }, - glm::vec2 { ( jmm2 * ShapePoints[ j ].z + m2 * ShapePoints[ j + iNumShapePoints ].z ) / Texturescale, tv2 } ); + pt, + glm::normalize( norm ), + glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } ); } if( p ) // jeśli jest wskaźnik do tablicy if( *p ) @@ -460,27 +475,27 @@ bool TSegment::RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, if( bRender ) { for( int j = 0; j < iNumShapePoints; ++j ) { //łuk z jednym profilem - pt = parallel1 * ( ShapePoints[ j ].x - fOffsetX ) + pos1; - pt.y += ShapePoints[ j ].y; - pt -= Origin; - norm = ShapePoints[ j ].n.x * parallel1; - norm.y += ShapePoints[ j ].n.y; + pt = parallel1 * ( ShapePoints[ j ].position.x - fOffsetX ) + pos1; + pt.y += ShapePoints[ j ].position.y; +// pt -= Origin; + norm = ShapePoints[ j ].normal.x * parallel1; + norm.y += ShapePoints[ j ].normal.y; Output.emplace_back( - glm::vec3 { pt.x, pt.y, pt.z }, - glm::vec3 { norm.x, norm.y, norm.z }, - glm::vec2 { ShapePoints[ j ].z / Texturescale, tv1 } ); + pt, + glm::normalize( norm ), + glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } ); - pt = parallel2 * ShapePoints[ j ].x + pos2; - pt.y += ShapePoints[ j ].y; - pt -= Origin; - norm = ShapePoints[ j ].n.x * parallel2; - norm.y += ShapePoints[ j ].n.y; + pt = parallel2 * ShapePoints[ j ].position.x + pos2; + pt.y += ShapePoints[ j ].position.y; +// pt -= Origin; + norm = ShapePoints[ j ].normal.x * parallel2; + norm.y += ShapePoints[ j ].normal.y; Output.emplace_back( - glm::vec3 { pt.x, pt.y, pt.z }, - glm::vec3 { norm.x, norm.y, norm.z }, - glm::vec2 { ShapePoints[ j ].z / Texturescale, tv2 } ); + pt, + glm::normalize( norm ), + glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } ); } } } diff --git a/Segment.h b/Segment.h index 618132da..dfcf09be 100644 --- a/Segment.h +++ b/Segment.h @@ -14,44 +14,13 @@ http://mozilla.org/MPL/2.0/. #include "Classes.h" #include "usefull.h" -// 110405 Ra: klasa punktów przekroju z normalnymi - -class vector6 : public Math3D::vector3 -{ // punkt przekroju wraz z wektorem normalnym - public: - Math3D::vector3 n; - vector6() - { - x = y = z = n.x = n.z = 0.0; - n.y = 1.0; - }; - vector6(double a, double b, double c, double d, double e, double f) - //{x=a; y=b; z=c; n.x=d; n.y=e; n.z=f;}; - { - x = a; - y = b; - z = c; - n.x = 0.0; - n.y = 1.0; - n.z = 0.0; - }; // Ra: bo na razie są z tym problemy - vector6(double a, double b, double c) - { - x = a; - y = b; - z = c; - n.x = 0.0; - n.y = 1.0; - n.z = 0.0; - }; -}; - class TSegment { // aproksymacja toru (zwrotnica ma dwa takie, jeden z nich jest aktywny) private: Math3D::vector3 Point1, CPointOut, CPointIn, Point2; - double fRoll1 = 0.0, - fRoll2 = 0.0; // przechyłka na końcach + float + fRoll1{ 0.f }, + fRoll2{ 0.f }; // przechyłka na końcach double fLength = 0.0; // długość policzona double *fTsBuffer = nullptr; // wartości parametru krzywej dla równych odcinków double fStep = 0.0; @@ -60,7 +29,6 @@ class TSegment double fStoop = 0.0; // Ra: kąt wzniesienia; dla łuku od Point1 Math3D::vector3 vA, vB, vC; // współczynniki wielomianów trzeciego stopnia vD==Point1 TTrack *pOwner = nullptr; // wskaźnik na właściciela - double fAngle[ 2 ] = { 0.0, 0.0 }; // kąty zakończenia drogi na przejazdach Math3D::vector3 GetFirstDerivative(double const fTime) const; @@ -118,18 +86,18 @@ public: FastGetPoint_1() const { return Point2; }; inline - double + float GetRoll(double const Distance) const { - return interpolate( fRoll1, fRoll2, Distance / fLength ); } + return interpolate( fRoll1, fRoll2, static_cast(Distance / fLength) ); } inline void - GetRolls(double &r1, double &r2) const { + GetRolls(float &r1, float &r2) const { // pobranie przechyłek (do generowania trójkątów) r1 = fRoll1; r2 = fRoll2; } bool - RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, vector6 const *ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, double fOffsetX = 0.0, Math3D::vector3 **p = nullptr, bool bRender = true); + RenderLoft( vertex_array &Output, Math3D::vector3 const &Origin, basic_vertex const *ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, float fOffsetX = 0.f, glm::vec3 **p = nullptr, bool bRender = true); void Render(); inline @@ -140,10 +108,6 @@ public: int RaSegCount() const { return fTsBuffer ? iSegCount : 1; }; - inline - void - AngleSet(int const i, double const a) { - fAngle[i] = a; }; }; //--------------------------------------------------------------------------- diff --git a/Texture.cpp b/Texture.cpp index add2d4f8..31c4f358 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -765,8 +765,11 @@ texture_manager::create( std::string Filename, bool const Loadnow ) { Filename.erase( traitpos ); } - if( Filename.rfind( '.' ) != std::string::npos ) - Filename.erase( Filename.rfind( '.' ) ); // trim extension if there's one + if( ( Filename.rfind( '.' ) != std::string::npos ) + && ( Filename.rfind( '.' ) != Filename.rfind( ".." ) + 1 ) ) { + // trim extension if there's one, but don't mistake folder traverse for extension + Filename.erase( Filename.rfind( '.' ) ); + } std::replace(Filename.begin(), Filename.end(), '\\', '/'); // fix slashes diff --git a/Timer.cpp b/Timer.cpp index 0685a176..c8b74e2c 100644 --- a/Timer.cpp +++ b/Timer.cpp @@ -11,8 +11,9 @@ http://mozilla.org/MPL/2.0/. #include "Timer.h" #include "Globals.h" -namespace Timer -{ +namespace Timer { + +subsystem_stopwatches subsystem; double DeltaTime, DeltaRenderTime; double fFPS{ 0.0f }; diff --git a/Timer.h b/Timer.h index b6b58371..d0d9e5c9 100644 --- a/Timer.h +++ b/Timer.h @@ -9,8 +9,7 @@ http://mozilla.org/MPL/2.0/. #pragma once -namespace Timer -{ +namespace Timer { double GetTime(); @@ -28,6 +27,39 @@ double GetFPS(); void ResetTimers(); void UpdateTimers(bool pause); + +class stopwatch { + +public: + void + start() { + m_start = std::chrono::steady_clock::now(); } + void + stop() { + m_accumulator = 0.95f * m_accumulator + std::chrono::duration_cast( ( std::chrono::steady_clock::now() - m_start ) ).count() / 1000.f; } + float + average() const { + return m_accumulator / 20.f;} + +private: + std::chrono::time_point m_start { std::chrono::steady_clock::now() }; + float m_accumulator { 1000.f / 30.f * 20.f }; // 20 last samples, initial 'neutral' rate of 30 fps +}; + +struct subsystem_stopwatches { + stopwatch gfx_total; + stopwatch gfx_color; + stopwatch gfx_shadows; + stopwatch gfx_reflections; + stopwatch gfx_swap; + stopwatch sim_total; + stopwatch sim_dynamics; + stopwatch sim_events; + stopwatch sim_ai; +}; + +extern subsystem_stopwatches subsystem; + }; //--------------------------------------------------------------------------- diff --git a/Track.cpp b/Track.cpp index 32feb856..5b28c5bd 100644 --- a/Track.cpp +++ b/Track.cpp @@ -13,36 +13,27 @@ http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" + #include "Track.h" +#include "simulation.h" #include "Globals.h" -#include "Logs.h" -#include "usefull.h" -#include "renderer.h" #include "Timer.h" -#include "Ground.h" -#include "parser.h" -#include "MOVER.h" -#include "DynObj.h" -#include "AnimModel.h" -#include "MemCell.h" -#include "Event.h" +#include "Logs.h" // 101206 Ra: trapezoidalne drogi i tory // 110720 Ra: rozprucie zwrotnicy i odcinki izolowane -static const double fMaxOffset = 0.1; // double(0.1f)==0.100000001490116 +static float const fMaxOffset = 0.1f; // double(0.1f)==0.100000001490116 // const int NextMask[4]={0,1,0,1}; //tor następny dla stanów 0, 1, 2, 3 // const int PrevMask[4]={0,0,1,1}; //tor poprzedni dla stanów 0, 1, 2, 3 const int iLewo4[4] = {5, 3, 4, 6}; // segmenty (1..6) do skręcania w lewo const int iPrawo4[4] = {-4, -6, -3, -5}; // segmenty (1..6) do skręcania w prawo const int iProsto4[4] = {1, -1, 2, -2}; // segmenty (1..6) do jazdy prosto -const int iEnds4[13] = {3, 0, 2, 1, 2, 0, -1, - 1, 3, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1" +const int iEnds4[13] = {3, 0, 2, 1, 2, 0, -1, 1, 3, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1" const int iLewo3[4] = {1, 3, 2, 1}; // segmenty do skręcania w lewo const int iPrawo3[4] = {-2, -1, -3, -2}; // segmenty do skręcania w prawo const int iProsto3[4] = {1, -1, 2, 1}; // segmenty do jazdy prosto -const int iEnds3[13] = {3, 0, 2, 1, 2, 0, -1, - 1, 0, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1" +const int iEnds3[13] = {3, 0, 2, 1, 2, 0, -1, 1, 0, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1" TIsolated *TIsolated::pRoot = NULL; TSwitchExtension::TSwitchExtension(TTrack *owner, int const what) @@ -52,7 +43,7 @@ TSwitchExtension::TSwitchExtension(TTrack *owner, int const what) pPrevs[0] = nullptr; pPrevs[1] = nullptr; fOffset1 = fOffset = fDesiredOffset = -fOffsetDelay; // położenie zasadnicze - fOffset2 = 0.0; // w zasadniczym wewnętrzna iglica dolega + fOffset2 = 0.f; // w zasadniczym wewnętrzna iglica dolega Segments[0] = std::make_shared(owner); // z punktu 1 do 2 Segments[1] = std::make_shared(owner); // z punktu 3 do 4 (1=3 dla zwrotnic; odwrócony dla skrzyżowań, ewentualnie 1=4) Segments[2] = (what >= 3) ? @@ -83,18 +74,14 @@ TIsolated::TIsolated(std::string const &n, TIsolated *i) : // utworznie obwodu izolowanego. nothing to do here. }; -TIsolated::~TIsolated(){ - // usuwanie - /* - TIsolated *p=pRoot; - while (pRoot) - { - p=pRoot; - p->pNext=NULL; - delete p; - } - */ -}; +void TIsolated::DeleteAll() { + + while( pRoot ) { + auto *next = pRoot->Next(); + delete pRoot; + pRoot = next; + } +} TIsolated * TIsolated::Find(std::string const &n) { // znalezienie obiektu albo utworzenie nowego @@ -117,9 +104,9 @@ void TIsolated::Modify(int i, TDynamicObject *o) if (!iAxles) { // jeśli po zmianie nie ma żadnej osi na odcinku izolowanym if (evFree) - Global::AddToQuery(evFree, o); // dodanie zwolnienia do kolejki + simulation::Events.AddToQuery(evFree, o); // dodanie zwolnienia do kolejki if (Global::iMultiplayer) // jeśli multiplayer - Global::pGround->WyslijString(asName, 10); // wysłanie pakietu o zwolnieniu + multiplayer::WyslijString(asName, 10); // wysłanie pakietu o zwolnieniu if (pMemCell) // w powiązanej komórce pMemCell->UpdateValues( "", 0, int( pMemCell->Value2() ) & ~0xFF, update_memval2 ); //"zerujemy" ostatnią wartość @@ -131,9 +118,9 @@ void TIsolated::Modify(int i, TDynamicObject *o) if (iAxles) { if (evBusy) - Global::AddToQuery(evBusy, o); // dodanie zajętości do kolejki + simulation::Events.AddToQuery(evBusy, o); // dodanie zajętości do kolejki if (Global::iMultiplayer) // jeśli multiplayer - Global::pGround->WyslijString(asName, 11); // wysłanie pakietu o zajęciu + multiplayer::WyslijString(asName, 11); // wysłanie pakietu o zajęciu if (pMemCell) // w powiązanej komórce pMemCell->UpdateValues( "", 0, int( pMemCell->Value2() ) | 1, update_memval2 ); // zmieniamy ostatnią wartość na nieparzystą } @@ -141,30 +128,13 @@ void TIsolated::Modify(int i, TDynamicObject *o) }; // tworzenie nowego odcinka ruchu -TTrack::TTrack(TGroundNode *g) : - pMyNode( g ) // Ra: proteza, żeby tor znał swoją nazwę TODO: odziedziczyć TTrack z TGroundNode -{ - fRadiusTable[ 0 ] = 0.0; - fRadiusTable[ 1 ] = 0.0; - nFouling[ 0 ] = nullptr; - nFouling[ 1 ] = nullptr; -} +TTrack::TTrack( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {} TTrack::~TTrack() { // likwidacja odcinka if( eType == tt_Cross ) { delete SwitchExtension->vPoints; // skrzyżowanie może mieć punkty } - -/* if (eType == tt_Normal) - delete Segment; // dla zwrotnic nie usuwać tego (kopiowany) - else - { // usuwanie dodatkowych danych dla niezwykłych odcinków - if (eType == tt_Cross) - delete SwitchExtension->vPoints; // skrzyżowanie może mieć punkty - SafeDelete(SwitchExtension); - } -*/ } void TTrack::Init() @@ -190,32 +160,39 @@ void TTrack::Init() } } +bool +TTrack::sort_by_material( TTrack const *Left, TTrack const *Right ) { + + return ( ( Left->m_material1 < Right->m_material1 ) + && ( Left->m_material2 < Right->m_material2 ) ); +} + TTrack * TTrack::Create400m(int what, double dx) { // tworzenie toru do wstawiania taboru podczas konwersji na E3D - TGroundNode *tmp = new TGroundNode(TP_TRACK); // node - TTrack *trk = tmp->pTrack; - trk->bVisible = false; // nie potrzeba pokazywać, zresztą i tak nie ma tekstur + scene::node_data nodedata; + nodedata.name = "auto_400m"; // track isn't visible so only name is needed + auto *trk = new TTrack( nodedata ); + trk->m_visible = false; // nie potrzeba pokazywać, zresztą i tak nie ma tekstur trk->iCategoryFlag = what; // taki sam typ plus informacja, że dodatkowy trk->Init(); // utworzenie segmentu - trk->Segment->Init(vector3(-dx, 0, 0), vector3(-dx, 0, 400), 10.0, 0, 0); // prosty - tmp->pCenter = vector3(-dx, 0, 200); //środek, aby się mogło wyświetlić - TSubRect *r = Global::pGround->GetSubRect(tmp->pCenter.x, tmp->pCenter.z); - r->NodeAdd(tmp); // dodanie toru do segmentu - r->Sort(); //żeby wyświetlał tabor z dodanego toru + trk->Segment->Init( vector3( -dx, 0, 0 ), vector3( -dx, 0, 400 ), 10.0, 0, 0 ); // prosty + trk->location( glm::dvec3{ -dx, 0, 200 } ); //środek, aby się mogło wyświetlić + simulation::Paths.insert( trk ); + simulation::Region->insert_path( trk, scene::scratch_data() ); return trk; }; TTrack * TTrack::NullCreate(int dir) { // tworzenie toru wykolejającego od strony (dir), albo pętli dla samochodów - TGroundNode *tmp = new TGroundNode(TP_TRACK), *tmp2 = NULL; // node - TTrack *trk = tmp->pTrack; // tor; UWAGA! obrotnica może generować duże ilości tego - // tmp->iType=TP_TRACK; - // TTrack* trk=new TTrack(tmp); //tor; UWAGA! obrotnica może generować duże ilości tego - // tmp->pTrack=trk; - trk->bVisible = false; // nie potrzeba pokazywać, zresztą i tak nie ma tekstur - // trk->iTrapezoid=1; //są przechyłki do uwzględniania w rysowaniu + TTrack + *trk { nullptr }, + *trk2 { nullptr }; + scene::node_data nodedata; + nodedata.name = "auto_null"; // track isn't visible so only name is needed + trk = new TTrack( nodedata ); + trk->m_visible = false; // nie potrzeba pokazywać, zresztą i tak nie ma tekstur trk->iCategoryFlag = (iCategoryFlag & 15) | 0x80; // taki sam typ plus informacja, że dodatkowy - double r1, r2; + float r1, r2; Segment->GetRolls(r1, r2); // pobranie przechyłek na początku toru vector3 p1, cv1, cv2, p2; // będziem tworzyć trajektorię lotu if (iCategoryFlag & 1) @@ -228,25 +205,21 @@ TTrack * TTrack::NullCreate(int dir) case 0: p1 = Segment->FastGetPoint_0(); p2 = p1 - 450.0 * Normalize(Segment->GetDirection1()); - trk->Segment->Init(p1, p2, 5, -RadToDeg(r1), - 70.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce + // bo prosty, kontrolne wyliczane przy zmiennej przechyłce + trk->Segment->Init(p1, p2, 5, -RadToDeg(r1), 70.0); ConnectPrevPrev(trk, 0); break; case 1: p1 = Segment->FastGetPoint_1(); p2 = p1 - 450.0 * Normalize(Segment->GetDirection2()); - trk->Segment->Init(p1, p2, 5, RadToDeg(r2), - 70.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce + // bo prosty, kontrolne wyliczane przy zmiennej przechyłce + trk->Segment->Init(p1, p2, 5, RadToDeg(r2), 70.0); ConnectNextPrev(trk, 0); break; case 3: // na razie nie możliwe p1 = SwitchExtension->Segments[1]->FastGetPoint_1(); // koniec toru drugiego zwrotnicy - p2 = p1 - - 450.0 * - Normalize( - SwitchExtension->Segments[1]->GetDirection2()); // przedłużenie na wprost - trk->Segment->Init(p1, p2, 5, RadToDeg(r2), - 70.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce + p2 = p1 - 450.0 * Normalize( SwitchExtension->Segments[1]->GetDirection2()); // przedłużenie na wprost + trk->Segment->Init(p1, p2, 5, RadToDeg(r2), 70.0); // bo prosty, kontrolne wyliczane przy zmiennej przechyłce ConnectNextPrev(trk, 0); // trk->ConnectPrevNext(trk,dir); SetConnections(1); // skopiowanie połączeń @@ -259,17 +232,15 @@ TTrack * TTrack::NullCreate(int dir) trk->fVelocity = 20.0; // zawracanie powoli trk->fRadius = 20.0; // promień, aby się dodawało do tabelki prędkości i liczyło narastająco trk->Init(); // utworzenie segmentu - tmp2 = new TGroundNode(TP_TRACK); // drugi odcinek do zapętlenia - TTrack *trk2 = tmp2->pTrack; + trk2 = new TTrack( nodedata ); trk2->iCategoryFlag = (iCategoryFlag & 15) | 0x80; // taki sam typ plus informacja, że dodatkowy - trk2->bVisible = false; + trk2->m_visible = false; trk2->fVelocity = 20.0; // zawracanie powoli - trk2->fRadius = 20.0; // promień, aby się dodawało do tabelki prędkości i liczyło - // narastająco + trk2->fRadius = 20.0; // promień, aby się dodawało do tabelki prędkości i liczyło narastająco trk2->Init(); // utworzenie segmentu - trk->pMyNode->asName = pMyNode->asName + ":loopstart"; - trk2->pMyNode->asName = pMyNode->asName + ":loopfinish"; + trk->m_name = m_name + ":loopstart"; + trk2->m_name = m_name + ":loopfinish"; switch (dir) { //łączenie z nowym torem case 0: @@ -297,19 +268,15 @@ TTrack * TTrack::NullCreate(int dir) } trk2->trPrev = this; trk->ConnectNextNext(trk2, 1); // połączenie dwóch dodatkowych odcinków punktami 2 - tmp2->pCenter = (0.5 * (p1 + p2)); //środek, aby się mogło wyświetlić } // trzeba jeszcze dodać do odpowiedniego segmentu, aby się renderowały z niego pojazdy - tmp->pCenter = (0.5 * (p1 + p2)); //środek, aby się mogło wyświetlić - if (tmp2) - tmp2->pCenter = tmp->pCenter; // ten sam środek jest - // Ra: to poniżej to porażka, ale na razie się nie da inaczej - TSubRect *r = Global::pGround->GetSubRect(tmp->pCenter.x, tmp->pCenter.z); - if( r != nullptr ) { - r->NodeAdd( tmp ); // dodanie toru do segmentu - if( tmp2 ) - r->NodeAdd( tmp2 ); // drugiego też - r->Sort(); //żeby wyświetlał tabor z dodanego toru + trk->location( glm::dvec3{ 0.5 * ( p1 + p2 ) } ); //środek, aby się mogło wyświetlić + simulation::Paths.insert( trk ); + simulation::Region->insert_path( trk, scene::scratch_data() ); + if( trk2 ) { + trk2->location( trk->location() ); // ten sam środek jest + simulation::Paths.insert( trk2 ); + simulation::Region->insert_path( trk2, scene::scratch_data() ); } return trk; }; @@ -332,8 +299,8 @@ void TTrack::ConnectPrevNext(TTrack *pTrack, int typ) iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy pTrack->trNext = this; pTrack->iNextDirection = 0; - if (bVisible) - if (pTrack->bVisible) + if (m_visible) + if (pTrack->m_visible) if (eType == tt_Normal) // jeśli łączone są dwa normalne if (pTrack->eType == tt_Normal) if ((fTrackWidth != @@ -352,8 +319,8 @@ void TTrack::ConnectNextPrev(TTrack *pTrack, int typ) iNextDirection = ((pTrack->eType == tt_Switch) ? 0 : (typ & 2)); pTrack->trPrev = this; pTrack->iPrevDirection = 1; - if (bVisible) - if (pTrack->bVisible) + if (m_visible) + if (pTrack->m_visible) if (eType == tt_Normal) // jeśli łączone są dwa normalne if (pTrack->eType == tt_Normal) if ((fTrackWidth != @@ -375,25 +342,15 @@ void TTrack::ConnectNextNext(TTrack *pTrack, int typ) } } -vector3 MakeCPoint(vector3 p, double d, double a1, double a2) -{ - vector3 cp = vector3(0, 0, 1); - cp.RotateX(DegToRad(a2)); - cp.RotateY(DegToRad(a1)); - cp = cp * d + p; - return cp; -} - vector3 LoadPoint(cParser *parser) { // pobranie współrzędnych punktu vector3 p; - std::string token; parser->getTokens(3); *parser >> p.x >> p.y >> p.z; return p; } -void TTrack::Load(cParser *parser, vector3 pOrigin, std::string name) +void TTrack::Load(cParser *parser, vector3 pOrigin) { // pobranie obiektu trajektorii ruchu vector3 pt, vec, p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań double a1, a2, r1, r2, r3, r4; @@ -476,8 +433,8 @@ void TTrack::Load(cParser *parser, vector3 pOrigin, std::string name) } parser->getTokens(); *parser >> token; - bVisible = (token.compare("vis") == 0); // visible - if (bVisible) + m_visible = (token.compare("vis") == 0); // visible + if (m_visible) { parser->getTokens(); *parser >> str; // railtex @@ -496,7 +453,11 @@ void TTrack::Load(cParser *parser, vector3 pOrigin, std::string name) null_handle : GfxRenderer.Fetch_Material( str ) ); parser->getTokens(3); - *parser >> fTexHeight1 >> fTexWidth >> fTexSlope; + *parser + >> fTexHeight1 + >> fTexWidth + >> fTexSlope; + if (iCategoryFlag & 4) fTexHeight1 = -fTexHeight1; // rzeki mają wysokość odwrotnie niż drogi } @@ -730,24 +691,28 @@ void TTrack::Load(cParser *parser, vector3 pOrigin, std::string name) } else if (str == "angle1") { // kąt ścięcia końca od strony 1 + // NOTE: not used/implemented parser->getTokens(); *parser >> a1; - Segment->AngleSet(0, a1); + //Segment->AngleSet(0, a1); } else if (str == "angle2") { // kąt ścięcia końca od strony 2 + // NOTE: not used/implemented parser->getTokens(); *parser >> a2; - Segment->AngleSet(1, a2); + //Segment->AngleSet(1, a2); } else if (str == "fouling1") { // wskazanie modelu ukresu w kierunku 1 + // NOTE: not used/implemented parser->getTokens(); *parser >> token; // nFouling[0]= } else if (str == "fouling2") { // wskazanie modelu ukresu w kierunku 2 + // NOTE: not used/implemented parser->getTokens(); *parser >> token; // nFouling[1]= @@ -761,19 +726,26 @@ void TTrack::Load(cParser *parser, vector3 pOrigin, std::string name) // ograniczenie dla pantografujących) } else - ErrorLog("Unknown property: \"" + str + "\" in track \"" + name + "\""); + ErrorLog("Unknown property: \"" + str + "\" in track \"" + m_name + "\""); parser->getTokens(); *parser >> token; str = token; } // alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru if (!pIsolated) - if ((i = name.find("@")) != std::string::npos) - if (i < name.length()) // nie może być puste + if ((i = m_name.find("@")) != std::string::npos) + if (i < m_name.length()) // nie może być puste { - pIsolated = TIsolated::Find(name.substr(i + 1, name.length())); - name = name.substr(0, i - 1); // usunięcie z nazwy + pIsolated = TIsolated::Find(m_name.substr(i + 1, m_name.length())); + m_name = m_name.substr(0, i - 1); // usunięcie z nazwy } + + // calculate path location + m_area.center = ( glm::dvec3{ ( + CurrentSegment()->FastGetPoint_0() + + CurrentSegment()->FastGetPoint( 0.5 ) + + CurrentSegment()->FastGetPoint_1() ) + / 3.0 } ); } // TODO: refactor this mess @@ -783,7 +755,7 @@ bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent if( NewEvent0 == nullptr ) { if( false == asEvent0Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEvent0Name + "\" assigned to track \"" + pMyNode->asName + "\" does not exist" ); + ErrorLog( "Bad event: event \"" + asEvent0Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; } } @@ -801,7 +773,7 @@ bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent if( NewEvent1 == nullptr ) { if( false == asEvent1Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEvent1Name + "\" assigned to track \"" + pMyNode->asName + "\" does not exist" ); + ErrorLog( "Bad event: event \"" + asEvent1Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; } } @@ -819,7 +791,7 @@ bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent if( NewEvent2 == nullptr ) { if( false == asEvent2Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEvent2Name + "\" assigned to track \"" + pMyNode->asName + "\" does not exist" ); + ErrorLog( "Bad event: event \"" + asEvent2Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; } } @@ -844,7 +816,7 @@ bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEv if( NewEvent0 == nullptr ) { if( false == asEventall0Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEventall0Name + "\" assigned to track \"" + pMyNode->asName + "\" does not exist" ); + ErrorLog( "Bad event: event \"" + asEventall0Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; } } @@ -862,7 +834,7 @@ bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEv if( NewEvent1 == nullptr ) { if( false == asEventall1Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEventall1Name + "\" assigned to track \"" + pMyNode->asName + "\" does not exist" ); + ErrorLog( "Bad event: event \"" + asEventall1Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; } } @@ -880,7 +852,7 @@ bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEv if( NewEvent2 == nullptr ) { if( false == asEventall2Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEventall2Name + "\" assigned to track \"" + pMyNode->asName + "\" does not exist" ); + ErrorLog( "Bad event: event \"" + asEventall2Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; } } @@ -933,7 +905,7 @@ bool TTrack::IsolatedEventsAssign(TEvent *busy, TEvent *free) return false; }; -// ABu: przeniesione z Track.h i poprawione!!! +// ABu: przeniesione z Path.h i poprawione!!! bool TTrack::AddDynamicObject(TDynamicObject *Dynamic) { // dodanie pojazdu do trajektorii // Ra: tymczasowo wysyłanie informacji o zajętości konkretnego toru @@ -947,9 +919,9 @@ bool TTrack::AddDynamicObject(TDynamicObject *Dynamic) // jeśli multiplayer if( true == Dynamics.empty() ) { // pierwszy zajmujący - if( pMyNode->asName != "none" ) { + if( m_name != "none" ) { // przekazanie informacji o zajętości toru - Global::pGround->WyslijString( pMyNode->asName, 8 ); + multiplayer::WyslijString( m_name, 8 ); } } } @@ -965,37 +937,39 @@ bool TTrack::AddDynamicObject(TDynamicObject *Dynamic) const int numPts = 4; const int nnumPts = 12; -const vector6 szyna[nnumPts] = // szyna - vextor6(x,y,mapowanie tekstury,xn,yn,zn) - { // tę wersję opracował Tolein (bez pochylenia) - vector6(0.111, -0.180, 0.00, 1.000, 0.000, 0.000), - vector6(0.046, -0.150, 0.15, 0.707, 0.707, 0.000), - vector6(0.044, -0.050, 0.25, 0.707, -0.707, 0.000), - vector6(0.073, -0.038, 0.35, 0.707, -0.707, 0.000), - vector6(0.072, -0.010, 0.40, 0.707, 0.707, 0.000), - vector6(0.052, -0.000, 0.45, 0.000, 1.000, 0.000), - vector6(0.020, -0.000, 0.55, 0.000, 1.000, 0.000), - vector6(0.000, -0.010, 0.60, -0.707, 0.707, 0.000), - vector6(-0.001, -0.038, 0.65, -0.707, -0.707, 0.000), - vector6(0.028, -0.050, 0.75, -0.707, -0.707, 0.000), - vector6(0.026, -0.150, 0.85, -0.707, 0.707, 0.000), - vector6(-0.039, -0.180, 1.00, -1.000, 0.000, 0.000)}; +// szyna - vextor6(x,y,mapowanie tekstury,xn,yn,zn) +// tę wersję opracował Tolein (bez pochylenia) +// TODO: profile definitions in external files +basic_vertex const szyna[ nnumPts ] = { + {{ 0.111f, -0.180f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.00f, 0.f}}, + {{ 0.046f, -0.150f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.15f, 0.f}}, + {{ 0.044f, -0.050f, 0.f}, { 0.707f, -0.707f, 0.f}, {0.25f, 0.f}}, + {{ 0.073f, -0.038f, 0.f}, { 0.707f, -0.707f, 0.f}, {0.35f, 0.f}}, + {{ 0.072f, -0.010f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.40f, 0.f}}, + {{ 0.052f, -0.000f, 0.f}, { 0.000f, 1.000f, 0.f}, {0.45f, 0.f}}, + {{ 0.020f, -0.000f, 0.f}, { 0.000f, 1.000f, 0.f}, {0.55f, 0.f}}, + {{ 0.000f, -0.010f, 0.f}, {-0.707f, 0.707f, 0.f}, {0.60f, 0.f}}, + {{-0.001f, -0.038f, 0.f}, {-0.707f, -0.707f, 0.f}, {0.65f, 0.f}}, + {{ 0.028f, -0.050f, 0.f}, {-0.707f, -0.707f, 0.f}, {0.75f, 0.f}}, + {{ 0.026f, -0.150f, 0.f}, {-0.707f, 0.707f, 0.f}, {0.85f, 0.f}}, + {{-0.039f, -0.180f, 0.f}, {-1.000f, 0.000f, 0.f}, {1.00f, 0.f}} }; -const vector6 iglica[nnumPts] = // iglica - vextor3(x,y,mapowanie tekstury) - { - vector6(0.010, -0.180, 0.00, 1.000, 0.000, 0.000), - vector6(0.010, -0.155, 0.15, 1.000, 0.000, 0.000), - vector6(0.010, -0.070, 0.25, 1.000, 0.000, 0.000), - vector6(0.010, -0.040, 0.35, 1.000, 0.000, 0.000), - vector6(0.010, -0.010, 0.40, 1.000, 0.000, 0.000), - vector6(0.010, -0.000, 0.45, 0.707, 0.707, 0.000), - vector6(0.000, -0.000, 0.55, 0.707, 0.707, 0.000), - vector6(0.000, -0.010, 0.60, -1.000, 0.000, 0.000), - vector6(0.000, -0.040, 0.65, -1.000, 0.000, 0.000), - vector6(0.000, -0.070, 0.75, -1.000, 0.000, 0.000), - vector6(0.000, -0.155, 0.85, -0.707, 0.707, 0.000), - vector6(-0.040, -0.180, 1.00, -1.000, 0.000, - 0.000) // 1mm więcej, żeby nie nachodziły tekstury? -}; +// iglica - vextor3(x,y,mapowanie tekstury) +// 1 mm więcej, żeby nie nachodziły tekstury? +// TODO: automatic generation from base profile TBD: reuse base profile? +basic_vertex const iglica[ nnumPts ] = { + {{ 0.010f, -0.180f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.00f, 0.f}}, + {{ 0.010f, -0.155f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.15f, 0.f}}, + {{ 0.010f, -0.070f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.25f, 0.f}}, + {{ 0.010f, -0.040f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.35f, 0.f}}, + {{ 0.010f, -0.010f, 0.f}, { 1.000f, 0.000f, 0.f}, {0.40f, 0.f}}, + {{ 0.010f, -0.000f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.45f, 0.f}}, + {{ 0.000f, -0.000f, 0.f}, { 0.707f, 0.707f, 0.f}, {0.55f, 0.f}}, + {{ 0.000f, -0.010f, 0.f}, {-1.000f, 0.000f, 0.f}, {0.60f, 0.f}}, + {{ 0.000f, -0.040f, 0.f}, {-1.000f, 0.000f, 0.f}, {0.65f, 0.f}}, + {{ 0.000f, -0.070f, 0.f}, {-1.000f, 0.000f, 0.f}, {0.75f, 0.f}}, + {{ 0.000f, -0.155f, 0.f}, {-0.707f, 0.707f, 0.f}, {0.85f, 0.f}}, + {{-0.040f, -0.180f, 0.f}, {-1.000f, 0.000f, 0.f}, {1.00f, 0.f}} }; bool TTrack::CheckDynamicObject(TDynamicObject *Dynamic) { // sprawdzenie, czy pojazd jest przypisany do toru @@ -1037,9 +1011,9 @@ bool TTrack::RemoveDynamicObject(TDynamicObject *Dynamic) // jeśli multiplayer if( true == Dynamics.empty() ) { // jeśli już nie ma żadnego - if( pMyNode->asName != "none" ) { + if( m_name != "none" ) { // przekazanie informacji o zwolnieniu toru - Global::pGround->WyslijString( pMyNode->asName, 9 ); + multiplayer::WyslijString( m_name, 9 ); } } } @@ -1073,12 +1047,11 @@ bool TTrack::InMovement() return false; }; -void TTrack::RaAssign(TGroundNode *gn, TAnimModel *am, TEvent *done, TEvent *joined) +void TTrack::RaAssign( TAnimModel *am, TEvent *done, TEvent *joined ) { // Ra: wiązanie toru z modelem obrotnicy if (eType == tt_Table) { SwitchExtension->pModel = am; - SwitchExtension->pMyNode = gn; SwitchExtension->evMinus = done; // event zakończenia animacji (zadanie nowej przedłuża) SwitchExtension->evPlus = joined; // event potwierdzenia połączenia (gdy nie znajdzie, to się nie połączy) @@ -1091,30 +1064,40 @@ void TTrack::RaAssign(TGroundNode *gn, TAnimModel *am, TEvent *done, TEvent *joi // wypełnianie tablic VBO void TTrack::create_geometry( geometrybank_handle const &Bank ) { // Ra: trzeba rozdzielić szyny od podsypki, aby móc grupować wg tekstur - double const fHTW = 0.5 * std::fabs(fTrackWidth); - double const side = std::fabs(fTexWidth); // szerokść podsypki na zewnątrz szyny albo pobocza - double const slop = std::fabs(fTexSlope); // brzeg zewnętrzny - double const rozp = fHTW + side + slop; // brzeg zewnętrzny - double hypot1 = std::hypot(slop, fTexHeight1); // rozmiar pochylenia do liczenia normalnych - if (hypot1 == 0.0) - hypot1 = 1.0; - vector3 normal1 { fTexSlope / hypot1, fTexHeight1 / hypot1, 0.0 }; // wektor normalny - double fHTW2, side2, slop2, rozp2, fTexHeight2, hypot2; - vector3 normal2; - if (iTrapezoid & 2) // ten bit oznacza, że istnieje odpowiednie pNext - { // Ra: jest OK - fHTW2 = 0.5 * std::fabs(trNext->fTrackWidth); // połowa rozstawu/nawierzchni + auto const fHTW = 0.5f * std::abs(fTrackWidth); + auto const side = std::abs(fTexWidth); // szerokść podsypki na zewnątrz szyny albo pobocza + auto const slop = std::abs(fTexSlope); // brzeg zewnętrzny + auto const rozp = fHTW + side + slop; // brzeg zewnętrzny + auto hypot1 = std::hypot(slop, fTexHeight1); // rozmiar pochylenia do liczenia normalnych + if( hypot1 == 0.f ) + hypot1 = 1.f; + glm::vec3 const normalup{ 0.f, 1.f, 0.f }; + glm::vec3 normal1 { fTexHeight1 / hypot1, fTexSlope / hypot1, 0.f }; // wektor normalny + if( glm::length( normal1 ) == 0.f ) { + // fix normal for vertical surfaces + normal1 = glm::vec3 { 1.f, 0.f, 0.f }; + } + glm::vec3 normal2; + float fHTW2, side2, slop2, rozp2, fTexHeight2, hypot2; + if( iTrapezoid & 2 ) { + // ten bit oznacza, że istnieje odpowiednie pNext + // Ra: jest OK + fHTW2 = 0.5f * std::fabs(trNext->fTrackWidth); // połowa rozstawu/nawierzchni side2 = std::fabs(trNext->fTexWidth); slop2 = std::fabs(trNext->fTexSlope); // nie jest używane później rozp2 = fHTW2 + side2 + slop2; fTexHeight2 = trNext->fTexHeight1; hypot2 = std::hypot(slop2, fTexHeight2); - if (hypot2 == 0.0) - hypot2 = 1.0; - normal2 = vector3(trNext->fTexSlope / hypot2, fTexHeight2 / hypot2, 0.0); + if( hypot2 == 0.f ) + hypot2 = 1.f; + normal2 = { fTexHeight2 / hypot2, trNext->fTexSlope / hypot2, 0.f }; + if( glm::length( normal2 ) == 0.f ) { + // fix normal for vertical surfaces + normal2 = glm::vec3 { 1.f, 0.f, 0.f }; + } } - else // gdy nie ma następnego albo jest nieodpowiednim końcem podpięty - { + else { + // gdy nie ma następnego albo jest nieodpowiednim końcem podpięty fHTW2 = fHTW; side2 = side; slop2 = slop; @@ -1124,9 +1107,7 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { normal2 = normal1; } - auto const origin { pMyNode->m_rootposition }; - - double roll1, roll2; + float roll1, roll2; switch (iCategoryFlag & 15) { case 1: // tor @@ -1135,111 +1116,206 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { Segment->GetRolls(roll1, roll2); else roll1 = roll2 = 0.0; // dla zwrotnic - double sin1 = std::sin(roll1), cos1 = std::cos(roll1), sin2 = std::sin(roll2), cos2 = std::cos(roll2); + float const + sin1 = std::sin(roll1), + cos1 = std::cos(roll1), + sin2 = std::sin(roll2), + cos2 = std::cos(roll2); // zwykla szyna: //Ra: czemu główki są asymetryczne na wysokości 0.140? - vector6 rpts1[24], rpts2[24], rpts3[24], rpts4[24]; - int i; - for (i = 0; i < 12; ++i) - { - rpts1[i] = vector6( - (fHTW + szyna[i].x) * cos1 + szyna[i].y * sin1, - -(fHTW + szyna[i].x) * sin1 + szyna[i].y * cos1, - szyna[i].z, - szyna[i].n.x * cos1 + szyna[i].n.y * sin1, - -szyna[i].n.x * sin1 + szyna[i].n.y * cos1, - 0.0); - rpts2[11 - i] = vector6( - (-fHTW - szyna[i].x) * cos1 + szyna[i].y * sin1, - -(-fHTW - szyna[i].x) * sin1 + szyna[i].y * cos1, - szyna[i].z, - -szyna[i].n.x * cos1 + szyna[i].n.y * sin1, - szyna[i].n.x * sin1 + szyna[i].n.y * cos1, - 0.0); + basic_vertex rpts1[24], rpts2[24], rpts3[24], rpts4[24]; + for( int i = 0; i < 12; ++i ) { + + rpts1[ i ] = { + // position + {( fHTW + szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1, + -( fHTW + szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1, + szyna[ i ].position.z}, + // normal + { szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1, + -szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; + + rpts2[ 11 - i ] = { + // position + {(-fHTW - szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1, + -(-fHTW - szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1, + szyna[ i ].position.z}, + // normal + {-szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1, + szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; + + if( false == iTrapezoid ) { continue; } + // trapez albo przechyłki, to oddzielne punkty na końcu + + rpts1[ 12 + i ] = { + // position + {( fHTW + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( fHTW + szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2, + szyna[ i ].position.z}, + // normal + { szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2, + -szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; + + rpts2[ 23 - i ] = { + // position + {(-fHTW - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -(-fHTW - szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2, + szyna[ i ].position.z}, + // normal + {-szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2, + szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; } - if (iTrapezoid) // trapez albo przechyłki, to oddzielne punkty na końcu - for (i = 0; i < 12; ++i) - { - rpts1[12 + i] = vector6( - (fHTW2 + szyna[i].x) * cos2 + szyna[i].y * sin2, - -(fHTW2 + szyna[i].x) * sin2 + szyna[i].y * cos2, - szyna[i].z, - szyna[i].n.x * cos2 + szyna[i].n.y * sin2, - -szyna[i].n.x * sin2 + szyna[i].n.y * cos2, - 0.0); - rpts2[23 - i] = vector6( - (-fHTW2 - szyna[i].x) * cos2 + szyna[i].y * sin2, - -(-fHTW2 - szyna[i].x) * sin2 + szyna[i].y * cos2, - szyna[i].z, - -szyna[i].n.x * cos2 + szyna[i].n.y * sin2, - szyna[i].n.x * sin2 + szyna[i].n.y * cos2, - 0.0); - } switch (eType) // dalej zależnie od typu { case tt_Table: // obrotnica jak zwykły tor, tylko animacja dochodzi case tt_Normal: if (m_material2) { // podsypka z podkładami jest tylko dla zwykłego toru - vector6 bpts1[8]; // punkty głównej płaszczyzny nie przydają się do robienia boków - if (fTexLength == 4.0) // jeśli stare mapowanie - { // stare mapowanie z różną gęstością pikseli i oddzielnymi teksturami na każdy profil - if (iTrapezoid) // trapez albo przechyłki - { // podsypka z podkladami trapezowata + basic_vertex bpts1[ 8 ]; // punkty głównej płaszczyzny nie przydają się do robienia boków + if( fTexLength == 4.f ) { + // stare mapowanie z różną gęstością pikseli i oddzielnymi teksturami na każdy profil + auto const normalx = std::cos( glm::radians( 75.f ) ); + auto const normaly = std::sin( glm::radians( 75.f ) ); + if( iTrapezoid ) { + // trapez albo przechyłki // ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671 ((0.3464,0.6536) // bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu - bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.00, -0.707, 0.707, 0.0); // lewy brzeg - bpts1[1] = vector6((fHTW + side) * cos1, -(fHTW + side) * sin1 - 0.18, 0.33, -0.707, 0.707, 0.0); // krawędź załamania - bpts1[2] = vector6(-bpts1[1].x, +(fHTW + side) * sin1 - 0.18, 0.67, 0.707, 0.707, 0.0); // prawy brzeg początku symetrycznie - bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 1.00, 0.707, 0.707, 0.0); // prawy skos + bpts1[ 0 ] = { + {rozp, -fTexHeight1 - 0.18f, 0.f}, + {normalx, normaly, 0.f}, + {0.00f, 0.f} }; // lewy brzeg + bpts1[ 1 ] = { + {( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - 0.18f, 0.f}, + {normalx, normaly, 0.f}, + {0.33f, 0.f} }; // krawędź załamania + bpts1[ 2 ] = { + {-bpts1[ 1 ].position.x, +( fHTW + side ) * sin1 - 0.18f, 0.f}, + {-normalx, normaly, 0.f}, + {0.67f, 0.f} }; // prawy brzeg początku symetrycznie + bpts1[ 3 ] = { + {-rozp, -fTexHeight1 - 0.18f, 0.f}, + {-normalx, normaly, 0.f}, + {1.f, 0.f} }; // prawy skos // końcowy przekrój - bpts1[4] = vector6(rozp2, -fTexHeight2 - 0.18, 0.00, -0.707, 0.707, 0.0); // lewy brzeg - bpts1[5] = vector6((fHTW2 + side2) * cos2, -(fHTW2 + side2) * sin2 - 0.18, 0.33, -0.707, 0.707, 0.0); // krawędź załamania - bpts1[6] = vector6(-bpts1[5].x, +(fHTW2 + side2) * sin2 - 0.18, 0.67, 0.707, 0.707, 0.0); // prawy brzeg początku symetrycznie - bpts1[7] = vector6(-rozp2, -fTexHeight2 - 0.18, 1.00, 0.707, 0.707, 0.0); // prawy skos + bpts1[ 4 ] = { + {rozp2, -fTexHeight2 - 0.18f, 0.f}, + {normalx, normaly, 0.f}, + {0.00f, 0.f} }; // lewy brzeg + bpts1[ 5 ] = { + {( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, + {normalx, normaly, 0.f}, + {0.33f, 0.f} }; // krawędź załamania + bpts1[ 6 ] = { + {-bpts1[ 5 ].position.x, +( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, + {-normalx, normaly, 0.f}, + {0.67f, 0.f} }; // prawy brzeg początku symetrycznie + bpts1[ 7 ] = { + {-rozp2, -fTexHeight2 - 0.18f, 0.f}, + {-normalx, normaly, 0.f}, + {1.00f, 0.f} }; // prawy skos } - else - { - bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.0, -0.707, 0.707, 0.0); // lewy brzeg - bpts1[1] = vector6(fHTW + side, -0.18, 0.33, -0.707, 0.707, 0.0); // krawędź załamania - bpts1[2] = vector6(-fHTW - side, -0.18, 0.67, 0.707, 0.707, 0.0); // druga - bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 1.0, 0.707, 0.707, 0.0); // prawy skos + else { + bpts1[ 0 ] = { + {rozp, -fTexHeight1 - 0.18f, 0.f}, + {normalx, normaly, 0.f}, + {0.00f, 0.f} }; // lewy brzeg + bpts1[ 1 ] = { + {fHTW + side, -0.18f, 0.f}, + {normalx, normaly, 0.f}, + {0.33f, 0.f} }; // krawędź załamania + bpts1[ 2 ] = { + {-fHTW - side, -0.18f, 0.f}, + {-normalx, normaly, 0.f}, + {0.67f, 0.f} }; // druga + bpts1[ 3 ] = { + {-rozp, -fTexHeight1 - 0.18f, 0.f}, + {-normalx, normaly, 0.f}, + {1.00f, 0.f} }; // prawy skos } } - else - { // mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa fTexLength - double max = fTexRatio2 * fTexLength; // szerokość proporcjonalna do długości - double map11 = max > 0.0 ? (fHTW + side) / max : 0.25; // załamanie od strony 1 - double map12 = - max > 0.0 ? (fHTW + side + hypot1) / max : 0.5; // brzeg od strony 1 - if (iTrapezoid) // trapez albo przechyłki - { // podsypka z podkladami trapezowata - double map21 = - max > 0.0 ? (fHTW2 + side2) / max : 0.25; // załamanie od strony 2 - double map22 = - max > 0.0 ? (fHTW2 + side2 + hypot2) / max : 0.5; // brzeg od strony 2 + else { + // mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa fTexLength + auto const max = fTexRatio2 * fTexLength; // szerokość proporcjonalna do długości + auto const map11 = max > 0.f ? (fHTW + side) / max : 0.25f; // załamanie od strony 1 + auto const map12 = max > 0.f ? (fHTW + side + hypot1) / max : 0.5f; // brzeg od strony 1 + if (iTrapezoid) { + // trapez albo przechyłki + auto const map21 = max > 0.f ? (fHTW2 + side2) / max : 0.25f; // załamanie od strony 2 + auto const map22 = max > 0.f ? (fHTW2 + side2 + hypot2) / max : 0.5f; // brzeg od strony 2 // ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671 // ((0.3464,0.6536) // bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu - bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.5 - map12, normal1.x, -normal1.y, 0.0); // lewy brzeg - bpts1[1] = vector6((fHTW + side) * cos1, -(fHTW + side) * sin1 - 0.18, 0.5 - map11, 0.0, 1.0, 0.0); // krawędź załamania - bpts1[2] = vector6(-bpts1[1].x, +(fHTW + side) * sin1 - 0.18, 0.5 + map11, 0.0, 1.0, 0.0); // prawy brzeg początku symetrycznie - bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 0.5 + map12, -normal1.x, -normal1.y, 0.0); // prawy skos + bpts1[ 0 ] = { + {rozp, -fTexHeight1 - 0.18f, 0.f}, + {normal1.x, normal1.y, 0.f}, + {0.5f - map12, 0.f} }; // lewy brzeg + bpts1[ 1 ] = { + {( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - 0.18f, 0.f}, + {normal1.x, normal1.y, 0.f}, + {0.5f - map11 , 0.f} }; // krawędź załamania + bpts1[ 2 ] = { + {-bpts1[ 1 ].position.x, +( fHTW + side ) * sin1 - 0.18f, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5 + map11, 0.f} }; // prawy brzeg początku symetrycznie + bpts1[ 3 ] = { + {-rozp, -fTexHeight1 - 0.18f, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5f + map12, 0.f} }; // prawy skos // przekrój końcowy - bpts1[4] = vector6(rozp2, -fTexHeight2 - 0.18, 0.5 - map22, normal2.x, -normal2.y, 0.0); // lewy brzeg - bpts1[5] = vector6((fHTW2 + side2) * cos2, -(fHTW2 + side2) * sin2 - 0.18, 0.5 - map21, 0.0, 1.0, 0.0); // krawędź załamania - bpts1[6] = vector6(-bpts1[5].x, +(fHTW2 + side2) * sin2 - 0.18, 0.5 + map21, 0.0, 1.0, 0.0); // prawy brzeg początku symetrycznie - bpts1[7] = vector6(-rozp2, -fTexHeight2 - 0.18, 0.5 + map22, -normal2.x, -normal2.y, 0.0); // prawy skos + bpts1[ 4 ] = { + {rozp2, -fTexHeight2 - 0.18f, 0.f}, + {normal2.x, normal2.y, 0.f}, + {0.5f - map22, 0.f} }; // lewy brzeg + bpts1[ 5 ] = { + {( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, + {normal2.x, normal2.y, 0.f}, + {0.5f - map21 , 0.f} }; // krawędź załamania + bpts1[ 6 ] = { + {-bpts1[ 5 ].position.x, +( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, + {-normal2.x, normal2.y, 0.f}, + {0.5f + map21, 0.f} }; // prawy brzeg początku symetrycznie + bpts1[ 7 ] = { + {-rozp2, -fTexHeight2 - 0.18f, 0.f}, + {-normal2.x, normal2.y, 0.f}, + {0.5f + map22, 0.f} }; // prawy skos } else { - bpts1[0] = vector6(rozp, -fTexHeight1 - 0.18, 0.5 - map12, +normal1.x, -normal1.y, 0.0); // lewy brzeg - bpts1[1] = vector6(fHTW + side, -0.18, 0.5 - map11, +normal1.x, -normal1.y, 0.0); // krawędź załamania - bpts1[2] = vector6(-fHTW - side, -0.18, 0.5 + map11, -normal1.x, -normal1.y, 0.0); // druga - bpts1[3] = vector6(-rozp, -fTexHeight1 - 0.18, 0.5 + map12, -normal1.x, -normal1.y, 0.0); // prawy skos + bpts1[ 0 ] = { + {rozp, -fTexHeight1 - 0.18f, 0.f}, + {+normal1.x, normal1.y, 0.f}, + {0.5f - map12, 0.f} }; // lewy brzeg + bpts1[ 1 ] = { + {fHTW + side, - 0.18f, 0.f}, + {+normal1.x, normal1.y, 0.f}, + {0.5f - map11, 0.f} }; // krawędź załamania + bpts1[ 2 ] = { + {-fHTW - side, - 0.18f, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5f + map11, 0.f} }; // druga + bpts1[ 3 ] = { + {-rozp, -fTexHeight1 - 0.18f, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5f + map12, 0.f} }; // prawy skos } } vertex_array vertices; - Segment->RenderLoft(vertices, origin, bpts1, iTrapezoid ? -4 : 4, fTexLength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -4 : 4, fTexLength); if( ( Bank != 0 ) && ( true == Geometry2.empty() ) ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } @@ -1252,48 +1328,52 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { { // szyny - generujemy dwie, najwyżej rysować się będzie jedną vertex_array vertices; if( ( Bank != 0 ) && ( true == Geometry1.empty() ) ) { - Segment->RenderLoft( vertices, origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // reuse the scratchpad - Segment->RenderLoft( vertices, origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if( ( Bank == 0 ) && ( false == Geometry1.empty() ) ) { // special variant, replace existing data for a turntable track - Segment->RenderLoft( vertices, origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); GfxRenderer.Replace( vertices, Geometry1[ 0 ] ); vertices.clear(); // reuse the scratchpad - Segment->RenderLoft( vertices, origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); GfxRenderer.Replace( vertices, Geometry1[ 1 ] ); } } break; case tt_Switch: // dla zwrotnicy dwa razy szyny - if( m_material1 || m_material2 ) - { // iglice liczone tylko dla zwrotnic - vector6 rpts3[24], rpts4[24]; - for (i = 0; i < 12; ++i) - { - rpts3[i] = - vector6( - +( fHTW + iglica[i].x) * cos1 + iglica[i].y * sin1, - -(+fHTW + iglica[i].x) * sin1 + iglica[i].y * cos1, - iglica[i].z); - rpts3[i + 12] = - vector6( - +( fHTW2 + szyna[i].x) * cos2 + szyna[i].y * sin2, - -(+fHTW2 + szyna[i].x) * sin2 + iglica[i].y * cos2, - szyna[i].z); - rpts4[11 - i] = - vector6( - (-fHTW - iglica[i].x) * cos1 + iglica[i].y * sin1, - -(-fHTW - iglica[i].x) * sin1 + iglica[i].y * cos1, - iglica[i].z); - rpts4[23 - i] = - vector6( - (-fHTW2 - szyna[i].x) * cos2 + szyna[i].y * sin2, - -(-fHTW2 - szyna[i].x) * sin2 + iglica[i].y * cos2, - szyna[i].z); + if( m_material1 || m_material2 ) { + // iglice liczone tylko dla zwrotnic + basic_vertex rpts3[24], rpts4[24]; + for( int i = 0; i < 12; ++i ) { + + rpts3[ i ] = { + {+( fHTW + iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, + -( fHTW + iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, + 0.f}, + {iglica[ i ].normal}, + {iglica[ i ].texture.x, 0.f} }; + rpts3[ i + 12 ] = { + {+( fHTW2 + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( fHTW2 + szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, + 0.f}, + {szyna[ i ].normal}, + {szyna[ i ].texture.x, 0.f} }; + rpts4[ 11 - i ] = { + { ( -fHTW - iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, + -( -fHTW - iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, + 0.f}, + {iglica[ i ].normal}, + {iglica[ i ].texture.x, 0.f} }; + rpts4[ 23 - i ] = { + { ( -fHTW2 - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( -fHTW2 - szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, + 0.f}, + {szyna[ i ].normal}, + {szyna[ i ].texture.x, 0.f} }; } // TODO, TBD: change all track geometry to triangles, to allow packing data in less, larger buffers if (SwitchExtension->RightSwitch) @@ -1301,27 +1381,27 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { vertex_array vertices; if( m_material1 ) { // fixed parts - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts2, nnumPts, fTexLength ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts1, nnumPts, fTexLength, 1.0, 2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, 2 ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // left blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, SwitchExtension->fOffset2 ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( m_material2 ) { // fixed parts - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts1, nnumPts, fTexLength ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts2, nnumPts, fTexLength, 1.0, 2 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, 2 ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // right blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -fMaxOffset + SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -fMaxOffset + SwitchExtension->fOffset1 ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1331,27 +1411,27 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { vertex_array vertices; if( m_material1 ) { // fixed parts - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts1, nnumPts, fTexLength ); // lewa szyna normalna cała + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength ); // lewa szyna normalna cała Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts2, nnumPts, fTexLength, 1.0, 2 ); // prawa szyna za iglicą + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, 2 ); // prawa szyna za iglicą Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // right blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -SwitchExtension->fOffset2 ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( m_material2 ) { // fixed parts - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts2, nnumPts, fTexLength ); // prawa szyna normalnie cała + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength ); // prawa szyna normalnie cała Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts1, nnumPts, fTexLength, 1.0, 2 ); // lewa szyna za iglicą + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, 2 ); // lewa szyna za iglicą Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // left blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, fMaxOffset - SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, fMaxOffset - SwitchExtension->fOffset1 ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1366,105 +1446,207 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { { case tt_Normal: // drogi proste, bo skrzyżowania osobno { - vector6 bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków - if (m_material1 || m_material2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma - { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100% - double max = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości - double map1 = max > 0.0 ? fHTW / max : 0.5; // obcięcie tekstury od strony 1 - double map2 = max > 0.0 ? fHTW2 / max : 0.5; // obcięcie tekstury od strony 2 - if (iTrapezoid) // trapez albo przechyłki - { // nawierzchnia trapezowata + basic_vertex bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków + if (m_material1 || m_material2) { + // punkty się przydadzą, nawet jeśli nawierzchni nie ma +/* + double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100% +*/ + auto const max = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości + auto const map1 = max > 0.f ? fHTW / max : 0.5f; // obcięcie tekstury od strony 1 + auto const map2 = max > 0.f ? fHTW2 / max : 0.5f; // obcięcie tekstury od strony 2 + if (iTrapezoid) { + // trapez albo przechyłki Segment->GetRolls(roll1, roll2); - bpts1[0] = vector6(fHTW * cos(roll1), -fHTW * sin(roll1), 0.5 - map1); // lewy brzeg początku - bpts1[1] = vector6(-bpts1[0].x, -bpts1[0].y, 0.5 + map1); // prawy brzeg początku symetrycznie - bpts1[2] = vector6(fHTW2 * cos(roll2), -fHTW2 * sin(roll2), 0.5 - map2); // lewy brzeg końca - bpts1[3] = vector6(-bpts1[2].x, -bpts1[2].y, 0.5 + map2); // prawy brzeg początku symetrycznie + bpts1[ 0 ] = { + {fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f}, + normalup, + {0.5f - map1, 0.f} }; // lewy brzeg początku + bpts1[ 1 ] = { + {-bpts1[ 0 ].position.x, -bpts1[ 0 ].position.y, 0.f}, + normalup, + {0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie + bpts1[ 2 ] = { + {fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f}, + normalup, + {0.5f - map2, 0.f} }; // lewy brzeg końca + bpts1[ 3 ] = { + {-bpts1[ 2 ].position.x, -bpts1[ 2 ].position.y, 0.f}, + normalup, + {0.5f + map2, 0.f} }; // prawy brzeg początku symetrycznie } else { - bpts1[ 0 ] = vector6( fHTW, 0.0, 0.5 - map1, 0.0, 1.0, 0.0 ); - bpts1[ 1 ] = vector6( -fHTW, 0.0, 0.5 + map1, 0.0, 1.0, 0.0 ); + bpts1[ 0 ] = { + {fHTW, 0.f, 0.f}, + normalup, + {0.5f - map1, 0.f} }; + bpts1[ 1 ] = { + {-fHTW, 0.f, 0.f}, + normalup, + {0.5f + map1, 0.f} }; } } if (m_material1) // jeśli podana była tekstura, generujemy trójkąty { // tworzenie trójkątów nawierzchni szosy vertex_array vertices; - Segment->RenderLoft(vertices, origin, bpts1, iTrapezoid ? -2 : 2, fTexLength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -2 : 2, fTexLength); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if (m_material2) { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?) - vector6 + basic_vertex rpts1[6], rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony - if (fTexHeight1 >= 0.0) + if (fTexHeight1 >= 0.f) { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo - rpts1[0] = vector6(rozp, -fTexHeight1, 0.0); // lewy brzeg podstawy - rpts1[1] = vector6(bpts1[0].x + side, bpts1[0].y, 0.5), // lewa krawędź załamania - rpts1[2] = vector6(bpts1[0].x, bpts1[0].y, 1.0); // lewy brzeg pobocza (mapowanie może być inne - rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 1.0); // prawy brzeg pobocza - rpts2[1] = vector6(bpts1[1].x - side, bpts1[1].y, 0.5); // prawa krawędź załamania - rpts2[2] = vector6(-rozp, -fTexHeight1, 0.0); // prawy brzeg podstawy - if (iTrapezoid) // trapez albo przechyłki - { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - rpts1[3] = vector6(rozp2, -fTexHeight2, 0.0); // lewy brzeg lewego pobocza - rpts1[4] = vector6(bpts1[2].x + side2, bpts1[2].y, 0.5); // krawędź załamania - rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 1.0); // brzeg pobocza - rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 1.0); - rpts2[4] = vector6(bpts1[3].x - side2, bpts1[3].y, 0.5); - rpts2[5] = vector6(-rozp2, -fTexHeight2, 0.0); // prawy brzeg prawego pobocza + rpts1[ 0 ] = { + {rozp, -fTexHeight1, 0.f}, + { 1.f, 0.f, 0.f }, + {0.f, 0.f} }; // lewy brzeg podstawy + rpts1[ 1 ] = { + {bpts1[ 0 ].position.x + side, bpts1[ 0 ].position.y, 0.f}, + normalup, + {0.5, 0.f} }; // lewa krawędź załamania + rpts1[ 2 ] = { + {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne + rpts2[ 0 ] = { + {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // prawy brzeg pobocza + rpts2[ 1 ] = { + {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // prawa krawędź załamania + rpts2[ 2 ] = { + {-rozp, -fTexHeight1, 0.f}, + { -1.f, 0.f, 0.f }, + {0.f, 0.f} }; // prawy brzeg podstawy + if (iTrapezoid) { + // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka + rpts1[ 3 ] = { + {rozp2, -fTexHeight2, 0.f}, + { 1.f, 0.f, 0.f }, + {0.f, 0.f} }; // lewy brzeg lewego pobocza + rpts1[ 4 ] = { + {bpts1[ 2 ].position.x + side2, bpts1[ 2 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // krawędź załamania + rpts1[ 5 ] = { + {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // brzeg pobocza + rpts2[ 3 ] = { + {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; + rpts2[ 4 ] = { + {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; + rpts2[ 5 ] = { + {-rozp2, -fTexHeight2, 0.f}, + { -1.f, 0.f, 0.f }, + {0.f, 0.f} }; // prawy brzeg prawego pobocza } } else { // wersja dla chodnika: skos 1:3.75, każdy chodnik innej szerokości // mapowanie propocjonalne do szerokości chodnika // krawężnik jest mapowany od 31/64 do 32/64 lewy i od 32/64 do 33/64 prawy - double d = -fTexHeight1 / 3.75; // krawężnik o wysokości 150mm jest pochylony 40mm - double max = fTexRatio2 * fTexLength; // test: szerokość proporcjonalna do długości - double map1l = ( - max > 0.0 ? + auto const d = -fTexHeight1 / 3.75f; // krawężnik o wysokości 150mm jest pochylony 40mm + auto const max = fTexRatio2 * fTexLength; // test: szerokość proporcjonalna do długości + auto const map1l = ( + max > 0.f ? side / max : - 0.484375 ); // obcięcie tekstury od lewej strony punktu 1 - double map1r = ( - max > 0.0 ? + 0.484375f ); // obcięcie tekstury od lewej strony punktu 1 + auto const map1r = ( + max > 0.f ? slop / max : - 0.484375 ); // obcięcie tekstury od prawej strony punktu 1 - double h1r = ( + 0.484375f ); // obcięcie tekstury od prawej strony punktu 1 + auto const h1r = ( slop > d ? -fTexHeight1 : - 0 ); - double h1l = ( + 0.f ); + auto const h1l = ( side > d ? -fTexHeight1 : - 0 ); - rpts1[0] = vector6(bpts1[0].x + slop, bpts1[0].y + h1r, 0.515625 + map1r); // prawy brzeg prawego chodnika - rpts1[1] = vector6(bpts1[0].x + d, bpts1[0].y + h1r, 0.515625); // prawy krawężnik u góry - rpts1[2] = vector6(bpts1[0].x, bpts1[0].y, 0.515625 - d / 2.56); // prawy krawężnik u dołu - rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 0.484375 + d / 2.56); // lewy krawężnik u dołu - rpts2[1] = vector6(bpts1[1].x - d, bpts1[1].y + h1l, 0.484375); // lewy krawężnik u góry - rpts2[2] = vector6(bpts1[1].x - side, bpts1[1].y + h1l, 0.484375 - map1l); // lewy brzeg lewego chodnika - if (iTrapezoid) // trapez albo przechyłki - { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka + 0.f ); + + rpts1[ 0 ] = { + {bpts1[ 0 ].position.x + slop, bpts1[ 0 ].position.y + h1r, 0.f}, + normalup, + {0.515625f + map1r, 0.f} }; // prawy brzeg prawego chodnika + rpts1[ 1 ] = { + {bpts1[ 0 ].position.x + d, bpts1[ 0 ].position.y + h1r, 0.f}, + normalup, + {0.515625f, 0.f} }; // prawy krawężnik u góry + rpts1[ 2 ] = { + {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, + { -1.f, 0.f, 0.f }, + {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu + rpts2[ 0 ] = { + {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, + { 1.f, 0.f, 0.f }, + {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu + rpts2[ 1 ] = { + {bpts1[ 1 ].position.x - d, bpts1[ 1 ].position.y + h1l, 0.f}, + normalup, + {0.484375f, 0.f} }; // lewy krawężnik u góry + rpts2[ 2 ] = { + {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y + h1l, 0.f}, + normalup, + {0.484375f - map1l, 0.f} }; // lewy brzeg lewego chodnika + + if (iTrapezoid) { + // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka slop2 = ( - fabs((iTrapezoid & 2) ? + std::fabs((iTrapezoid & 2) ? slop2 : slop) ); // szerokość chodnika po prawej - double map2l = ( - max > 0.0 ? + auto const map2l = ( + max > 0.f ? side2 / max : - 0.484375 ); // obcięcie tekstury od lewej strony punktu 2 - double map2r = ( - max > 0.0 ? + 0.484375f ); // obcięcie tekstury od lewej strony punktu 2 + auto const map2r = ( + max > 0.f ? slop2 / max : - 0.484375 ); // obcięcie tekstury od prawej strony punktu 2 - double h2r = (slop2 > d) ? -fTexHeight2 : 0; - double h2l = (side2 > d) ? -fTexHeight2 : 0; - rpts1[3] = vector6(bpts1[2].x + slop2, bpts1[2].y + h2r, 0.515625 + map2r); // prawy brzeg prawego chodnika - rpts1[4] = vector6(bpts1[2].x + d, bpts1[2].y + h2r, 0.515625); // prawy krawężnik u góry - rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 0.515625 - d / 2.56); // prawy krawężnik u dołu - rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 0.484375 + d / 2.56); // lewy krawężnik u dołu - rpts2[4] = vector6(bpts1[3].x - d, bpts1[3].y + h2l, 0.484375); // lewy krawężnik u góry - rpts2[5] = vector6(bpts1[3].x - side2, bpts1[3].y + h2l, 0.484375 - map2l); // lewy brzeg lewego chodnika + 0.484375f ); // obcięcie tekstury od prawej strony punktu 2 + auto const h2r = ( + slop2 > d ? + -fTexHeight2 : + 0.f ); + auto const h2l = ( + side2 > d ? + -fTexHeight2 : + 0.f ); + + rpts1[ 3 ] = { + {bpts1[ 2 ].position.x + slop2, bpts1[ 2 ].position.y + h2r, 0.f}, + normalup, + {0.515625f + map2r, 0.f} }; // prawy brzeg prawego chodnika + rpts1[ 4 ] = { + {bpts1[ 2 ].position.x + d, bpts1[ 2 ].position.y + h2r, 0.f}, + normalup, + {0.515625f, 0.f} }; // prawy krawężnik u góry + rpts1[ 5 ] = { + {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, + { -1.f, 0.f, 0.f }, + {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu + rpts2[ 3 ] = { + {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, + { 1.f, 0.f, 0.f }, + {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu + rpts2[ 4 ] = { + {bpts1[ 3 ].position.x - d, bpts1[ 3 ].position.y + h2l, 0.f}, + normalup, + {0.484375f, 0.f} }; // lewy krawężnik u góry + rpts2[ 5 ] = { + {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y + h2l, 0.f}, + normalup, + {0.484375f - map2l, 0.f} }; // lewy brzeg lewego chodnika } } vertex_array vertices; @@ -1472,24 +1654,24 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony // odcinka if( ( fTexHeight1 >= 0.0 ) || ( slop != 0.0 ) ) { - Segment->RenderLoft( vertices, origin, rpts1, -3, fTexLength ); // tylko jeśli jest z prawej + Segment->RenderLoft( vertices, m_origin, rpts1, -3, fTexLength ); // tylko jeśli jest z prawej Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - Segment->RenderLoft( vertices, origin, rpts2, -3, fTexLength ); // tylko jeśli jest z lewej + Segment->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength ); // tylko jeśli jest z lewej Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } } else { // pobocza zwykłe, brak przechyłki if( ( fTexHeight1 >= 0.0 ) || ( slop != 0.0 ) ) { - Segment->RenderLoft( vertices, origin, rpts1, 3, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts1, 3, fTexLength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - Segment->RenderLoft( vertices, origin, rpts2, 3, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts2, 3, fTexLength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1540,25 +1722,37 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { if (!SwitchExtension->vPoints) { // jeśli tablica punktów nie jest jeszcze utworzona, zliczamy punkty i tworzymy ją // we'll need to add couple extra points for the complete fan we'll build - SwitchExtension->vPoints = new vector3[pointcount + SwitchExtension->iRoads]; + SwitchExtension->vPoints = new glm::vec3[pointcount + SwitchExtension->iRoads]; } - vector3 *b = + glm::vec3 *b = SwitchExtension->bPoints ? nullptr : SwitchExtension->vPoints; // zmienna robocza, NULL gdy tablica punktów już jest wypełniona - vector6 bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków + basic_vertex bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków if (m_material1 || m_material2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100% - double max = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości - double map1 = max > 0.0 ? fHTW / max : 0.5; // obcięcie tekstury od strony 1 - double map2 = max > 0.0 ? fHTW2 / max : 0.5; // obcięcie tekstury od strony 2 + auto const max = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości + auto const map1 = max > 0.f ? fHTW / max : 0.5f; // obcięcie tekstury od strony 1 + auto const map2 = max > 0.f ? fHTW2 / max : 0.5f; // obcięcie tekstury od strony 2 // if (iTrapezoid) //trapez albo przechyłki { // nawierzchnia trapezowata Segment->GetRolls(roll1, roll2); - bpts1[0] = vector6(fHTW * cos(roll1), -fHTW * sin(roll1), 0.5 - map1, sin(roll1), cos(roll1), 0.0); // lewy brzeg początku - bpts1[1] = vector6(-bpts1[0].x, -bpts1[0].y, 0.5 + map1, -sin(roll1), cos(roll1), 0.0); // prawy brzeg początku symetrycznie - bpts1[2] = vector6(fHTW2 * cos(roll2), -fHTW2 * sin(roll2), 0.5 - map2, sin(roll2), cos(roll2), 0.0); // lewy brzeg końca - bpts1[3] = vector6(-bpts1[2].x, -bpts1[2].y, 0.5 + map2, -sin(roll2), cos(roll2), 0.0); // prawy brzeg początku symetrycznie + bpts1[ 0 ] = { + {fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f}, + {std::sin( roll1 ), std::cos( roll1 ), 0.f}, + {0.5f - map1, 0.f} }; // lewy brzeg początku + bpts1[ 1 ] = { + {-bpts1[ 0 ].position.x, -bpts1[ 0 ].position.y, 0.f}, + {-std::sin( roll1 ), std::cos( roll1 ), 0.f}, + {0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie + bpts1[ 2 ] = { + {fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f}, + {std::sin( roll2 ), std::cos( roll2 ), 0.f}, + {0.5f - map2, 0.f} }; // lewy brzeg końca + bpts1[ 3 ] = { + {-bpts1[ 2 ].position.x, -bpts1[ 2 ].position.y, 0.f}, + {-std::sin( roll2 ), std::cos( roll2 ), 0.f}, + {0.5 + map2, 0.f} }; // prawy brzeg początku symetrycznie } } // najpierw renderowanie poboczy i zapamiętywanie punktów @@ -1567,61 +1761,142 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { // ale pobocza renderują się później, więc nawierzchnia nie załapuje się na renderowanie w swoim czasie if( m_material2 ) { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?) - vector6 + basic_vertex rpts1[6], rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony // Ra 2014-07: trzeba to przerobić na pętlę i pobierać profile (przynajmniej 2..4) z sąsiednich dróg if (fTexHeight1 >= 0.0) { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo - rpts1[0] = vector6(rozp, -fTexHeight1, 0.0); // lewy brzeg podstawy - rpts1[1] = vector6(bpts1[0].x + side, bpts1[0].y, 0.5); // lewa krawędź załamania - rpts1[2] = vector6(bpts1[0].x, bpts1[0].y, 1.0); // lewy brzeg pobocza (mapowanie może być inne - rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 1.0); // prawy brzeg pobocza - rpts2[1] = vector6(bpts1[1].x - side, bpts1[1].y, 0.5); // prawa krawędź załamania - rpts2[2] = vector6(-rozp, -fTexHeight1, 0.0); // prawy brzeg podstawy + rpts1[ 0 ] = { + {rozp, -fTexHeight1, 0.f}, + { 1.f, 0.f, 0.f }, + {0.f, 0.f} }; // lewy brzeg podstawy + rpts1[ 1 ] = { + {bpts1[ 0 ].position.x + side, bpts1[ 0 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} };// lewa krawędź załamania + rpts1[ 2 ] = { + {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne + rpts2[ 0 ] = { + {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // prawy brzeg pobocza + rpts2[ 1 ] = { + {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // prawa krawędź załamania + rpts2[ 2 ] = { + {-rozp, -fTexHeight1, 0.f}, + { -1.f, 0.f, 0.f }, + {0.f, 0.f} }; // prawy brzeg podstawy // if (iTrapezoid) //trapez albo przechyłki { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - rpts1[3] = vector6(rozp2, -fTexHeight2, 0.0); // lewy brzeg lewego pobocza - rpts1[4] = vector6(bpts1[2].x + side2, bpts1[2].y, 0.5); // krawędź załamania - rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 1.0); // brzeg pobocza - rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 1.0); - rpts2[4] = vector6(bpts1[3].x - side2, bpts1[3].y, 0.5); - rpts2[5] = vector6(-rozp2, -fTexHeight2, 0.0); // prawy brzeg prawego pobocza + rpts1[ 3 ] = { + {rozp2, -fTexHeight2, 0.f}, + { 1.f, 0.f, 0.f }, + {0.f, 0.f} }; // lewy brzeg lewego pobocza + rpts1[ 4 ] = { + {bpts1[ 2 ].position.x + side2, bpts1[ 2 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // krawędź załamania + rpts1[ 5 ] = { + {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // brzeg pobocza + rpts2[ 3 ] = { + {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; + rpts2[ 4 ] = { + {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; + rpts2[ 5 ] = { + {-rozp2, -fTexHeight2, 0.f}, + { -1.f, 0.f, 0.f }, + {0.f, 0.f} }; // prawy brzeg prawego pobocza } } else { // wersja dla chodnika: skos 1:3.75, każdy chodnik innej szerokości // mapowanie propocjonalne do szerokości chodnika // krawężnik jest mapowany od 31/64 do 32/64 lewy i od 32/64 do 33/64 prawy - double d = -fTexHeight1 / 3.75; // krawężnik o wysokości 150mm jest pochylony 40mm - double max = fTexRatio2 * fTexLength; // test: szerokość proporcjonalna do długości - double map1l = max > 0.0 ? - side / max : - 0.484375; // obcięcie tekstury od lewej strony punktu 1 - double map1r = max > 0.0 ? - slop / max : - 0.484375; // obcięcie tekstury od prawej strony punktu 1 - rpts1[0] = vector6(bpts1[0].x + slop, bpts1[0].y - fTexHeight1, 0.515625 + map1r); // prawy brzeg prawego chodnika - rpts1[1] = vector6(bpts1[0].x + d, bpts1[0].y - fTexHeight1, 0.515625); // prawy krawężnik u góry - rpts1[2] = vector6(bpts1[0].x, bpts1[0].y, 0.515625 - d / 2.56); // prawy krawężnik u dołu - rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 0.484375 + d / 2.56); // lewy krawężnik u dołu - rpts2[1] = vector6(bpts1[1].x - d, bpts1[1].y - fTexHeight1, 0.484375); // lewy krawężnik u góry - rpts2[2] = vector6(bpts1[1].x - side, bpts1[1].y - fTexHeight1, 0.484375 - map1l); // lewy brzeg lewego chodnika + auto const d = -fTexHeight1 / 3.75f; // krawężnik o wysokości 150mm jest pochylony 40mm + auto const max = fTexRatio2 * fTexLength; // test: szerokość proporcjonalna do długości + auto const map1l = ( + max > 0.f ? + side / max : + 0.484375f ); // obcięcie tekstury od lewej strony punktu 1 + auto const map1r = ( + max > 0.f ? + slop / max : + 0.484375f ); // obcięcie tekstury od prawej strony punktu 1 + + rpts1[ 0 ] = { + {bpts1[ 0 ].position.x + slop, bpts1[ 0 ].position.y - fTexHeight1, 0.f}, + normalup, + { 0.515625f + map1r, 0.f} }; // prawy brzeg prawego chodnika + rpts1[ 1 ] = { + {bpts1[ 0 ].position.x + d, bpts1[ 0 ].position.y - fTexHeight1, 0.f}, + normalup, + {0.515625f, 0.f} }; // prawy krawężnik u góry + rpts1[ 2 ] = { + {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, + { -1.f, 0.f, 0.f }, + {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu + rpts2[ 0 ] = { + {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, + { 1.f, 0.f, 0.f }, + {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu + rpts2[ 1 ] = { + {bpts1[ 1 ].position.x - d, bpts1[ 1 ].position.y - fTexHeight1, 0.f}, + normalup, + {0.484375f, 0.f} }; // lewy krawężnik u góry + rpts2[ 2 ] = { + {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y - fTexHeight1, 0.f}, + normalup, + {0.484375f - map1l, 0.f} }; // lewy brzeg lewego chodnika // if (iTrapezoid) //trapez albo przechyłki { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - slop2 = std::fabs((iTrapezoid & 2) ? slop2 : slop); // szerokość chodnika po prawej - double map2l = max > 0.0 ? - side2 / max : - 0.484375; // obcięcie tekstury od lewej strony punktu 2 - double map2r = max > 0.0 ? - slop2 / max : - 0.484375; // obcięcie tekstury od prawej strony punktu 2 - rpts1[3] = vector6(bpts1[2].x + slop2, bpts1[2].y - fTexHeight2, 0.515625 + map2r); // prawy brzeg prawego chodnika - rpts1[4] = vector6(bpts1[2].x + d, bpts1[2].y - fTexHeight2, 0.515625); // prawy krawężnik u góry - rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 0.515625 - d / 2.56); // prawy krawężnik u dołu - rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 0.484375 + d / 2.56); // lewy krawężnik u dołu - rpts2[4] = vector6(bpts1[3].x - d, bpts1[3].y - fTexHeight2, 0.484375); // lewy krawężnik u góry - rpts2[5] = vector6(bpts1[3].x - side2, bpts1[3].y - fTexHeight2, 0.484375 - map2l); // lewy brzeg lewego chodnika + slop2 = std::abs( + ( (iTrapezoid & 2) ? + slop2 : + slop ) ); // szerokość chodnika po prawej + auto const map2l = ( + max > 0.f ? + side2 / max : + 0.484375f ); // obcięcie tekstury od lewej strony punktu 2 + auto const map2r = ( + max > 0.f ? + slop2 / max : + 0.484375f ); // obcięcie tekstury od prawej strony punktu 2 + + rpts1[ 3 ] = { + {bpts1[ 2 ].position.x + slop2, bpts1[ 2 ].position.y - fTexHeight2, 0.f}, + normalup, + { 0.515625f + map2r, 0.f} }; // prawy brzeg prawego chodnika + rpts1[ 4 ] = { + {bpts1[ 2 ].position.x + d, bpts1[ 2 ].position.y - fTexHeight2, 0.f}, + normalup, + {0.515625f, 0.f} }; // prawy krawężnik u góry + rpts1[ 5 ] = { + {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, + { -1.f, 0.f, 0.f }, + {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu + rpts2[ 3 ] = { + {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, + { 1.f, 0.f, 0.f }, + {0.484375f + d / 2.56, 0.f} }; // lewy krawężnik u dołu + rpts2[ 4 ] = { + {bpts1[ 3 ].position.x - d, bpts1[ 3 ].position.y - fTexHeight2, 0.f}, + normalup, + {0.484375f, 0.f} }; // lewy krawężnik u góry + rpts2[ 5 ] = { + {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y - fTexHeight2, 0.f}, + normalup, + {0.484375f - map2l, 0.f} }; // lewy brzeg lewego chodnika } } bool render = ( m_material2 != 0 ); // renderować nie trzeba, ale trzeba wyznaczyć punkty brzegowe nawierzchni @@ -1629,22 +1904,22 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { if (SwitchExtension->iRoads == 4) { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 3 ]->RenderLoft( vertices, origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 3 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 4 ]->RenderLoft( vertices, origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 4 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 5 ]->RenderLoft( vertices, origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 5 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); @@ -1654,17 +1929,17 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { else { // punkt 3 pokrywa się z punktem 1, jak w zwrotnicy; połączenie 1->2 nie musi być prostoliniowe if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P2 do P4 + SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P2 do P4 if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P4 do P3=P1 (odwrócony) + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P4 do P3=P1 (odwrócony) if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P1 do P2 + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P1 do P2 if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); @@ -1686,14 +1961,14 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { // we start with a vertex in the middle... vertices.emplace_back( glm::vec3{ - oxz.x - origin.x, - oxz.y - origin.y, - oxz.z - origin.z }, + oxz.x - m_origin.x, + oxz.y - m_origin.y, + oxz.z - m_origin.z }, glm::vec3{ 0.0f, 1.0f, 0.0f }, glm::vec2{ 0.5f, 0.5f } ); // ...and add one extra vertex to close the fan... - u = ( SwitchExtension->vPoints[ 0 ].x - oxz.x + origin.x ) / fTexLength; - v = ( SwitchExtension->vPoints[ 0 ].z - oxz.z + origin.z ) / ( fTexRatio1 * fTexLength ); + u = ( SwitchExtension->vPoints[ 0 ].x - oxz.x + m_origin.x ) / fTexLength; + v = ( SwitchExtension->vPoints[ 0 ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * fTexLength ); vertices.emplace_back( glm::vec3 { SwitchExtension->vPoints[ 0 ].x, @@ -1707,8 +1982,8 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { // ...then draw the precalculated rest for (int i = pointcount + SwitchExtension->iRoads - 1; i >= 0; --i) { // mapowanie we współrzędnych scenerii - u = ( SwitchExtension->vPoints[ i ].x - oxz.x + origin.x ) / fTexLength; - v = ( SwitchExtension->vPoints[ i ].z - oxz.z + origin.z ) / ( fTexRatio1 * fTexLength ); + u = ( SwitchExtension->vPoints[ i ].x - oxz.x + m_origin.x ) / fTexLength; + v = ( SwitchExtension->vPoints[ i ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * fTexLength ); vertices.emplace_back( glm::vec3 { SwitchExtension->vPoints[ i ].x, @@ -1731,71 +2006,130 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { { case tt_Normal: // drogi proste, bo skrzyżowania osobno { - vector6 bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków + basic_vertex bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków if (m_material1 || m_material2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100% - double max = (iCategoryFlag & 4) ? - 0.0 : - fTexLength; // test: szerokość dróg proporcjonalna do długości - double map1 = max > 0.0 ? fHTW / max : 0.5; // obcięcie tekstury od strony 1 - double map2 = max > 0.0 ? fHTW2 / max : 0.5; // obcięcie tekstury od strony 2 - if (iTrapezoid) // trapez albo przechyłki - { // nawierzchnia trapezowata + auto const max = ( + ( iCategoryFlag & 4 ) ? + 0.f : + fTexLength ); // test: szerokość dróg proporcjonalna do długości + auto const map1 = ( + max > 0.f ? + fHTW / max : + 0.5f ); // obcięcie tekstury od strony 1 + auto const map2 = ( + max > 0.f ? + fHTW2 / max : + 0.5f ); // obcięcie tekstury od strony 2 + + if (iTrapezoid) { + // nawierzchnia trapezowata Segment->GetRolls(roll1, roll2); - bpts1[0] = vector6(fHTW * cos(roll1), -fHTW * sin(roll1), - 0.5 - map1); // lewy brzeg początku - bpts1[1] = vector6(-bpts1[0].x, -bpts1[0].y, - 0.5 + map1); // prawy brzeg początku symetrycznie - bpts1[2] = vector6(fHTW2 * cos(roll2), -fHTW2 * sin(roll2), - 0.5 - map2); // lewy brzeg końca - bpts1[3] = vector6(-bpts1[2].x, -bpts1[2].y, - 0.5 + map2); // prawy brzeg początku symetrycznie + bpts1[ 0 ] = { + {fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f}, + normalup, + {0.5f - map1, 0.f} }; // lewy brzeg początku + bpts1[ 1 ] = { + {-bpts1[ 0 ].position.x, -bpts1[ 0 ].position.y, 0.f}, + normalup, + {0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie + bpts1[ 2 ] = { + {fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f}, + normalup, + {0.5f - map2, 0.f} }; // lewy brzeg końca + bpts1[ 3 ] = { + {-bpts1[ 2 ].position.x, -bpts1[ 2 ].position.y, 0.f}, + normalup, + {0.5f + map2, 0.f} }; // prawy brzeg początku symetrycznie } else { - bpts1[0] = vector6(fHTW, 0.0, 0.5 - map1); // zawsze standardowe mapowanie - bpts1[1] = vector6(-fHTW, 0.0, 0.5 + map1); + bpts1[ 0 ] = { + {fHTW, 0.f, 0.f}, + normalup, + {0.5 - map1, 0.f} }; // zawsze standardowe mapowanie + bpts1[ 1 ] = { + {-fHTW, 0.f, 0.f}, + normalup, + {0.5f + map1, 0.f} }; } } if (m_material1) // jeśli podana była tekstura, generujemy trójkąty { // tworzenie trójkątów nawierzchni szosy vertex_array vertices; - Segment->RenderLoft(vertices, origin, bpts1, iTrapezoid ? -2 : 2, fTexLength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -2 : 2, fTexLength); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if (m_material2) { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?) vertex_array vertices; - vector6 rpts1[6], + basic_vertex + rpts1[6], rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony - rpts1[0] = vector6(rozp, -fTexHeight1, 0.0); // lewy brzeg podstawy - rpts1[1] = vector6(bpts1[0].x + side, bpts1[0].y, 0.5), // lewa krawędź załamania - rpts1[2] = vector6(bpts1[0].x, bpts1[0].y, - 1.0); // lewy brzeg pobocza (mapowanie może być inne - rpts2[0] = vector6(bpts1[1].x, bpts1[1].y, 1.0); // prawy brzeg pobocza - rpts2[1] = vector6(bpts1[1].x - side, bpts1[1].y, 0.5); // prawa krawędź załamania - rpts2[2] = vector6(-rozp, -fTexHeight1, 0.0); // prawy brzeg podstawy + + rpts1[ 0 ] = { + {rozp, -fTexHeight1, 0.f}, + normalup, + {0.0f, 0.f} }; // lewy brzeg podstawy + rpts1[ 1 ] = { + {bpts1[ 0 ].position.x + side, bpts1[ 0 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // lewa krawędź załamania + rpts1[ 2 ] = { + {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, + normalup, + {1.0f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne + rpts2[ 0 ] = { + {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, + normalup, + {1.0f, 0.f} }; // prawy brzeg pobocza + rpts2[ 1 ] = { + {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // prawa krawędź załamania + rpts2[ 2 ] = { + {-rozp, -fTexHeight1, 0.f}, + normalup, + {0.0f, 0.f} }; // prawy brzeg podstawy if (iTrapezoid) // trapez albo przechyłki { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - rpts1[3] = vector6(rozp2, -fTexHeight2, 0.0); // lewy brzeg lewego pobocza - rpts1[4] = vector6(bpts1[2].x + side2, bpts1[2].y, 0.5); // krawędź załamania - rpts1[5] = vector6(bpts1[2].x, bpts1[2].y, 1.0); // brzeg pobocza - rpts2[3] = vector6(bpts1[3].x, bpts1[3].y, 1.0); - rpts2[4] = vector6(bpts1[3].x - side2, bpts1[3].y, 0.5); - rpts2[5] = vector6(-rozp2, -fTexHeight2, 0.0); // prawy brzeg prawego pobocza - Segment->RenderLoft(vertices, origin, rpts1, -3, fTexLength); + rpts1[ 3 ] = { + {rozp2, -fTexHeight2, 0.f}, + normalup, + {0.0f, 0.f} }; // lewy brzeg lewego pobocza + rpts1[ 4 ] = { + {bpts1[ 2 ].position.x + side2, bpts1[ 2 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // krawędź załamania + rpts1[ 5 ] = { + {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, + normalup, + {1.0f, 0.f} }; // brzeg pobocza + rpts2[ 3 ] = { + {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, + normalup, + {1.0f, 0.f} }; + rpts2[ 4 ] = { + {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; + rpts2[ 5 ] = { + {-rozp2, -fTexHeight2, 0.f}, + normalup, + {0.0f, 0.f} }; // prawy brzeg prawego pobocza + Segment->RenderLoft(vertices, m_origin, rpts1, -3, fTexLength); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - Segment->RenderLoft(vertices, origin, rpts2, -3, fTexLength); + Segment->RenderLoft(vertices, m_origin, rpts2, -3, fTexLength); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } else { // pobocza zwykłe, brak przechyłki - Segment->RenderLoft(vertices, origin, rpts1, 3, fTexLength); + Segment->RenderLoft(vertices, m_origin, rpts1, 3, fTexLength); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - Segment->RenderLoft(vertices, origin, rpts2, 3, fTexLength); + Segment->RenderLoft(vertices, m_origin, rpts2, 3, fTexLength); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1809,7 +2143,6 @@ void TTrack::create_geometry( geometrybank_handle const &Bank ) { void TTrack::EnvironmentSet() { // ustawienie zmienionego światła - glColor3f(1.0f, 1.0f, 1.0f); // Ra: potrzebne to? switch( eEnvironment ) { case e_canyon: { Global::DayLight.apply_intensity(0.4f); @@ -1871,14 +2204,14 @@ bool TTrack::SetConnections(int i) return false; } -bool TTrack::Switch(int i, double t, double d) +bool TTrack::Switch(int i, float const t, float const d) { // przełączenie torów z uruchomieniem animacji if (SwitchExtension) // tory przełączalne mają doklejkę if (eType == tt_Switch) { // przekładanie zwrotnicy jak zwykle - if (t > 0.0) // prędkość liniowa ruchu iglic + if (t > 0.f) // prędkość liniowa ruchu iglic SwitchExtension->fOffsetSpeed = t; // prędkość łatwiej zgrać z animacją modelu - if (d >= 0.0) // dodatkowy ruch drugiej iglicy (zamknięcie nastawnicze) + if (d >= 0.f) // dodatkowy ruch drugiej iglicy (zamknięcie nastawnicze) SwitchExtension->fOffsetDelay = d; i &= 1; // ograniczenie błędów !!!! SwitchExtension->fDesiredOffset = @@ -1904,7 +2237,7 @@ bool TTrack::Switch(int i, double t, double d) } else if (eType == tt_Table) { // blokowanie (0, szukanie torów) lub odblokowanie (1, rozłączenie) obrotnicy - if (i) + if (i) // NOTE: this condition seems opposite to intention/comment? TODO: investigate this { // 0: rozłączenie sąsiednich torów od obrotnicy if (trPrev) // jeśli jest tor od Point1 obrotnicy if (iPrevDirection) // 0:dołączony Point1, 1:dołączony Point2 @@ -1921,25 +2254,30 @@ bool TTrack::Switch(int i, double t, double d) fVelocity = 0.0; // AI, nie ruszaj się! if (SwitchExtension->pOwner) SwitchExtension->pOwner->RaTrackAnimAdd(this); // dodanie do listy animacyjnej + // TODO: unregister path ends in the owner cell } else { // 1: ustalenie finalnego położenia (gdy nie było animacji) RaAnimate(); // ostatni etap animowania // zablokowanie pozycji i połączenie do sąsiednich torów - Global::pGround->TrackJoin(SwitchExtension->pMyNode); + // TODO: register new position of the path endpoints with the region + simulation::Region->TrackJoin( this ); if (trNext || trPrev) { fVelocity = 6.0; // jazda dozwolona - if (trPrev) - if (trPrev->fVelocity == - 0.0) // ustawienie 0 da możliwość zatrzymania AI na obrotnicy - trPrev->VelocitySet(6.0); // odblokowanie dołączonego toru do jazdy - if (trNext) - if (trNext->fVelocity == 0.0) - trNext->VelocitySet(6.0); - if (SwitchExtension->evPlus) // w starych sceneriach może nie być - Global::AddToQuery(SwitchExtension->evPlus, - NULL); // potwierdzenie wykonania (np. odpala WZ) + if( ( trPrev ) + && ( trPrev->fVelocity == 0.0 ) ) { + // ustawienie 0 da możliwość zatrzymania AI na obrotnicy + trPrev->VelocitySet( 6.0 ); // odblokowanie dołączonego toru do jazdy + } + if( ( trNext ) + && ( trNext->fVelocity == 0.0 ) ) { + trNext->VelocitySet( 6.0 ); + } + if( SwitchExtension->evPlus ) { // w starych sceneriach może nie być + // potwierdzenie wykonania (np. odpala WZ) + simulation::Events.AddToQuery( SwitchExtension->evPlus, nullptr ); + } } } SwitchExtension->CurrentIndex = i; // zapamiętanie stanu zablokowania @@ -1974,11 +2312,11 @@ bool TTrack::SwitchForced(int i, TDynamicObject *o) { case 0: if (SwitchExtension->evPlus) - Global::AddToQuery(SwitchExtension->evPlus, o); // dodanie do kolejki + simulation::Events.AddToQuery(SwitchExtension->evPlus, o); // dodanie do kolejki break; case 1: if (SwitchExtension->evMinus) - Global::AddToQuery(SwitchExtension->evMinus, o); // dodanie do kolejki + simulation::Events.AddToQuery(SwitchExtension->evMinus, o); // dodanie do kolejki break; } Switch(i); // jeśli się tu nie przełączy, to każdy pojazd powtórzy event rozrprucia @@ -1998,18 +2336,18 @@ int TTrack::CrossSegment(int from, int into) switch (into) { case 0: // stop - // WriteLog("Crossing from P"+AnsiString(from+1)+" into stop on "+pMyNode->asName); +// WriteLog( "Stopping in P" + to_string( from + 1 ) + " on " + pMyNode->asName ); break; case 1: // left - // WriteLog("Crossing from P"+AnsiString(from+1)+" to left on "+pMyNode->asName); +// WriteLog( "Turning left from P" + to_string( from + 1 ) + " on " + pMyNode->asName ); i = (SwitchExtension->iRoads == 4) ? iLewo4[from] : iLewo3[from]; break; case 2: // right - // WriteLog("Crossing from P"+AnsiString(from+1)+" to right on "+pMyNode->asName); +// WriteLog( "Turning right from P" + to_string( from + 1 ) + " on " + pMyNode->asName ); i = (SwitchExtension->iRoads == 4) ? iPrawo4[from] : iPrawo3[from]; break; case 3: // stright - // WriteLog("Crossing from P"+AnsiString(from+1)+" to straight on "+pMyNode->asName); +// WriteLog( "Going straight from P" + to_string( from + 1 ) + " on " + pMyNode->asName ); i = (SwitchExtension->iRoads == 4) ? iProsto4[from] : iProsto3[from]; break; } @@ -2052,16 +2390,16 @@ TTrack * TTrack::RaAnimate() bool m = true; // animacja trwa if (eType == tt_Switch) // dla zwrotnicy tylko szyny { - double v = SwitchExtension->fDesiredOffset - SwitchExtension->fOffset; // kierunek + auto const v = SwitchExtension->fDesiredOffset - SwitchExtension->fOffset; // kierunek SwitchExtension->fOffset += sign(v) * Timer::GetDeltaTime() * SwitchExtension->fOffsetSpeed; // Ra: trzeba dać to do klasy... SwitchExtension->fOffset1 = SwitchExtension->fOffset; SwitchExtension->fOffset2 = SwitchExtension->fOffset; - if (SwitchExtension->fOffset1 >= fMaxOffset) + if (SwitchExtension->fOffset1 > fMaxOffset) SwitchExtension->fOffset1 = fMaxOffset; // ograniczenie animacji zewnętrznej iglicy - if (SwitchExtension->fOffset2 <= 0.00) - SwitchExtension->fOffset2 = 0.0; // ograniczenie animacji wewnętrznej iglicy - if (v < 0) + if (SwitchExtension->fOffset2 < 0.f) + SwitchExtension->fOffset2 = 0.f; // ograniczenie animacji wewnętrznej iglicy + if (v < 0.f) { // jak na pierwszy z torów if (SwitchExtension->fOffset <= SwitchExtension->fDesiredOffset) { @@ -2083,48 +2421,54 @@ TTrack * TTrack::RaAnimate() && ( ( false == Geometry1.empty() ) || ( false == Geometry2.empty() ) ) ) { // iglice liczone tylko dla zwrotnic - double fHTW = 0.5 * fabs( fTrackWidth ); - double fHTW2 = fHTW; // Ra: na razie niech tak będzie - double cos1 = 1.0, sin1 = 0.0, cos2 = 1.0, sin2 = 0.0; // Ra: ... - vector6 rpts3[ 24 ], rpts4[ 24 ]; - for (int i = 0; i < 12; ++i) - { - rpts3[i] = - vector6( - +( fHTW + iglica[i].x) * cos1 + iglica[i].y * sin1, - -(+fHTW + iglica[i].x) * sin1 + iglica[i].y * cos1, - iglica[i].z); - rpts3[i + 12] = - vector6( - +( fHTW2 + szyna[i].x) * cos2 + szyna[i].y * sin2, - -(+fHTW2 + szyna[i].x) * sin2 + iglica[i].y * cos2, - szyna[i].z); - rpts4[11 - i] = - vector6( - (-fHTW - iglica[i].x) * cos1 + iglica[i].y * sin1, - -(-fHTW - iglica[i].x) * sin1 + iglica[i].y * cos1, - iglica[i].z); - rpts4[23 - i] = - vector6( - (-fHTW2 - szyna[i].x) * cos2 + szyna[i].y * sin2, - -(-fHTW2 - szyna[i].x) * sin2 + iglica[i].y * cos2, - szyna[i].z); + auto const fHTW = 0.5f * std::abs( fTrackWidth ); + auto const fHTW2 = fHTW; // Ra: na razie niech tak będzie + auto const cos1 = 1.0f, sin1 = 0.0f, cos2 = 1.0f, sin2 = 0.0f; // Ra: ... + + basic_vertex + rpts3[ 24 ], + rpts4[ 24 ]; + for (int i = 0; i < 12; ++i) { + + rpts3[ i ] = { + {+( fHTW + iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, + -( fHTW + iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, + 0.f}, + {iglica[ i ].normal}, + {iglica[ i ].texture.x, 0.f} }; + rpts3[ i + 12 ] = { + {+( fHTW2 + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( fHTW2 + szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, + 0.f}, + {szyna[ i ].normal}, + {szyna[ i ].texture.x, 0.f} }; + rpts4[ 11 - i ] = { + {+( -fHTW - iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, + -( -fHTW - iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, + 0.f}, + {iglica[ i ].normal}, + {iglica[ i ].texture.x, 0.f} }; + rpts4[ 23 - i ] = { + { ( -fHTW2 - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( -fHTW2 - szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, + 0.f}, + {szyna[ i ].normal}, + {szyna[ i ].texture.x, 0.f} }; } - auto const origin { pMyNode->m_rootposition }; vertex_array vertices; if (SwitchExtension->RightSwitch) { // nowa wersja z SPKS, ale odwrotnie lewa/prawa if( m_material1 ) { // left blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, SwitchExtension->fOffset2 ); GfxRenderer.Replace( vertices, Geometry1[ 2 ] ); vertices.clear(); } if( m_material2 ) { // right blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -fMaxOffset + SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -fMaxOffset + SwitchExtension->fOffset1 ); GfxRenderer.Replace( vertices, Geometry2[ 2 ] ); vertices.clear(); } @@ -2132,13 +2476,13 @@ TTrack * TTrack::RaAnimate() else { // lewa działa lepiej niż prawa if( m_material1 ) { // right blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -SwitchExtension->fOffset2 ); GfxRenderer.Replace( vertices, Geometry1[ 2 ] ); vertices.clear(); } if( m_material2 ) { // left blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, fMaxOffset - SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, fMaxOffset - SwitchExtension->fOffset1 ); GfxRenderer.Replace( vertices, Geometry2[ 2 ] ); vertices.clear(); } @@ -2163,14 +2507,14 @@ TTrack * TTrack::RaAnimate() double hlen = 0.5 * SwitchExtension->Segments[0]->GetLength(); // połowa // długości SwitchExtension->fOffset = ac->AngleGet(); // pobranie kąta z submodelu - double sina = -hlen * sin(DegToRad(SwitchExtension->fOffset)), - cosa = -hlen * cos(DegToRad(SwitchExtension->fOffset)); + double sina = -hlen * std::sin(glm::radians(SwitchExtension->fOffset)), + cosa = -hlen * std::cos(glm::radians(SwitchExtension->fOffset)); SwitchExtension->vTrans = ac->TransGet(); vector3 middle = - SwitchExtension->pMyNode->pCenter + + location() + SwitchExtension->vTrans; // SwitchExtension->Segments[0]->FastGetPoint(0.5); Segment->Init(middle + vector3(sina, 0.0, cosa), - middle - vector3(sina, 0.0, cosa), 5.0); // nowy odcinek + middle - vector3(sina, 0.0, cosa), 10.0); // nowy odcinek for( auto dynamic : Dynamics ) { // minimalny ruch, aby przeliczyć pozycję dynamic->Move( 0.000001 ); @@ -2293,19 +2637,48 @@ int TTrack::TestPoint(vector3 *Point) return -1; }; +// retrieves list of the track's end points +std::vector +TTrack::endpoints() const { + + switch( eType ) { + case tt_Normal: + case tt_Table: { + return { + glm::dvec3{ Segment->FastGetPoint_0() }, + glm::dvec3{ Segment->FastGetPoint_1() } }; + } + case tt_Switch: + case tt_Cross: { + return { + glm::dvec3{ SwitchExtension->Segments[ 0 ]->FastGetPoint_0() }, + glm::dvec3{ SwitchExtension->Segments[ 0 ]->FastGetPoint_1() }, + glm::dvec3{ SwitchExtension->Segments[ 1 ]->FastGetPoint_0() }, + glm::dvec3{ SwitchExtension->Segments[ 1 ]->FastGetPoint_1() } }; + } + default: { + return{}; + } + } +} + +// calculates path's bounding radius +void +TTrack::radius_() { + + auto const points = endpoints(); + for( auto &point : points ) { + m_area.radius = std::max( + m_area.radius, + static_cast( glm::length( m_area.center - point ) ) ); // extra margin to prevent driven vehicle from flicking + } +} + void TTrack::MovedUp1(float const dh) { // poprawienie przechyłki wymaga wydłużenia podsypki fTexHeight1 += dh; }; -std::string TTrack::NameGet() -{ // ustalenie nazwy toru - if (this) - if (pMyNode) - return pMyNode->asName; - return "none"; -}; - void TTrack::VelocitySet(float v) { // ustawienie prędkości z ograniczeniem do pierwotnej wartości (zapisanej w scenerii) if (SwitchExtension ? SwitchExtension->fVelocity >= 0.0 : false) @@ -2317,25 +2690,25 @@ void TTrack::VelocitySet(float v) fVelocity = v; // nie ma ograniczenia }; -float TTrack::VelocityGet() +double TTrack::VelocityGet() { // pobranie dozwolonej prędkości podczas skanowania - return ((iDamageFlag & 128) ? 0.0f : fVelocity); // tor uszkodzony = prędkość zerowa + return ((iDamageFlag & 128) ? 0.0 : fVelocity); // tor uszkodzony = prędkość zerowa }; void TTrack::ConnectionsLog() { // wypisanie informacji o połączeniach int i; - WriteLog("--> tt_Cross named " + pMyNode->asName); + WriteLog("--> tt_Cross named " + m_name); if (eType == tt_Cross) for (i = 0; i < 2; ++i) { if (SwitchExtension->pPrevs[i]) WriteLog("Point " + std::to_string(i + i + 1) + " -> track " + - SwitchExtension->pPrevs[i]->pMyNode->asName + ":" + + SwitchExtension->pPrevs[i]->m_name + ":" + std::to_string(int(SwitchExtension->iPrevDirection[i]))); if (SwitchExtension->pNexts[i]) WriteLog("Point " + std::to_string(i + i + 2) + " -> track " + - SwitchExtension->pNexts[i]->pMyNode->asName + ":" + + SwitchExtension->pNexts[i]->m_name + ":" + std::to_string(int(SwitchExtension->iNextDirection[i]))); } }; @@ -2389,3 +2762,241 @@ TTrack * TTrack::Connected(int s, double &d) const } return NULL; }; + + + +path_table::~path_table() { + + TIsolated::DeleteAll(); +} + +// legacy method, initializes tracks after deserialization from scenario file +void +path_table::InitTracks() { + + int connection { -1 }; + TTrack *matchingtrack { nullptr }; + +#ifdef EU07_IGNORE_LEGACYPROCESSINGORDER + for( auto *track : m_items ) { +#else + // NOTE: legacy code peformed item operations last-to-first due to way the items were added to the list + // this had impact in situations like two possible connection candidates, where only the first one would be used + for( auto first = std::rbegin(m_items); first != std::rend(m_items); ++first ) { + auto *track = *first; +#endif + + track->AssignEvents( + simulation::Events.FindEvent( track->asEvent0Name ), + simulation::Events.FindEvent( track->asEvent1Name ), + simulation::Events.FindEvent( track->asEvent2Name ) ); + track->AssignallEvents( + simulation::Events.FindEvent( track->asEventall0Name ), + simulation::Events.FindEvent( track->asEventall1Name ), + simulation::Events.FindEvent( track->asEventall2Name ) ); + + auto const trackname { track->name() }; + + if( ( Global::iHiddenEvents & 1 ) + && ( false == trackname.empty() ) ) { + // jeśli podana jest nazwa torów, można szukać eventów skojarzonych przez nazwę + track->AssignEvents( + simulation::Events.FindEvent( trackname + ":event0" ), + simulation::Events.FindEvent( trackname + ":event1" ), + simulation::Events.FindEvent( trackname + ":event2" ) ); + track->AssignallEvents( + simulation::Events.FindEvent( trackname + ":eventall0" ), + simulation::Events.FindEvent( trackname + ":eventall1" ), + simulation::Events.FindEvent( trackname + ":eventall2" ) ); + } + + switch (track->eType) { + // TODO: re-enable + case tt_Table: { + // obrotnicę też łączymy na starcie z innymi torami + // szukamy modelu o tej samej nazwie + auto *instance = simulation::Instances.find( trackname ); + // wiązanie toru z modelem obrotnicy + track->RaAssign( + instance, + simulation::Events.FindEvent( trackname + ":done" ), + simulation::Events.FindEvent( trackname + ":joined" ) ); + if( instance == nullptr ) { + // jak nie ma modelu to pewnie jest wykolejnica, a ta jest domyślnie zamknięta i wykoleja + break; + } + // no break on purpose: + // "jak coś pójdzie źle, to robimy z tego normalny tor" + } + case tt_Normal: { + // tylko proste są podłączane do rozjazdów, stąd dwa rozjazdy się nie połączą ze sobą + if( track->CurrentPrev() == nullptr ) { + // tylko jeśli jeszcze nie podłączony + std::tie( matchingtrack, connection ) = simulation::Region->find_path( track->CurrentSegment()->FastGetPoint_0(), track ); + switch( connection ) { + case -1: // Ra: pierwsza koncepcja zawijania samochodów i statków + // if ((Track->iCategoryFlag&1)==0) //jeśli nie jest torem szynowym + // Track->ConnectPrevPrev(Track,0); //łączenie końca odcinka do samego siebie + break; + case 0: + track->ConnectPrevPrev( matchingtrack, 0 ); + break; + case 1: + track->ConnectPrevNext( matchingtrack, 1 ); + break; + case 2: + track->ConnectPrevPrev( matchingtrack, 0 ); // do Point1 pierwszego + matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie + break; + case 3: + track->ConnectPrevNext( matchingtrack, 1 ); // do Point2 pierwszego + matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie + break; + case 4: + matchingtrack->Switch( 1 ); + track->ConnectPrevPrev( matchingtrack, 2 ); // do Point1 drugiego + matchingtrack->SetConnections( 1 ); // robi też Switch(0) + matchingtrack->Switch( 0 ); + break; + case 5: + matchingtrack->Switch( 1 ); + track->ConnectPrevNext( matchingtrack, 3 ); // do Point2 drugiego + matchingtrack->SetConnections( 1 ); // robi też Switch(0) + matchingtrack->Switch( 0 ); + break; + default: + break; + } + } + if( track->CurrentNext() == nullptr ) { + // tylko jeśli jeszcze nie podłączony + std::tie( matchingtrack, connection ) = simulation::Region->find_path( track->CurrentSegment()->FastGetPoint_1(), track ); + switch( connection ) { + case -1: // Ra: pierwsza koncepcja zawijania samochodów i statków + // if ((Track->iCategoryFlag&1)==0) //jeśli nie jest torem szynowym + // Track->ConnectNextNext(Track,1); //łączenie końca odcinka do samego siebie + break; + case 0: + track->ConnectNextPrev( matchingtrack, 0 ); + break; + case 1: + track->ConnectNextNext( matchingtrack, 1 ); + break; + case 2: + track->ConnectNextPrev( matchingtrack, 0 ); + matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie + break; + case 3: + track->ConnectNextNext( matchingtrack, 1 ); + matchingtrack->SetConnections( 0 ); // zapamiętanie ustawień w Segmencie + break; + case 4: + matchingtrack->Switch( 1 ); + track->ConnectNextPrev( matchingtrack, 2 ); + matchingtrack->SetConnections( 1 ); // robi też Switch(0) + // tmp->Switch(0); + break; + case 5: + matchingtrack->Switch( 1 ); + track->ConnectNextNext( matchingtrack, 3 ); + matchingtrack->SetConnections( 1 ); // robi też Switch(0) + // tmp->Switch(0); + break; + default: + break; + } + } + break; + } + case tt_Switch: { + // dla rozjazdów szukamy eventów sygnalizacji rozprucia + track->AssignForcedEvents( + simulation::Events.FindEvent( trackname + ":forced+" ), + simulation::Events.FindEvent( trackname + ":forced-" ) ); + break; + } + default: { + break; + } + } // switch + + // pobranie nazwy odcinka izolowanego + auto const isolatedname { track->IsolatedName() }; + if( false == isolatedname.empty() ) { + // jeśli została zwrócona nazwa + track->IsolatedEventsAssign( + simulation::Events.FindEvent( isolatedname + ":busy" ), + simulation::Events.FindEvent( isolatedname + ":free" ) ); + } + + if( ( trackname[ 0 ] == '*' ) + && ( !track->CurrentPrev() && track->CurrentNext() ) ) { + // możliwy portal, jeśli nie podłączony od strony 1 + // ustawienie flagi portalu + track->iCategoryFlag |= 0x100; + } + } + + TIsolated *isolated = TIsolated::Root(); + while( isolated ) { + // jeśli się znajdzie, to podać wskaźnik + auto *memorycell = simulation::Memory.find( isolated->asName ); // czy jest komóka o odpowiedniej nazwie + if( memorycell != nullptr ) { + // przypisanie powiązanej komórki + isolated->pMemCell = memorycell; + } + else { + // utworzenie automatycznej komórki + // TODO: determine suitable location for this one, create and add world reference node + scene::node_data nodedata; + nodedata.name = isolated->asName; + auto *memorycell = new TMemCell( nodedata ); // to nie musi mieć nazwy, nazwa w wyszukiwarce wystarczy + simulation::Memory.insert( memorycell ); + isolated->pMemCell = memorycell; // wskaźnik komóki przekazany do odcinka izolowanego + } + isolated = isolated->Next(); + } +} + +// legacy method, sends list of occupied paths over network +void +path_table::TrackBusyList() const { + // wysłanie informacji o wszystkich zajętych odcinkach + for( auto const *path : m_items ) { + if( ( false == path->name().empty() ) // musi być nazwa + && ( false == path->Dynamics.empty() ) ) { + // zajęty + multiplayer::WyslijString( path->name(), 8 ); + } + } +} + +// legacy method, sends list of occupied path sections over network +void +path_table::IsolatedBusyList() const { + // wysłanie informacji o wszystkich odcinkach izolowanych + TIsolated *Current; + for( Current = TIsolated::Root(); Current; Current = Current->Next() ) { + + if( Current->Busy() ) { multiplayer::WyslijString( Current->asName, 11 ); } + else { multiplayer::WyslijString( Current->asName, 10 ); } + } + multiplayer::WyslijString( "none", 10 ); // informacja o końcu listy +} + +// legacy method, sends state of specified path section over network +void +path_table::IsolatedBusy( std::string const &Name ) const { + // wysłanie informacji o odcinku izolowanym (t) + // Ra 2014-06: do wyszukania użyć drzewka nazw + TIsolated *Current; + for( Current = TIsolated::Root(); Current; Current = Current->Next() ) { + if( Current->asName == Name ) { + if( Current->Busy() ) { multiplayer::WyslijString( Current->asName, 11 ); } + else { multiplayer::WyslijString( Current->asName, 10 ); } + // nie sprawdzaj dalszych + return; + } + } + multiplayer::WyslijString( Name, 10 ); // wolny (technically not found but, eh) +} diff --git a/Track.h b/Track.h index 91ea6138..54c0b77f 100644 --- a/Track.h +++ b/Track.h @@ -10,23 +10,29 @@ http://mozilla.org/MPL/2.0/. #pragma once #include -#include "GL/glew.h" -#include "ResourceManager.h" +#include +#include + #include "Segment.h" #include "material.h" +#include "scenenode.h" +#include "Names.h" -typedef enum -{ +namespace scene { +class basic_cell; +} + +enum TTrackType { tt_Unknown, tt_Normal, tt_Switch, tt_Table, tt_Cross, tt_Tributary -} TTrackType; +}; // McZapkie-100502 -typedef enum -{ + +enum TEnvironmentType { e_unknown = -1, e_flat = 0, e_mountains, @@ -34,7 +40,7 @@ typedef enum e_tunnel, e_bridge, e_bank -} TEnvironmentType; +}; // Ra: opracować alternatywny system cieni/świateł z definiowaniem koloru oświetlenia w halach class TEvent; @@ -49,49 +55,43 @@ class TSwitchExtension TSwitchExtension(TTrack *owner, int const what); ~TSwitchExtension(); std::shared_ptr Segments[6]; // dwa tory od punktu 1, pozosta³e dwa od 2? Ra 140101: 6 po³¹czeñ dla skrzy¿owañ - // TTrack *trNear[4]; //tory do³¹czone do punktów 1, 2, 3 i 4 - // dotychczasowe [2]+[2] wskaŸniki zamieniæ na nowe [4] TTrack *pNexts[2]; // tory do³¹czone do punktów 2 i 4 TTrack *pPrevs[2]; // tory do³¹czone do punktów 1 i 3 int iNextDirection[2]; // to te¿ z [2]+[2] przerobiæ na [4] int iPrevDirection[2]; int CurrentIndex = 0; // dla zwrotnicy - double fOffset = 0.0, - fDesiredOffset = 0.0; // aktualne i docelowe położenie napędu iglic - double fOffsetSpeed = 0.1; // prędkość liniowa ruchu iglic - double fOffsetDelay = 0.05; // opóźnienie ruchu drugiej iglicy względem pierwszej // dodatkowy ruch drugiej iglicy po zablokowaniu pierwszej na opornicy + float + fOffset{ 0.f }, + fDesiredOffset{ 0.f }; // aktualne i docelowe położenie napędu iglic + float fOffsetSpeed = 0.1f; // prędkość liniowa ruchu iglic + float fOffsetDelay = 0.05f; // opóźnienie ruchu drugiej iglicy względem pierwszej // dodatkowy ruch drugiej iglicy po zablokowaniu pierwszej na opornicy union { struct { // zmienne potrzebne tylko dla zwrotnicy - double fOffset1, - fOffset2; // przesunięcia iglic - 0=na wprost + float fOffset1, + fOffset2; // przesunięcia iglic - 0=na wprost bool RightSwitch; // czy zwrotnica w prawo }; struct { // zmienne potrzebne tylko dla obrotnicy/przesuwnicy - TGroundNode *pMyNode; // dla obrotnicy do wtórnego podłączania torów - // TAnimContainer *pAnim; //animator modelu dla obrotnicy + // TAnimContainer *pAnim; //animator modelu dla obrotnicy TAnimModel *pModel; // na razie model }; struct { // zmienne dla skrzyżowania int iRoads; // ile dróg się spotyka? - Math3D::vector3 *vPoints; // tablica wierzchołków nawierzchni, generowana przez pobocze -// int iPoints; // liczba faktycznie użytych wierzchołków nawierzchni + glm::vec3 *vPoints; // tablica wierzchołków nawierzchni, generowana przez pobocze bool bPoints; // czy utworzone? }; }; bool bMovement = false; // czy w trakcie animacji - int iLeftVBO = 0, - iRightVBO = 0; // indeksy iglic w VBO - TSubRect *pOwner = nullptr; // sektor, któremu trzeba zgłosić animację + scene::basic_cell *pOwner = nullptr; // TODO: convert this to observer pattern TTrack *pNextAnim = nullptr; // następny tor do animowania TEvent *evPlus = nullptr, *evMinus = nullptr; // zdarzenia sygnalizacji rozprucia float fVelocity = -1.0; // maksymalne ograniczenie prędkości (ustawianej eventem) Math3D::vector3 vTrans; // docelowa translacja przesuwnicy - private: }; class TIsolated @@ -106,7 +106,7 @@ class TIsolated TMemCell *pMemCell = nullptr; // automatyczna komórka pamięci, która współpracuje z odcinkiem izolowanym TIsolated(); TIsolated(const std::string &n, TIsolated *i); - ~TIsolated(); + static void DeleteAll(); static TIsolated * Find(const std::string &n); // znalezienie obiektu albo utworzenie nowego void Modify(int i, TDynamicObject *o); // dodanie lub odjęcie osi bool Busy() { @@ -118,12 +118,11 @@ class TIsolated }; // trajektoria ruchu - opakowanie -class TTrack /*: public Resource*/ { +class TTrack : public editor::basic_node { friend class opengl_renderer; private: - TGroundNode * pMyNode = nullptr; // Ra: proteza, żeby tor znał swoją nazwę TODO: odziedziczyć TTrack z TGroundNode TIsolated * pIsolated = nullptr; // obwód izolowany obsługujący zajęcia/zwolnienia grupy torów std::shared_ptr SwitchExtension; // dodatkowe dane do toru, który jest zwrotnicą std::shared_ptr Segment; @@ -132,7 +131,7 @@ private: // McZapkie-070402: dodalem zmienne opisujace rozmiary tekstur int iTrapezoid = 0; // 0-standard, 1-przechyłka, 2-trapez, 3-oba - double fRadiusTable[ 2 ]; // dwa promienie, drugi dla zwrotnicy + double fRadiusTable[ 2 ] = { 0.0, 0.0 }; // dwa promienie, drugi dla zwrotnicy float fTexLength = 4.0f; // długość powtarzania tekstury w metrach float fTexRatio1 = 1.0f; // proporcja boków tekstury nawierzchni (żeby zaoszczędzić na rozmiarach tekstur...) float fTexRatio2 = 1.0f; // proporcja boków tekstury chodnika (żeby zaoszczędzić na rozmiarach tekstur...) @@ -140,6 +139,7 @@ private: float fTexWidth = 0.9f; // szerokość boku float fTexSlope = 0.9f; + glm::dvec3 m_origin; material_handle m_material1 = 0; // tekstura szyn albo nawierzchni material_handle m_material2 = 0; // tekstura automatycznej podsypki albo pobocza typedef std::vector geometryhandle_sequence; @@ -173,22 +173,22 @@ public: int iQualityFlag = 20; int iDamageFlag = 0; TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie - bool bVisible = true; // czy rysowany int iAction = 0; // czy modyfikowany eventami (specjalna obsługa przy skanowaniu) float fOverhead = -1.0; // można normalnie pobierać prąd (0 dla jazdy bezprądowej po danym odcinku, >0-z opuszczonym i ograniczeniem prędkości) - private: +private: double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia) - public: +public: // McZapkie-100502: double fTrackLength = 100.0; // długość z wpisu, nigdzie nie używana double fRadius = 0.0; // promień, dla zwrotnicy kopiowany z tabeli bool ScannedFlag = false; // McZapkie: do zaznaczania kolorem torów skanowanych przez AI - TTraction *hvOverhead = nullptr; // drut zasilający do szybkiego znalezienia (nie używany) - TGroundNode *nFouling[ 2 ]; // współrzędne ukresu albo oporu kozła + TGroundNode *nFouling[ 2 ] = { nullptr, nullptr }; // współrzędne ukresu albo oporu kozła + + TTrack( scene::node_data const &Nodedata ); + virtual ~TTrack(); - TTrack(TGroundNode *g); - ~TTrack(); void Init(); + static bool sort_by_material( TTrack const *Left, TTrack const *Right ); static TTrack * Create400m(int what, double dx); TTrack * NullCreate(int dir); inline bool IsEmpty() { @@ -207,7 +207,7 @@ public: return trPrev; }; TTrack *Connected(int s, double &d) const; bool SetConnections(int i); - bool Switch(int i, double t = -1.0, double d = -1.0); + bool Switch(int i, float const t = -1.f, float const d = -1.f); bool SwitchForced(int i, TDynamicObject *o); int CrossSegment(int from, int into); inline int GetSwitchState() { @@ -215,7 +215,16 @@ public: SwitchExtension ? SwitchExtension->CurrentIndex : -1); }; - void Load(cParser *parser, Math3D::vector3 pOrigin, std::string name); + // returns number of different routes possible to take from given point + // TODO: specify entry point, number of routes for switches can vary + inline + int + RouteCount() const { + return ( + SwitchExtension != nullptr ? + SwitchExtension->iRoads - 1 : + 1 ); } + void Load(cParser *parser, Math3D::vector3 pOrigin); bool AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2); bool AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2); bool AssignForcedEvents(TEvent *NewEventPlus, TEvent *NewEventMinus); @@ -223,14 +232,21 @@ public: bool AddDynamicObject(TDynamicObject *Dynamic); bool RemoveDynamicObject(TDynamicObject *Dynamic); - void create_geometry(geometrybank_handle const &Bank); // wypełnianie VBO + // set origin point + void + origin( glm::dvec3 Origin ) { + m_origin = Origin; } + // retrieves list of the track's end points + std::vector + endpoints() const; + + void create_geometry( geometrybank_handle const &Bank ); // wypełnianie VBO void RenderDynSounds(); // odtwarzanie dźwięków pojazdów jest niezależne od ich wyświetlania - void RaOwnerSet(TSubRect *o) { - if (SwitchExtension) - SwitchExtension->pOwner = o; }; + void RaOwnerSet( scene::basic_cell *o ) { + if( SwitchExtension ) { SwitchExtension->pOwner = o; } }; bool InMovement(); // czy w trakcie animacji? - void RaAssign(TGroundNode *gn, TAnimModel *am, TEvent *done, TEvent *joined); + void RaAssign( TAnimModel *am, TEvent *done, TEvent *joined ); void RaAnimListAdd(TTrack *t); TTrack * RaAnimate(); @@ -241,22 +257,42 @@ public: std::string IsolatedName(); bool IsolatedEventsAssign(TEvent *busy, TEvent *free); double WidthTotal(); - GLuint TextureGet(int i) { - return ( - i ? - m_material1 : - m_material2 ); }; bool IsGroupable(); int TestPoint( Math3D::vector3 *Point); void MovedUp1(float const dh); - std::string NameGet(); void VelocitySet(float v); - float VelocityGet(); + double VelocityGet(); void ConnectionsLog(); - private: +protected: + // calculates path's bounding radius + void + radius_(); + +private: void EnvironmentSet(); void EnvironmentReset(); }; + + +// collection of virtual tracks and roads present in the scene +class path_table : public basic_table { + +public: + ~path_table(); + // legacy method, initializes tracks after deserialization from scenario file + void + InitTracks(); + // legacy method, sends list of occupied paths over network + void + TrackBusyList() const; + // legacy method, sends list of occupied path sections over network + void + IsolatedBusyList() const; + // legacy method, sends state of specified path section over network + void + IsolatedBusy( std::string const &Name ) const; +}; + //--------------------------------------------------------------------------- diff --git a/Traction.cpp b/Traction.cpp index 1c4c68a4..08cf60f6 100644 --- a/Traction.cpp +++ b/Traction.cpp @@ -14,10 +14,10 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "Traction.h" + +#include "simulation.h" #include "Globals.h" #include "Logs.h" -#include "mctools.h" -#include "TractionPower.h" //--------------------------------------------------------------------------- /* @@ -90,9 +90,92 @@ jawnie nazwę sekcji, ewentualnie nazwę zasilacza (zostanie zastąpiona wskazan sekcji z sąsiedniego przęsła). */ -std::size_t -TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &Origin ) { +TTraction::TTraction( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {} +glm::dvec3 LoadPoint( cParser &Input ) { + // pobranie współrzędnych punktu + glm::dvec3 point; + Input.getTokens( 3 ); + Input + >> point.x + >> point.y + >> point.z; + return point; +} + +void +TTraction::Load( cParser *parser, glm::dvec3 const &pOrigin ) { + + parser->getTokens( 4 ); + *parser + >> asPowerSupplyName + >> NominalVoltage + >> MaxCurrent + >> fResistivity; + if( fResistivity == 0.01f ) { + // tyle jest w sceneriach [om/km] + // taka sensowniejsza wartość za http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf + fResistivity = 0.075f; + } + fResistivity *= 0.001f; // teraz [om/m] + // 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 + auto const material = parser->getToken(); + // 1=miedziana, rysuje się na zielono albo czerwono + // 2=aluminiowa, rysuje się na czarno + if( material == "none" ) { Material = 0; } + else if( material == "al" ) { Material = 2; } + else { Material = 1; } + parser->getTokens( 2 ); + *parser + >> WireThickness + >> DamageFlag; + pPoint1 = LoadPoint( *parser ) + pOrigin; + pPoint2 = LoadPoint( *parser ) + pOrigin; + pPoint3 = LoadPoint( *parser ) + pOrigin; + pPoint4 = LoadPoint( *parser ) + pOrigin; + auto const minheight { parser->getToken() }; + fHeightDifference = ( pPoint3.y - pPoint1.y + pPoint4.y - pPoint2.y ) * 0.5 - minheight; + auto const segmentlength { parser->getToken() }; + iNumSections = ( + segmentlength ? + glm::length( ( pPoint1 - pPoint2 ) ) / segmentlength : + 0 ); + parser->getTokens( 2 ); + *parser + >> Wires + >> WireOffset; + m_visible = ( parser->getToken() == "vis" ); + + std::string token { parser->getToken() }; + if( token == "parallel" ) { + // jawne wskazanie innego przęsła, na które może przestawić się pantograf + parser->getTokens(); + *parser >> asParallel; + } + while( ( false == token.empty() ) + && ( token != "endtraction" ) ) { + + token = parser->getToken(); + } + + Init(); // przeliczenie parametrów + + // calculate traction location + location( interpolate( pPoint2, pPoint1, 0.5 ) ); +} + +// retrieves list of the track's end points +std::vector +TTraction::endpoints() const { + + return { pPoint1, pPoint2 }; +} + +std::size_t +TTraction::create_geometry( geometrybank_handle const &Bank ) { if( m_geometry != null_handle ) { return GfxRenderer.Vertices( m_geometry ).size() / 2; } @@ -106,14 +189,14 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O basic_vertex startvertex, endvertex; startvertex.position = glm::vec3( - pPoint1.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - Origin.x, - pPoint1.y - Origin.y, - pPoint1.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - Origin.z ); + pPoint1.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x, + pPoint1.y - m_origin.y, + pPoint1.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z ); endvertex.position = glm::vec3( - pPoint2.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - Origin.x, - pPoint2.y - Origin.y, - pPoint2.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - Origin.z ); + pPoint2.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x, + pPoint2.y - m_origin.y, + pPoint2.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); // Nie wiem co 'Marcin @@ -130,9 +213,9 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O if( Wires > 1 ) { // lina nośna w kawałkach startvertex.position = glm::vec3( - pPoint3.x - Origin.x, - pPoint3.y - Origin.y, - pPoint3.z - Origin.z ); + pPoint3.x - m_origin.x, + pPoint3.y - m_origin.y, + pPoint3.z - m_origin.z ); for( int i = 0; i < iNumSections - 1; ++i ) { pt3 = pPoint3 + v1 * f; t = ( 1 - std::fabs( f - mid ) * 2 ); @@ -141,9 +224,9 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O && ( i != iNumSections - 2 ) ) ) { endvertex.position = glm::vec3( - pt3.x - Origin.x, - pt3.y - std::sqrt( t ) * fHeightDifference - Origin.y, - pt3.z - Origin.z ); + pt3.x - m_origin.x, + pt3.y - std::sqrt( t ) * fHeightDifference - m_origin.y, + pt3.z - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); startvertex = endvertex; @@ -152,9 +235,9 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O } endvertex.position = glm::vec3( - pPoint4.x - Origin.x, - pPoint4.y - Origin.y, - pPoint4.z - Origin.z ); + pPoint4.x - m_origin.x, + pPoint4.y - m_origin.y, + pPoint4.z - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); } @@ -162,14 +245,14 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O if( Wires > 2 ) { startvertex.position = glm::vec3( - pPoint1.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - Origin.x, - pPoint1.y - Origin.y, - pPoint1.z + ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - Origin.z ); + pPoint1.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x, + pPoint1.y - m_origin.y, + pPoint1.z + ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z ); endvertex.position = glm::vec3( - pPoint2.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - Origin.x, - pPoint2.y - Origin.y, - pPoint2.z + ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - Origin.z ); + pPoint2.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x, + pPoint2.y - m_origin.y, + pPoint2.z + ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); } @@ -179,22 +262,22 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O if( Wires == 4 ) { startvertex.position = glm::vec3( - pPoint3.x - Origin.x, - pPoint3.y - 0.65f * fHeightDifference - Origin.y, - pPoint3.z - Origin.z ); + pPoint3.x - m_origin.x, + pPoint3.y - 0.65f * fHeightDifference - m_origin.y, + pPoint3.z - m_origin.z ); for( int i = 0; i < iNumSections - 1; ++i ) { pt3 = pPoint3 + v1 * f; t = ( 1 - std::fabs( f - mid ) * 2 ); endvertex.position = glm::vec3( - pt3.x - Origin.x, + pt3.x - m_origin.x, pt3.y - std::sqrt( t ) * fHeightDifference - ( ( ( i == 0 ) || ( i == iNumSections - 2 ) ) ? 0.25f * fHeightDifference : 0.05 ) - - Origin.y, - pt3.z - Origin.z ); + - m_origin.y, + pt3.z - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); startvertex = endvertex; @@ -202,9 +285,9 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O } endvertex.position = glm::vec3( - pPoint4.x - Origin.x, - pPoint4.y - 0.65f * fHeightDifference - Origin.y, - pPoint4.z - Origin.z ); + pPoint4.x - m_origin.x, + pPoint4.y - 0.65f * fHeightDifference - m_origin.y, + pPoint4.z - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); } @@ -223,28 +306,28 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O if( ( i % 2 ) == 0 ) { startvertex.position = glm::vec3( - pt3.x - Origin.x, - pt3.y - std::sqrt( t ) * fHeightDifference - ( ( i == 0 ) || ( i == iNumSections - 2 ) ? flo : flo1 ) - Origin.y, - pt3.z - Origin.z ); + pt3.x - m_origin.x, + pt3.y - std::sqrt( t ) * fHeightDifference - ( ( i == 0 ) || ( i == iNumSections - 2 ) ? flo : flo1 ) - m_origin.y, + pt3.z - m_origin.z ); endvertex.position = glm::vec3( - pt4.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - Origin.x, - pt4.y - Origin.y, - pt4.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - Origin.z ); + pt4.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x, + pt4.y - m_origin.y, + pt4.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); } else { startvertex.position = glm::vec3( - pt3.x - Origin.x, - pt3.y - std::sqrt( t ) * fHeightDifference - ( ( i == 0 ) || ( i == iNumSections - 2 ) ? flo : flo1 ) - Origin.y, - pt3.z - Origin.z ); + pt3.x - m_origin.x, + pt3.y - std::sqrt( t ) * fHeightDifference - ( ( i == 0 ) || ( i == iNumSections - 2 ) ? flo : flo1 ) - m_origin.y, + pt3.z - m_origin.z ); endvertex.position = glm::vec3( - pt4.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - Origin.x, - pt4.y - Origin.y, - pt4.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - Origin.z ); + pt4.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x, + pt4.y - m_origin.y, + pt4.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); } @@ -253,14 +336,14 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O || ( i == iNumSections - 3 ) ) ) ) { startvertex.position = glm::vec3( - pt3.x - Origin.x, - pt3.y - std::sqrt( t ) * fHeightDifference - 0.05 - Origin.y, - pt3.z - Origin.z ); + pt3.x - m_origin.x, + pt3.y - std::sqrt( t ) * fHeightDifference - 0.05 - m_origin.y, + pt3.z - m_origin.z ); endvertex.position = glm::vec3( - pt3.x - Origin.x, - pt3.y - std::sqrt( t ) * fHeightDifference - Origin.y, - pt3.z - Origin.z ); + pt3.x - m_origin.x, + pt3.y - std::sqrt( t ) * fHeightDifference - m_origin.y, + pt3.z - m_origin.z ); vertices.emplace_back( startvertex ); vertices.emplace_back( endvertex ); } @@ -276,55 +359,59 @@ TTraction::create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &O int TTraction::TestPoint(glm::dvec3 const &Point) { // sprawdzanie, czy przęsła można połączyć - if (!hvNext[0]) - if( glm::all( glm::epsilonEqual( Point, pPoint1, 0.025 ) ) ) - return 0; - if (!hvNext[1]) - if( glm::all( glm::epsilonEqual( Point, pPoint2, 0.025 ) ) ) - return 1; + if( ( hvNext[ 0 ] == nullptr ) + && ( glm::all( glm::epsilonEqual( Point, pPoint1, 0.025 ) ) ) ) { + return 0; + } + if( ( hvNext[ 1 ] == nullptr ) + && ( glm::all( glm::epsilonEqual( Point, pPoint2, 0.025 ) ) ) ) { + return 1; + } return -1; }; -void TTraction::Connect(int my, TTraction *with, int to) -{ //łączenie segmentu (with) od strony (my) do jego (to) - if (my) - { // do mojego Point2 - hvNext[1] = with; - iNext[1] = to; - } - else - { // do mojego Point1 - hvNext[0] = with; - iNext[0] = to; - } - if (to) - { // do jego Point2 - with->hvNext[1] = this; - with->iNext[1] = my; - } - else - { // do jego Point1 - with->hvNext[0] = this; - with->iNext[0] = my; - } - if (hvNext[0]) // jeśli z obu stron podłączony - if (hvNext[1]) - iLast = 0; // to nie jest ostatnim - if (with->hvNext[0]) // temu też, bo drugi raz łączenie się nie nie wykona - if (with->hvNext[1]) - with->iLast = 0; // to nie jest ostatnim -}; +//łączenie segmentu (with) od strony (my) do jego (to) +void +TTraction::Connect(int my, TTraction *with, int to) { -bool TTraction::WhereIs() -{ // ustalenie przedostatnich przęseł - if (iLast) - return (iLast == 1); // ma już ustaloną informację o położeniu - if (hvNext[0] ? hvNext[0]->iLast == 1 : false) // jeśli poprzedni jest ostatnim - iLast = 2; // jest przedostatnim - else if (hvNext[1] ? hvNext[1]->iLast == 1 : false) // jeśli następny jest ostatnim - iLast = 2; // jest przedostatnim - return (iLast == 1); // ostatnie będą dostawać zasilanie -}; + hvNext[ my ] = with; + iNext[ my ] = to; + + if( ( hvNext[ 0 ] != nullptr ) + && ( hvNext[ 1 ] != nullptr ) ) { + // jeśli z obu stron podłączony to nie jest ostatnim + iLast = 0; + } + + with->hvNext[ to ] = this; + with->iNext[ to ] = my; + + if( ( with->hvNext[ 0 ] != nullptr ) + && ( with->hvNext[ 1 ] != nullptr ) ) { + // temu też, bo drugi raz łączenie się nie nie wykona + with->iLast = 0; + } +} + +// ustalenie przedostatnich przęseł +bool TTraction::WhereIs() { + + if( iLast ) { + // ma już ustaloną informację o położeniu + return ( iLast & 1 ); + } + for( int endindex = 0; endindex < 2; ++endindex ) { + if( hvNext[ endindex ] == nullptr ) { + // no neighbour piece means this one is last + iLast |= 1; + } + else if( hvNext[ endindex ]->hvNext[ 1 - iNext[ endindex ] ] == nullptr ) { + // otherwise if that neighbour has no further connection on the opposite end then this piece is second-last + iLast |= 2; + } + } + return (iLast & 1); // ostatnie będą dostawać zasilanie +} void TTraction::Init() { // przeliczenie parametrów @@ -441,6 +528,18 @@ double TTraction::VoltageGet(double u, double i) return 0.0; // gdy nie podłączony wcale? }; +// calculates path's bounding radius +void +TTraction::radius_() { + + auto const points = endpoints(); + for( auto &point : points ) { + m_area.radius = std::max( + m_area.radius, + static_cast( glm::length( m_area.center - point ) ) ); + } +} + glm::vec3 TTraction::wire_color() const { @@ -488,30 +587,41 @@ TTraction::wire_color() const { case 1: { // czerwone z podłączonym zasilaniem 1 - color.r = 1.0f; - color.g = 0.0f; - color.b = 0.0f; +// color.r = 1.0f; +// color.g = 0.0f; +// color.b = 0.0f; + // cyan + color = glm::vec3 { 0.f, 174.f / 255.f, 239.f / 255.f }; break; } case 2: { // zielone z podłączonym zasilaniem 2 - color.r = 0.0f; - color.g = 1.0f; - color.b = 0.0f; +// color.r = 0.0f; +// color.g = 1.0f; +// color.b = 0.0f; + // yellow + color = glm::vec3 { 240.f / 255.f, 228.f / 255.f, 0.f }; break; } case 3: { //żółte z podłączonym zasilaniem z obu stron - color.r = 1.0f; - color.g = 1.0f; - color.b = 0.0f; +// color.r = 1.0f; +// color.g = 1.0f; +// color.b = 0.0f; + // green + color = glm::vec3 { 0.f, 239.f / 255.f, 118.f / 255.f }; break; } case 4: { // niebieskie z podłączonym zasilaniem - color.r = 0.5f; - color.g = 0.5f; - color.b = 1.0f; +// color.r = 0.5f; +// color.g = 0.5f; +// color.b = 1.0f; + // white for powered, red for ends + color = ( + psPowered != nullptr ? + glm::vec3{ 239.f / 255.f, 239.f / 255.f, 239.f / 255.f } : + glm::vec3{ 239.f / 255.f, 128.f / 255.f, 128.f / 255.f } ); break; } default: { break; } @@ -521,6 +631,262 @@ TTraction::wire_color() const { color.g *= 0.6f; color.b *= 0.6f; } +/* + switch( iTries ) { + case 0: { + color = glm::vec3{ 239.f / 255.f, 128.f / 255.f, 128.f / 255.f }; // red + break; + } + case 1: { + color = glm::vec3{ 240.f / 255.f, 228.f / 255.f, 0.f }; // yellow + break; + } + case 5: { + color = glm::vec3{ 0.f / 255.f, 239.f / 255.f, 118.f / 255.f }; // green + break; + } + default: { + color = glm::vec3{ 239.f / 255.f, 239.f / 255.f, 239.f / 255.f }; + break; + } + } +*/ +/* + switch( iLast ) { + case 0: { + color = glm::vec3{ 240.f / 255.f, 228.f / 255.f, 0.f }; // yellow + break; + } + case 1: { + color = glm::vec3{ 239.f / 255.f, 128.f / 255.f, 128.f / 255.f }; // red + break; + } + case 2: { + color = glm::vec3{ 0.f / 255.f, 239.f / 255.f, 118.f / 255.f }; // green + break; + } + default: { + color = glm::vec3{ 239.f / 255.f, 239.f / 255.f, 239.f / 255.f }; + break; + } + } +*/ } return color; } + + + +// legacy method, initializes traction after deserialization from scenario file +void +traction_table::InitTraction() { + + //łączenie drutów ze sobą oraz z torami i eventami +// TGroundNode *nCurrent, *nTemp; +// TTraction *tmp; // znalezione przęsło + + int connection { -1 }; + TTraction *matchingtraction { nullptr }; + + for( auto *traction : m_items ) { + // 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 + auto *powersource = simulation::Powergrid.find( traction->asPowerSupplyName ); + if( powersource ) { + // jak zasilacz znaleziony to podłączyć do przęsła + traction->PowerSet( powersource ); + } + else { + if( ( traction->asPowerSupplyName != "*" ) // gwiazdka dla przęsła z izolatorem + && ( traction->asPowerSupplyName != "none" ) ) { // dopuszczamy na razie brak podłączenia? + // logowanie błędu i utworzenie zasilacza o domyślnej zawartości + ErrorLog( "Bad scenario: traction piece connected to nonexistent power source \"" + traction->asPowerSupplyName + "\"" ); + scene::node_data nodedata; + nodedata.name = traction->asPowerSupplyName; + powersource = new TTractionPowerSource( nodedata ); + powersource->Init( traction->NominalVoltage, traction->MaxCurrent ); + simulation::Powergrid.insert( powersource ); + } + } + } + +#ifdef EU07_IGNORE_LEGACYPROCESSINGORDER + for( auto *traction : m_items ) { +#else + // NOTE: legacy code peformed item operations last-to-first due to way the items were added to the list + // this had impact in situations like two possible connection candidates, where only the first one would be used + for( auto first = std::rbegin(m_items); first != std::rend(m_items); ++first ) { + auto *traction = *first; +#endif + if( traction->hvNext[ 0 ] == nullptr ) { + // tylko jeśli jeszcze nie podłączony + std::tie( matchingtraction, connection ) = simulation::Region->find_traction( traction->pPoint1, traction ); + switch (connection) { + case 0: + case 1: { + traction->Connect( 0, matchingtraction, connection ); + break; + } + default: { + break; + } + } + if( traction->hvNext[ 0 ] ) { + // jeśli został podłączony + if( ( traction->psSection != nullptr ) + && ( matchingtraction->psSection != nullptr ) ) { + // tylko przęsło z izolatorem może nie mieć zasilania, bo ma 2, trzeba sprawdzać sąsiednie + if( traction->psSection != matchingtraction->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( ( true == traction->psSection->bSection ) + && ( false == matchingtraction->psSection->bSection ) ) { + //(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji + matchingtraction->PowerSet( traction->psSection ); // zastąpienie wskazaniem sekcji + } + else if( ( false == traction->psSection->bSection ) + && ( true == matchingtraction->psSection->bSection ) ) { + //(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji + traction->PowerSet( matchingtraction->psSection ); // zastąpienie wskazaniem sekcji + } + else { + // jeśli obie to sekcje albo obie podstacje, to będzie błąd + ErrorLog( "Bad scenario: faulty traction power connection at location " + to_string( traction->pPoint1 ) ); + } + } + } + } + } + if( traction->hvNext[ 1 ] == nullptr ) { + // tylko jeśli jeszcze nie podłączony + std::tie( matchingtraction, connection ) = simulation::Region->find_traction( traction->pPoint2, traction ); + switch (connection) { + case 0: + case 1: { + traction->Connect( 1, matchingtraction, connection ); + break; + } + default: { + break; + } + } + if( traction->hvNext[ 1 ] ) { + // jeśli został podłączony + if( ( traction->psSection != nullptr ) + && ( matchingtraction->psSection != nullptr ) ) { + // tylko przęsło z izolatorem może nie mieć zasilania, bo ma 2, trzeba sprawdzać sąsiednie + if( traction->psSection != matchingtraction->psSection ) { + // to może być albo podłączenie podstacji lub kabiny sekcyjnej do sekcji, albo błąd + if( ( true == traction->psSection->bSection ) + && ( false == matchingtraction->psSection->bSection ) ) { + //(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji + matchingtraction->PowerSet( traction->psSection ); // zastąpienie wskazaniem sekcji + } + else if( ( false == traction->psSection->bSection ) + && ( true == matchingtraction->psSection->bSection ) ) { + //(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji + traction->PowerSet( matchingtraction->psSection ); // zastąpienie wskazaniem sekcji + } + else { + // jeśli obie to sekcje albo obie podstacje, to będzie błąd + ErrorLog( "Bad scenario: faulty traction power connection at location " + to_string( traction->pPoint2 ) ); + } + } + } + } + } + } + + auto endcount { 0 }; + for( auto *traction : m_items ) { + // operacje mające na celu wykrywanie bieżni wspólnych i łączenie przęseł naprążania + if( traction->WhereIs() ) { + // true for outer pieces of the traction section + traction->iTries = 5; // oznaczanie końcowych + ++endcount; + } + if (traction->fResistance[0] == 0.0) { + // obliczanie przęseł w segmencie z bezpośrednim zasilaniem + traction->ResistanceCalc(); + traction->iTries = 0; // nie potrzeba mu szukać zasilania + } + } + + std::vector ends; ends.reserve( endcount ); + for( auto *traction : m_items ) { + //łączenie bieżni wspólnych, w tym oznaczanie niepodanych jawnie + if( false == traction->asParallel.empty() ) { + // będzie wskaźnik na inne przęsło + if( ( traction->asParallel == "none" ) + || ( traction->asParallel == "*" ) ) { + // jeśli nieokreślone + traction->iLast |= 2; // jakby przedostatni - niech po prostu szuka (iLast już przeliczone) + } + else if( traction->hvParallel == nullptr ) { + // jeśli jeszcze nie został włączony w kółko + auto *nTemp = find( traction->asParallel ); + if( nTemp != nullptr ) { + // o ile zostanie znalezione przęsło o takiej nazwie + if( nTemp->hvParallel == nullptr ) { + // jeśli tamten jeszcze nie ma wskaźnika bieżni wspólnej + traction->hvParallel = nTemp; // wpisać siebie i dalej dać mu wskaźnik zwrotny + } + else { + // a jak ma, to albo dołączyć się do kółeczka + traction->hvParallel = nTemp->hvParallel; // przjąć dotychczasowy wskaźnik od niego + } + nTemp->hvParallel = traction; // i na koniec ustawienie wskaźnika zwrotnego + } + if( traction->hvParallel == nullptr ) { + ErrorLog( "Missed overhead: " + traction->asParallel ); // logowanie braku + } + } + } + if( traction->iTries == 5 ) { + // jeśli zaznaczony do podłączenia + // wypełnianie tabeli końców w celu szukania im połączeń + ends.emplace_back( traction ); + } + } + + bool connected; // nieefektywny przebieg kończy łączenie + do { + // ustalenie zastępczej rezystancji dla każdego przęsła + // flaga podłączonych przęseł końcowych: -1=puste wskaźniki, 0=coś zostało, 1=wykonano łączenie + connected = false; + for( auto &end : ends ) { + // załatwione będziemy zerować + if( end == nullptr ) { continue; } + // każdy przebieg to próba podłączenia końca segmentu naprężania do innego zasilanego przęsła + if( end->hvNext[ 0 ] != nullptr ) { + // jeśli końcowy ma ciąg dalszy od strony 0 (Point1), szukamy odcinka najbliższego do Point2 + std::tie( matchingtraction, connection ) = simulation::Region->find_traction( end->pPoint2, end, 0 ); + if( matchingtraction != nullptr ) { + // jak znalezione przęsło z zasilaniem, to podłączenie "równoległe" + end->ResistanceCalc( 0, matchingtraction->fResistance[ connection ], matchingtraction->psPower[ connection ] ); + // jak coś zostało podłączone, to może zasilanie gdzieś dodatkowo dotrze + connected = true; + end = nullptr; + } + } + else if( end->hvNext[ 1 ] != nullptr ) { + // jeśli końcowy ma ciąg dalszy od strony 1 (Point2), szukamy odcinka najbliższego do Point1 + std::tie( matchingtraction, connection ) = simulation::Region->find_traction( end->pPoint1, end, 1 ); + if( matchingtraction != nullptr ) { + // jak znalezione przęsło z zasilaniem, to podłączenie "równoległe" + end->ResistanceCalc( 1, matchingtraction->fResistance[ connection ], matchingtraction->psPower[ connection ] ); + // jak coś zostało podłączone, to może zasilanie gdzieś dodatkowo dotrze + connected = true; + end = nullptr; + } + } + else { + // gdy koniec jest samotny, to na razie nie zostanie podłączony (nie powinno takich być) + end = nullptr; + } + } + } while( true == connected ); +} diff --git a/Traction.h b/Traction.h index b3077103..5834ce8d 100644 --- a/Traction.h +++ b/Traction.h @@ -10,14 +10,17 @@ http://mozilla.org/MPL/2.0/. #pragma once #include -#include "GL/glew.h" -#include "dumb3d.h" -#include "openglgeometrybank.h" + +#include "scenenode.h" +#include "Segment.h" +#include "material.h" +#include "Names.h" +#include "Model3d.h" class TTractionPowerSource; -class TTraction -{ // drut zasilający, dla wskaźników używać przedrostka "hv" +class TTraction : public editor::basic_node { + friend class opengl_renderer; public: // na razie @@ -25,11 +28,11 @@ class TTraction TTractionPowerSource *psPowered { nullptr }; // ustawione tylko dla bezpośrednio zasilanego przęsła TTraction *hvNext[ 2 ] { nullptr, nullptr }; //łączenie drutów w sieć int iNext[ 2 ] { 0, 0 }; // do którego końca się łączy - int iLast { 1 }; //że niby ostatni drut // ustawiony bit 0, jeśli jest ostatnim drutem w sekcji; bit1 - przedostatni + int iLast { 0 }; //że niby ostatni drut // ustawiony bit 0, jeśli jest ostatnim drutem w sekcji; bit1 - przedostatni public: glm::dvec3 pPoint1, pPoint2, pPoint3, pPoint4; glm::dvec3 vParametric; // współczynniki równania parametrycznego odcinka - double fHeightDifference { 0.0 }; //,fMiddleHeight; + double fHeightDifference { 0.0 }; int iNumSections { 0 }; float NominalVoltage { 0.0f }; float MaxCurrent { 0.0f }; @@ -44,14 +47,25 @@ class TTraction std::string asParallel; // nazwa przęsła, z którym może być bieżnia wspólna TTraction *hvParallel { nullptr }; // jednokierunkowa i zapętlona lista przęseł ewentualnej bieżni wspólnej float fResistance[ 2 ] { -1.0f, -1.0f }; // rezystancja zastępcza do punktu zasilania (0: przęsło zasilane, <0: do policzenia) - int iTries { 0 }; + int iTries { 1 }; // 0 is used later down the road to mark directly powered pieces int PowerState { 0 }; // type of incoming power, if any // visualization data + glm::dvec3 m_origin; geometry_handle m_geometry; + TTraction( scene::node_data const &Nodedata ); + + void Load( cParser *parser, glm::dvec3 const &pOrigin ); + // set origin point + void + origin( glm::dvec3 Origin ) { + m_origin = Origin; } + // retrieves list of the track's end points + std::vector + endpoints() const; // creates geometry data in specified geometry bank. returns: number of created elements, or NULL // NOTE: deleting nodes doesn't currently release geometry data owned by the node. TODO: implement erasing individual geometry chunks and banks - std::size_t create_geometry( geometrybank_handle const &Bank, glm::dvec3 const &Origin ); + std::size_t create_geometry( geometrybank_handle const &Bank ); int TestPoint(glm::dvec3 const &Point); void Connect(int my, TTraction *with, int to); void Init(); @@ -59,8 +73,25 @@ class TTraction void ResistanceCalc(int d = -1, double r = 0, TTractionPowerSource *ps = nullptr); void PowerSet(TTractionPowerSource *ps); double VoltageGet(double u, double i); + +protected: + // calculates piece's bounding radius + void + radius_(); + private: glm::vec3 wire_color() const; }; + + +// collection of virtual tracks and roads present in the scene +class traction_table : public basic_table { + +public: + // legacy method, initializes traction after deserialization from scenario file + void + InitTraction(); +}; + //--------------------------------------------------------------------------- diff --git a/TractionPower.cpp b/TractionPower.cpp index ade5cc5b..c83eb49a 100644 --- a/TractionPower.cpp +++ b/TractionPower.cpp @@ -15,19 +15,13 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "TractionPower.h" + #include "Logs.h" -#include "Ground.h" //--------------------------------------------------------------------------- -TTractionPowerSource::TTractionPowerSource(TGroundNode const *node) : - gMyNode( node ) -{ - psNode[0] = nullptr; // sekcje zostaną podłączone do zasilaczy - psNode[1] = nullptr; -}; - -TTractionPowerSource::~TTractionPowerSource(){}; +TTractionPowerSource::TTractionPowerSource( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {} +// legacy constructor void TTractionPowerSource::Init(double const u, double const i) { // ustawianie zasilacza przy braku w scenerii @@ -36,61 +30,62 @@ void TTractionPowerSource::Init(double const u, double const i) MaxOutputCurrent = i; }; -bool TTractionPowerSource::Load(cParser *parser) -{ - std::string token; - // AnsiString str; - // str= Parser->GetNextSymbol()LowerCase(); - // asName= str; - parser->getTokens(5); - *parser >> NominalVoltage >> VoltageFrequency >> InternalRes >> MaxOutputCurrent >> - FastFuseTimeOut; - parser->getTokens(); - *parser >> FastFuseRepetition; - parser->getTokens(); - *parser >> SlowFuseTimeOut; - parser->getTokens(); - *parser >> token; - if (token.compare("recuperation") == 0) - Recuperation = true; - else if (token.compare("section") == 0) // odłącznik sekcyjny - bSection = true; // nie jest źródłem zasilania, a jedynie informuje o prądzie odłączenia - // sekcji z obwodu - parser->getTokens(); - *parser >> token; - if (token.compare("end") != 0) - Error("tractionpowersource end statement missing"); - // if (!bSection) //odłącznik sekcji zasadniczo nie ma impedancji (0.01 jest OK) - if (InternalRes < 0.1) // coś mała ta rezystancja była... - InternalRes = 0.2; // tak około 0.2, wg - // http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf - return true; -}; +bool TTractionPowerSource::Load(cParser *parser) { -bool TTractionPowerSource::Render() -{ + parser->getTokens( 10, false ); + *parser + >> m_area.center.x + >> m_area.center.y + >> m_area.center.z + >> NominalVoltage + >> VoltageFrequency + >> InternalRes + >> MaxOutputCurrent + >> FastFuseTimeOut + >> FastFuseRepetition + >> SlowFuseTimeOut; + + std::string token { parser->getToken() }; + if( token == "recuperation" ) { + Recuperation = true; + } + else if( token == "section" ) { + // odłącznik sekcyjny + // nie jest źródłem zasilania, a jedynie informuje o prądzie odłączenia sekcji z obwodu + bSection = true; + } + // skip rest of the section + while( ( false == token.empty() ) + && ( token != "end" ) ) { + + token = parser->getToken(); + } + + if( InternalRes < 0.1 ) { + // coś mała ta rezystancja była... + // tak około 0.2, wg + // http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf + InternalRes = 0.2; + } return true; }; bool TTractionPowerSource::Update(double dt) { // powinno być wykonane raz na krok fizyki - // if (NominalVoltage * TotalPreviousAdmitance > - // MaxOutputCurrent * 0.00000005) // iloczyn napięcia i admitancji daje prąd - // ErrorLog("Power overload: \"" + gMyNode->asName + "\" with current " + AnsiString(NominalVoltage * TotalPreviousAdmitance) + "A"); - if (NominalVoltage * TotalPreviousAdmitance > - MaxOutputCurrent) // iloczyn napięcia i admitancji daje prąd - { + // iloczyn napięcia i admitancji daje prąd + if (NominalVoltage * TotalPreviousAdmitance > MaxOutputCurrent) { + FastFuse = true; FuseCounter += 1; - if (FuseCounter > FastFuseRepetition) - { + if (FuseCounter > FastFuseRepetition) { + SlowFuse = true; - ErrorLog("Power overload: \"" + gMyNode->asName + "\" disabled for " + - std::to_string(SlowFuseTimeOut) + "s"); + ErrorLog( "Power overload: \"" + m_name + "\" disabled for " + std::to_string( SlowFuseTimeOut ) + "s" ); } - else - ErrorLog("Power overload: \"" + gMyNode->asName + "\" disabled for " + - std::to_string(FastFuseTimeOut) + "s"); + else { + ErrorLog( "Power overload: \"" + m_name + "\" disabled for " + std::to_string( FastFuseTimeOut ) + "s" ); + } + FuseTimer = 0; } if (FastFuse || SlowFuse) @@ -145,4 +140,15 @@ void TTractionPowerSource::PowerSet(TTractionPowerSource *ps) // else ErrorLog("nie może być więcej punktów zasilania niż dwa"); }; + + +// legacy method, calculates changes in simulation state over specified time +void +powergridsource_table::update( double const Deltatime ) { + + for( auto *powersource : m_items ) { + powersource->Update( Deltatime ); + } +} + //--------------------------------------------------------------------------- diff --git a/TractionPower.h b/TractionPower.h index d85330f7..0af243ec 100644 --- a/TractionPower.h +++ b/TractionPower.h @@ -7,14 +7,14 @@ obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef TractionPowerH -#define TractionPowerH +#pragma once + +#include "scenenode.h" #include "parser.h" //Tolaris-010603 +#include "Names.h" -class TGroundNode; +class TTractionPowerSource : public editor::basic_node { -class TTractionPowerSource -{ private: double NominalVoltage = 0.0; double VoltageFrequency = 0.0; @@ -33,27 +33,32 @@ class TTractionPowerSource bool SlowFuse = false; double FuseTimer = 0.0; int FuseCounter = 0; - TGroundNode const *gMyNode = nullptr; // wskaźnik na węzeł rodzica - protected: - public: // zmienne publiczne - TTractionPowerSource *psNode[2]; // zasilanie na końcach dla sekcji +public: + // zmienne publiczne + TTractionPowerSource *psNode[ 2 ] = { nullptr, nullptr }; // zasilanie na końcach dla sekcji bool bSection = false; // czy jest sekcją - public: - // AnsiString asName; - TTractionPowerSource(TGroundNode const *node); - ~TTractionPowerSource(); + + TTractionPowerSource( scene::node_data const &Nodedata ); + void Init(double const u, double const i); bool Load(cParser *parser); - bool Render(); bool Update(double dt); double CurrentGet(double res); - void VoltageSet(double const v) - { - NominalVoltage = v; - }; + void VoltageSet(double const v) { + NominalVoltage = v; }; void PowerSet(TTractionPowerSource *ps); }; + + +// collection of generators for power grid present in the scene +class powergridsource_table : public basic_table { + +public: + // legacy method, calculates changes in simulation state over specified time + void + update( double const Deltatime ); +}; + //--------------------------------------------------------------------------- -#endif diff --git a/Train.cpp b/Train.cpp index 9800f0bc..fffb3df6 100644 --- a/Train.cpp +++ b/Train.cpp @@ -360,11 +360,6 @@ TTrain::TTrain() { TTrain::~TTrain() { - if (DynamicObject) - if (DynamicObject->Mechanik) - DynamicObject->Mechanik->TakeControl( - true); // likwidacja kabiny wymaga przejęcia przez AI - sound_man->destroy_sound(&dsbNastawnikJazdy); sound_man->destroy_sound(&dsbNastawnikBocz); sound_man->destroy_sound(&dsbRelay); @@ -515,11 +510,13 @@ PyObject *TTrain::GetTrainState() { PyDict_SetItemString( dict, "velocity", PyGetFloat( mover->Vel ) ); PyDict_SetItemString( dict, "tractionforce", PyGetFloat( mover->Ft ) ); PyDict_SetItemString( dict, "slipping_wheels", PyGetBool( mover->SlippingWheels ) ); + PyDict_SetItemString( dict, "sanding", PyGetBool( mover->SandDose )); // electric current data PyDict_SetItemString( dict, "traction_voltage", PyGetFloat( mover->RunningTraction.TractionVoltage ) ); PyDict_SetItemString( dict, "voltage", PyGetFloat( mover->Voltage ) ); PyDict_SetItemString( dict, "im", PyGetFloat( mover->Im ) ); PyDict_SetItemString( dict, "fuse", PyGetBool( mover->FuseFlag ) ); + PyDict_SetItemString( dict, "epfuse", PyGetBool( mover->EpFuse )); // induction motor state data const char* TXTT[ 10 ] = { "fd", "fdt", "fdb", "pd", "pdt", "pdb", "itothv", "1", "2", "3" }; const char* TXTC[ 10 ] = { "fr", "frt", "frb", "pr", "prt", "prb", "im", "vm", "ihv", "uhv" }; @@ -559,6 +556,7 @@ PyObject *TTrain::GetTrainState() { PyDict_SetItemString( dict, ( std::string( "code_" ) + std::to_string( i + 1 ) ).c_str(), PyGetString( std::string( std::to_string( iUnits[ i ] ) + cCode[ i ] ).c_str() ) ); PyDict_SetItemString( dict, ( std::string( "car_name" ) + std::to_string( i + 1 ) ).c_str(), PyGetString( asCarName[ i ].c_str() ) ); + PyDict_SetItemString( dict, ( std::string( "slip_" ) + std::to_string( i + 1 )).c_str(), PyGetBool( bSlip[i]) ); } // ai state data auto const &driver = DynamicObject->Mechanik; @@ -1630,7 +1628,7 @@ void TTrain::OnCommand_linebreakertoggle( TTrain *Train, command_data const &Com // sound feedback, engine start for diesel vehicle Train->play_sound( Train->dsbDieselIgnition ); // side-effects - Train->mvControlled->ConverterSwitch( Train->ggConverterButton.GetValue() > 0.5 ); + Train->mvControlled->ConverterSwitch( ( Train->ggConverterButton.GetValue() > 0.5 ) || ( Train->mvControlled->ConverterStart == start::automatic ) ); Train->mvControlled->CompressorSwitch( Train->ggCompressorButton.GetValue() > 0.5 ); } } @@ -3400,19 +3398,33 @@ if d = d->Prev(); // w drugą stronę też } } - else if (cKey == GLFW_KEY_RIGHT_BRACKET) - { - while (d) - { - d->Move(-100.0 * d->DirectionGet()); - d = d->Next(); // pozostałe też - } - d = DynamicObject->Prev(); - while (d) - { - d->Move(-100.0 * d->DirectionGet()); - d = d->Prev(); // w drugą stronę też - } + else if (cKey == GLFW_KEY_RIGHT_BRACKET) + { + while (d) + { + d->Move(-100.0 * d->DirectionGet()); + d = d->Next(); // pozostałe też + } + d = DynamicObject->Prev(); + while (d) + { + d->Move(-100.0 * d->DirectionGet()); + d = d->Prev(); // w drugą stronę też + } + } + else if (cKey == GLFW_KEY_TAB) + { + while (d) + { + d->MoverParameters->V+= d->DirectionGet()*2.78; + d = d->Next(); // pozostałe też + } + d = DynamicObject->Prev(); + while (d) + { + d->MoverParameters->V += d->DirectionGet()*2.78; + d = d->Prev(); // w drugą stronę też + } } } if (cKey == GLFW_KEY_MINUS) @@ -3697,21 +3709,22 @@ bool TTrain::Update( double const Deltatime ) bDoors[i][2] = (p->dDoorMoveL > 0.001); iDoorNo[i] = p->iAnimType[ANIM_DOORS]; iUnits[i] = iUnitNo; - cCode[i] = p->MoverParameters->TypeName[p->MoverParameters->TypeName.length()]; - asCarName[i] = p->GetName(); + cCode[i] = p->MoverParameters->TypeName[p->MoverParameters->TypeName.length() - 1]; + asCarName[i] = p->name(); bPants[iUnitNo - 1][0] = (bPants[iUnitNo - 1][0] || p->MoverParameters->PantFrontUp); bPants[iUnitNo - 1][1] = (bPants[iUnitNo - 1][1] || p->MoverParameters->PantRearUp); bComp[iUnitNo - 1][0] = (bComp[iUnitNo - 1][0] || p->MoverParameters->CompressorAllow); + bSlip[i] = p->MoverParameters->SlippingWheels; if (p->MoverParameters->CompressorSpeed > 0.00001) { bComp[iUnitNo - 1][1] = (bComp[iUnitNo - 1][1] || p->MoverParameters->CompressorFlag); } if ((in < 8) && (p->MoverParameters->eimc[eimc_p_Pmax] > 1)) { - fEIMParams[1 + in][0] = p->MoverParameters->eimv[eimv_Fr]; + fEIMParams[1 + in][0] = p->MoverParameters->eimv[eimv_Fmax]; fEIMParams[1 + in][1] = Max0R(fEIMParams[1 + in][0], 0); fEIMParams[1 + in][2] = -Min0R(fEIMParams[1 + in][0], 0); - fEIMParams[1 + in][3] = p->MoverParameters->eimv[eimv_Fr] / + fEIMParams[1 + in][3] = p->MoverParameters->eimv[eimv_Fmax] / Max0R(p->MoverParameters->eimv[eimv_Fful], 1); fEIMParams[1 + in][4] = Max0R(fEIMParams[1 + in][3], 0); fEIMParams[1 + in][5] = -Min0R(fEIMParams[1 + in][3], 0); @@ -3743,6 +3756,7 @@ bool TTrain::Update( double const Deltatime ) bDoors[i][0] = false; bDoors[i][1] = false; bDoors[i][2] = false; + bSlip[i] = false; iUnits[i] = 0; cCode[i] = 0; //'0'; asCarName[i] = ""; @@ -6385,7 +6399,7 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) } else if (token == "pyscreen:") { - pyScreens.init(*parser, DynamicObject->mdKabina, DynamicObject->GetName(), + pyScreens.init(*parser, DynamicObject->mdKabina, DynamicObject->name(), NewCabNo); } // btLampkaUnknown.Init("unknown",mdKabina,false); @@ -6868,9 +6882,11 @@ void TTrain::set_cab_controls() { if( true == bCabLightDim ) { ggCabLightDimButton.PutValue( 1.0 ); } - if( true == InstrumentLightActive ) { - ggInstrumentLightButton.PutValue( 1.0 ); - } + + ggInstrumentLightButton.PutValue( ( + InstrumentLightActive ? + 1.0 : + 0.0 ) ); // doors // NOTE: we're relying on the cab models to have switches reversed for the rear cab(?) ggDoorLeftButton.PutValue( mvOccupied->DoorLeftOpened ? 1.0 : 0.0 ); @@ -7168,7 +7184,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con /* TGauge *gg { nullptr }; // roboczy wskaźnik na obiekt animujący gałkę */ - std::unordered_map gauges = { + std::unordered_map const gauges = { { "mainctrl:", ggMainCtrl }, { "scndctrl:", ggScndCtrl }, { "dirkey:" , ggDirKey }, diff --git a/Train.h b/Train.h index 194937d6..2a498336 100644 --- a/Train.h +++ b/Train.h @@ -72,12 +72,14 @@ public: class TTrain { + friend class TWorld; // temporary due to use of play_sound TODO: refactor this + public: bool CabChange(int iDirection); bool ShowNextCurrent; // pokaz przd w podlaczonej lokomotywie (ET41) bool InitializeCab(int NewCabNo, std::string const &asFileName); TTrain(); - ~TTrain(); + ~TTrain(); // McZapkie-010302 bool Init(TDynamicObject *NewDynamicObject, bool e3d = false); void OnKeyDown(int cKey); @@ -468,6 +470,7 @@ public: // reszta może by?publiczna int iUnits[20]; // numer jednostki int iDoorNo[20]; // liczba drzwi char cCode[20]; // kod pojazdu + bool bSlip[20]; // poślizg kół pojazdu std::string asCarName[20]; // nazwa czlonu bool bMains[8]; // WSy float fCntVol[8]; // napiecie NN diff --git a/TrkFoll.cpp b/TrkFoll.cpp index ceddcb1c..ddfa4fee 100644 --- a/TrkFoll.cpp +++ b/TrkFoll.cpp @@ -15,11 +15,10 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "TrkFoll.h" + +#include "simulation.h" #include "Globals.h" #include "Logs.h" -#include "Driver.h" -#include "DynObj.h" -#include "Event.h" TTrackFollower::~TTrackFollower() { @@ -116,7 +115,7 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) && ( pCurrentTrack->evEvent1 ) && ( !pCurrentTrack->evEvent1->iQueued ) ) { // dodanie do kolejki - Global::AddToQuery( pCurrentTrack->evEvent1, Owner ); + simulation::Events.AddToQuery( pCurrentTrack->evEvent1, Owner ); } } } @@ -127,7 +126,7 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) -1)) // McZapkie-280503: wyzwalanie eventall dla wszystkich pojazdow if (bPrimary && pCurrentTrack->evEventall1 && (!pCurrentTrack->evEventall1->iQueued)) - Global::AddToQuery(pCurrentTrack->evEventall1, Owner); // dodanie do kolejki + simulation::Events.AddToQuery(pCurrentTrack->evEventall1, Owner); // dodanie do kolejki // Owner->RaAxleEvent(pCurrentTrack->Eventall1); //Ra: dynamic zdecyduje, czy dodać // do kolejki } @@ -142,7 +141,7 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) if( ( bPrimary ) && ( pCurrentTrack->evEvent2 ) && ( !pCurrentTrack->evEvent2->iQueued ) ) { - Global::AddToQuery( pCurrentTrack->evEvent2, Owner ); + simulation::Events.AddToQuery( pCurrentTrack->evEvent2, Owner ); } } } @@ -154,7 +153,7 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) if( ( bPrimary ) && ( pCurrentTrack->evEventall2 ) && ( !pCurrentTrack->evEventall2->iQueued ) ) { - Global::AddToQuery( pCurrentTrack->evEventall2, Owner ); + simulation::Events.AddToQuery( pCurrentTrack->evEventall2, Owner ); } } // Owner->RaAxleEvent(pCurrentTrack->Eventall2); //Ra: dynamic zdecyduje, czy dodać @@ -167,13 +166,13 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) // tylko dla jednego członu if( pCurrentTrack->evEvent0 ) if( !pCurrentTrack->evEvent0->iQueued ) - Global::AddToQuery( pCurrentTrack->evEvent0, Owner ); + simulation::Events.AddToQuery( pCurrentTrack->evEvent0, Owner ); } // Owner->RaAxleEvent(pCurrentTrack->Event0); //Ra: dynamic zdecyduje, czy dodać do // kolejki if (pCurrentTrack->evEventall0) if (!pCurrentTrack->evEventall0->iQueued) - Global::AddToQuery(pCurrentTrack->evEventall0, Owner); + simulation::Events.AddToQuery(pCurrentTrack->evEventall0, Owner); // Owner->RaAxleEvent(pCurrentTrack->Eventall0); //Ra: dynamic zdecyduje, czy dodać // do kolejki } @@ -275,14 +274,14 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) // if (Owner->MoverParameters->CabNo!=0) { if (pCurrentTrack->evEvent1 && pCurrentTrack->evEvent1->fDelay <= -1.0f) - Global::AddToQuery(pCurrentTrack->evEvent1, Owner); + simulation::Events.AddToQuery(pCurrentTrack->evEvent1, Owner); if (pCurrentTrack->evEvent2 && pCurrentTrack->evEvent2->fDelay <= -1.0f) - Global::AddToQuery(pCurrentTrack->evEvent2, Owner); + simulation::Events.AddToQuery(pCurrentTrack->evEvent2, Owner); } if (pCurrentTrack->evEventall1 && pCurrentTrack->evEventall1->fDelay <= -1.0f) - Global::AddToQuery(pCurrentTrack->evEventall1, Owner); + simulation::Events.AddToQuery(pCurrentTrack->evEventall1, Owner); if (pCurrentTrack->evEventall2 && pCurrentTrack->evEventall2->fDelay <= -1.0f) - Global::AddToQuery(pCurrentTrack->evEventall2, Owner); + simulation::Events.AddToQuery(pCurrentTrack->evEventall2, Owner); } fCurrentDistance = s; // fDistance=0; diff --git a/World.cpp b/World.cpp index 20617628..6e54c434 100644 --- a/World.cpp +++ b/World.cpp @@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/. #include "World.h" #include "Globals.h" +#include "simulation.h" #include "Logs.h" #include "MdlMngr.h" #include "renderer.h" @@ -221,6 +222,13 @@ void TWorld::TrainDelete(TDynamicObject *d) if (Train) if (Train->Dynamic() != d) return; // nie tego usuwać +#ifdef EU07_SCENERY_EDITOR + if( ( Train->DynamicObject ) + && ( Train->DynamicObject->Mechanik ) ) { + // likwidacja kabiny wymaga przejęcia przez AI + Train->DynamicObject->Mechanik->TakeControl( true ); + } +#endif delete Train; // i nie ma czym sterować Train = NULL; Controlled = NULL; // tego też już nie ma @@ -248,13 +256,7 @@ bool TWorld::Init( GLFWwindow *Window ) { GfxRenderer.Render(); WriteLog( "World setup..." ); - if( true == Ground.Init( Global::SceneryFile ) ) { - WriteLog( "...world setup done" ); - } - else { - ErrorLog( "...world setup failed" ); - return false; - } + if( false == simulation::State.deserialize( Global::SceneryFile ) ) { return false; } simulation::Time.init(); @@ -264,13 +266,13 @@ bool TWorld::Init( GLFWwindow *Window ) { UILayer.set_progress( "Preparing train / Przygotowanie kabiny" ); WriteLog( "Player train init: " + Global::asHumanCtrlVehicle ); - TGroundNode *nPlayerTrain = NULL; - if (Global::asHumanCtrlVehicle != "ghostview") - nPlayerTrain = Ground.DynamicFind(Global::asHumanCtrlVehicle); // szukanie w tych z obsadą + TDynamicObject *nPlayerTrain; + if( Global::asHumanCtrlVehicle != "ghostview" ) + nPlayerTrain = simulation::Vehicles.find( Global::asHumanCtrlVehicle ); if (nPlayerTrain) { Train = new TTrain(); - if (Train->Init(nPlayerTrain->DynamicObject)) + if( Train->Init( nPlayerTrain ) ) { Controlled = Train->Dynamic(); mvControlled = Controlled->ControlledFind()->MoverParameters; @@ -301,20 +303,22 @@ bool TWorld::Init( GLFWwindow *Window ) { Controlled = NULL; mvControlled = NULL; Camera.Type = tp_Free; + DebugCamera = Camera; + Global::DebugCameraPosition = DebugCamera.Pos; } // if (!Global::bMultiplayer) //na razie włączone { // eventy aktywowane z klawiatury tylko dla jednego użytkownika - KeyEvents[0] = Ground.FindEvent("keyctrl00"); - KeyEvents[1] = Ground.FindEvent("keyctrl01"); - KeyEvents[2] = Ground.FindEvent("keyctrl02"); - KeyEvents[3] = Ground.FindEvent("keyctrl03"); - KeyEvents[4] = Ground.FindEvent("keyctrl04"); - KeyEvents[5] = Ground.FindEvent("keyctrl05"); - KeyEvents[6] = Ground.FindEvent("keyctrl06"); - KeyEvents[7] = Ground.FindEvent("keyctrl07"); - KeyEvents[8] = Ground.FindEvent("keyctrl08"); - KeyEvents[9] = Ground.FindEvent("keyctrl09"); + KeyEvents[ 0 ] = simulation::Events.FindEvent( "keyctrl00" ); + KeyEvents[ 1 ] = simulation::Events.FindEvent( "keyctrl01" ); + KeyEvents[ 2 ] = simulation::Events.FindEvent( "keyctrl02" ); + KeyEvents[ 3 ] = simulation::Events.FindEvent( "keyctrl03" ); + KeyEvents[ 4 ] = simulation::Events.FindEvent( "keyctrl04" ); + KeyEvents[ 5 ] = simulation::Events.FindEvent( "keyctrl05" ); + KeyEvents[ 6 ] = simulation::Events.FindEvent( "keyctrl06" ); + KeyEvents[ 7 ] = simulation::Events.FindEvent( "keyctrl07" ); + KeyEvents[ 8 ] = simulation::Events.FindEvent( "keyctrl08" ); + KeyEvents[ 9 ] = simulation::Events.FindEvent( "keyctrl09" ); } WriteLog( "Load time: " + @@ -398,11 +402,12 @@ void TWorld::OnKeyDown(int cKey) if( ( cKey >= GLFW_KEY_0 ) && ( cKey <= GLFW_KEY_9 ) ) // klawisze cyfrowe { int i = cKey - GLFW_KEY_0; // numer klawisza - if (Global::shiftState) - { // z [Shift] uruchomienie eventu - if (!Global::iPause) // podczas pauzy klawisze nie działają - if (KeyEvents[i]) - Ground.AddToQuery(KeyEvents[i], NULL); + if (Global::shiftState) { + // z [Shift] uruchomienie eventu + if( ( false == Global::iPause ) // podczas pauzy klawisze nie działają + && ( KeyEvents[ i ] != nullptr ) ) { + simulation::Events.AddToQuery( KeyEvents[ i ], NULL ); + } } else // zapamiętywanie kamery może działać podczas pauzy if (FreeFlyModeFlag) // w trybie latania można przeskakiwać do ustawionych kamer @@ -429,7 +434,6 @@ void TWorld::OnKeyDown(int cKey) } else // również przeskakiwanie { // Ra: to z tą kamerą (Camera.Pos i Global::pCameraPosition) jest trochę bez sensu - Ground.Silence( Global::pCameraPosition ); // wyciszenie wszystkiego z poprzedniej pozycji Global::SetCameraPosition( Global::FreeCameraInit[i] ); // nowa pozycja dla generowania obiektów Camera.Init(Global::FreeCameraInit[i], Global::FreeCameraInitAngle[i]); // przestawienie @@ -506,7 +510,8 @@ void TWorld::OnKeyDown(int cKey) break; } - TDynamicObject *tmp = Ground.DynamicNearest( Camera.Pos, 50, true ); //łapiemy z obsadą + TDynamicObject *tmp = std::get( simulation::Region->find_vehicle( Global::pCameraPosition, 50, true, false ) ); + if( ( tmp != nullptr ) && ( tmp != Controlled ) ) { @@ -651,8 +656,8 @@ void TWorld::OnKeyDown(int cKey) } else if( ( cKey == GLFW_KEY_PAUSE ) && ( Global::ctrlState ) && ( Global::shiftState ) ) { //[Ctrl]+[Break] hamowanie wszystkich pojazdów w okolicy // added shift to prevent odd issue with glfw producing pause presses on its own - if (Controlled->MoverParameters->Radio) - Ground.RadioStop(Camera.Pos); + if( Controlled->MoverParameters->Radio ) + simulation::Region->RadioStop( Camera.Pos ); } else if (!Global::iPause) //||(cKey==VK_F4)) //podczas pauzy sterownaie nie działa, F4 tak if (Train) @@ -685,82 +690,81 @@ void TWorld::OnKeyDown(int cKey) */ if (cKey == Global::Keys[k_Heating]) // Ra: klawisz nie jest najszczęśliwszy { // zmiana próżny/ładowny; Ra: zabrane z kabiny - TDynamicObject *temp = Global::DynamicNearest(); - if (temp) + auto *vehicle { std::get( simulation::Region->find_vehicle( Global::pCameraPosition, 20, false, false ) ) }; + if (vehicle) { - if (Global::shiftState ? temp->MoverParameters->IncBrakeMult() : - temp->MoverParameters->DecBrakeMult()) + if (Global::shiftState ? vehicle->MoverParameters->IncBrakeMult() : + vehicle->MoverParameters->DecBrakeMult()) if (Train) { // dźwięk oczywiście jest w kabinie - Train->dsbSwitch->gain(1.0f).play(); + Train->play_sound( Train->dsbSwitch ); } } } else if (cKey == Global::Keys[k_EndSign]) { // Ra 2014-07: zabrane z kabiny - TDynamicObject *tmp = Global::CouplerNearest(); // domyślnie wyszukuje do 20m - if (tmp) + auto *vehicle { std::get( simulation::Region->find_vehicle( Global::pCameraPosition, 20, false, true ) ) }; + if (vehicle) { - int CouplNr = (LengthSquared3(tmp->HeadPosition() - Camera.Pos) > - LengthSquared3(tmp->RearPosition() - Camera.Pos) ? + int CouplNr = (LengthSquared3(vehicle->HeadPosition() - Camera.Pos) > + LengthSquared3(vehicle->RearPosition() - Camera.Pos) ? 1 : -1) * - tmp->DirectionGet(); + vehicle->DirectionGet(); if (CouplNr < 0) CouplNr = 0; // z [-1,1] zrobić [0,1] - int mask, set = 0; // Ra: [Shift]+[Ctrl]+[T] odpala mi jakąś idiotyczną zmianę - // tapety pulpitu :/ + int mask, set = 0; // Ra: [Shift]+[Ctrl]+[T] odpala mi jakąś idiotyczną zmianę tapety pulpitu :/ if (Global::shiftState) // z [Shift] zapalanie - set = mask = 64; // bez [Ctrl] założyć tabliczki + set = mask = TMoverParameters::light::rearendsignals; // bez [Ctrl] założyć tabliczki else if (Global::ctrlState) - set = mask = 2 + 32; // z [Ctrl] zapalić światła czerwone + set = mask = ( TMoverParameters::light::redmarker_left | TMoverParameters::light::redmarker_right ); // z [Ctrl] zapalić światła czerwone else mask = 2 + 32 + 64; // wyłączanie ściąga wszystko - if (((tmp->iLights[CouplNr]) & mask) != set) + if (((vehicle->iLights[CouplNr]) & mask) != set) { - tmp->iLights[CouplNr] = (tmp->iLights[CouplNr] & ~mask) | set; + vehicle->iLights[CouplNr] = (vehicle->iLights[CouplNr] & ~mask) | set; if (Train) { // Ra: ten dźwięk z kabiny to przegięcie, ale na razie zostawiam - Train->dsbSwitch->gain(1.0f).play(); + Train->play_sound( Train->dsbSwitch ); } } } } else if (cKey == Global::Keys[k_IncLocalBrakeLevel]) { // zahamowanie dowolnego pojazdu - TDynamicObject *temp = Global::DynamicNearest(); - if (temp) + auto *vehicle { std::get( simulation::Region->find_vehicle( Global::pCameraPosition, 20, false, false ) ) }; + if (vehicle) { if (Global::ctrlState) - if ((temp->MoverParameters->LocalBrake == ManualBrake) || - (temp->MoverParameters->MBrake == true)) - temp->MoverParameters->IncManualBrakeLevel(1); + if ((vehicle->MoverParameters->LocalBrake == ManualBrake) || + (vehicle->MoverParameters->MBrake == true)) + vehicle->MoverParameters->IncManualBrakeLevel(1); else ; - else if (temp->MoverParameters->LocalBrake != ManualBrake) - if (temp->MoverParameters->IncLocalBrakeLevelFAST()) + else if (vehicle->MoverParameters->LocalBrake != ManualBrake) + if (vehicle->MoverParameters->IncLocalBrakeLevelFAST()) if (Train) { // dźwięk oczywiście jest w kabinie - Train->dsbPneumaticRelay->gain(Global::soundgainmode == Global::compat ? 0.9f : 0.5f).play(); + Train->play_sound( Train->dsbPneumaticRelay ); } } } else if (cKey == Global::Keys[k_DecLocalBrakeLevel]) { // odhamowanie dowolnego pojazdu - TDynamicObject *temp = Global::DynamicNearest(); - if (temp) + auto *vehicle { std::get( simulation::Region->find_vehicle( Global::pCameraPosition, 20, false, false ) ) }; + if (vehicle) { if (Global::ctrlState) - if ((temp->MoverParameters->LocalBrake == ManualBrake) || - (temp->MoverParameters->MBrake == true)) - temp->MoverParameters->DecManualBrakeLevel(1); + if ((vehicle->MoverParameters->LocalBrake == ManualBrake) || + (vehicle->MoverParameters->MBrake == true)) + vehicle->MoverParameters->DecManualBrakeLevel(1); else ; - else if (temp->MoverParameters->LocalBrake != ManualBrake) - if (temp->MoverParameters->DecLocalBrakeLevelFAST()) + else if (vehicle->MoverParameters->LocalBrake != ManualBrake) + if (vehicle->MoverParameters->DecLocalBrakeLevelFAST()) if (Train) { // dźwięk oczywiście jest w kabinie - Train->dsbPneumaticRelay->gain(Global::soundgainmode == Global::compat ? 0.9f : 0.5f).play(); + Train->play_sound( Train->dsbPneumaticRelay ); } } } @@ -786,6 +790,8 @@ void TWorld::InOutKey( bool const Near ) Train->Dynamic()->bDisplayCab = false; DistantView( Near ); } + DebugCamera = Camera; + Global::DebugCameraPosition = DebugCamera.Pos; } else { // jazda w kabinie @@ -857,8 +863,6 @@ void TWorld::FollowView(bool wycisz) { if (Controlled) // jest pojazd do prowadzenia? { - Ground.Silence( Camera.Pos ); // wyciszenie dźwięków z poprzedniej pozycji - if (FreeFlyModeFlag) { // jeżeli poza kabiną, przestawiamy w jej okolicę - OK if( Train ) { @@ -876,7 +880,7 @@ void TWorld::FollowView(bool wycisz) { if( wycisz ) { // wyciszenie dźwięków z poprzedniej pozycji // trzymanie prawego w kabinie daje marny efekt - Ground.Silence( Camera.Pos ); + // TODO: re-implement, old one kinda didn't really work } Camera.Pos = Train->pMechPosition; Camera.Roll = std::atan(Train->pMechShake.x * Train->fMechRoll); // hustanie kamery na boki @@ -900,22 +904,10 @@ void TWorld::FollowView(bool wycisz) { DistantView(); }; -bool TWorld::Update() -{ -#ifdef USE_SCENERY_MOVING - vector3 tmpvector = Global::GetCameraPosition(); - tmpvector = vector3(-int(tmpvector.x) + int(tmpvector.x) % 10000, - -int(tmpvector.y) + int(tmpvector.y) % 10000, - -int(tmpvector.z) + int(tmpvector.z) % 10000); - if (tmpvector.x || tmpvector.y || tmpvector.z) - { - WriteLog("Moving scenery"); - Ground.MoveGroundNode(tmpvector); - WriteLog("Scenery moved"); - }; -#endif +bool TWorld::Update() { Timer::UpdateTimers(Global::iPause != 0); + Timer::subsystem.sim_total.start(); if( (Global::iPause == false) || (m_init == false) ) { @@ -940,7 +932,7 @@ bool TWorld::Update() /* fTimeBuffer += dt; //[s] dodanie czasu od poprzedniej ramki */ - m_primaryupdateaccumulator += dt; +// m_primaryupdateaccumulator += dt; // unused for the time being m_secondaryupdateaccumulator += dt; /* if (fTimeBuffer >= fMaxDt) // jest co najmniej jeden krok; normalnie 0.01s @@ -986,33 +978,29 @@ bool TWorld::Update() dt = dt / iterations; // Ra: fizykę lepiej by było przeliczać ze stałym krokiem */ } + auto const stepdeltatime { dt / updatecount }; // NOTE: updates are limited to 20, but dt is distributed over potentially many more iterations // this means at count > 20 simulation and render are going to desync. is that right? // NOTE: experimentally changing this to prevent the desync. // TODO: test what happens if we hit more than 20 * 0.01 sec slices, i.e. less than 5 fps + Timer::subsystem.sim_dynamics.start(); if( true == Global::FullPhysics ) { - // default calculation mode, each step calculated separately - for( int updateidx = 0; updateidx < updatecount; ++updateidx ) { - Ground.Update( dt / updatecount, 1 ); + // mixed calculation mode, steps calculated in ~0.05s chunks + while( updatecount >= 5 ) { + simulation::State.update( stepdeltatime, 5 ); + updatecount -= 5; + } + if( updatecount ) { + simulation::State.update( stepdeltatime, updatecount ); } } else { - // slightly simplified calculation mode; can lead to errors - Ground.Update( dt / updatecount, updatecount ); + // simplified calculation mode; faster but can lead to errors + simulation::State.update( stepdeltatime, updatecount ); } + Timer::subsystem.sim_dynamics.stop(); - // yB dodał przyspieszacz fizyki - if( (true == DebugModeFlag) - && (true == Global::bActive) // nie przyspieszać, gdy jedzie w tle :) - && ( glfwGetKey( window, GLFW_KEY_PAUSE ) == GLFW_PRESS ) ) { - - Ground.Update( dt, 1 ); - Ground.Update( dt, 1 ); - Ground.Update( dt, 1 ); - Ground.Update( dt, 1 ); // 5 razy - } // secondary fixed step simulation time routines - while( m_secondaryupdateaccumulator >= m_secondaryupdaterate ) { Global::tranTexts.Update(); // obiekt obsługujący stenogramy dźwięków na ekranie @@ -1020,7 +1008,7 @@ bool TWorld::Update() // awaria PoKeys mogła włączyć pauzę - przekazać informację if( Global::iMultiplayer ) // dajemy znać do serwera o wykonaniu if( iPause != Global::iPause ) { // przesłanie informacji o pauzie do programu nadzorującego - Ground.WyslijParam( 5, 3 ); // ramka 5 z czasem i stanem zapauzowania + multiplayer::WyslijParam( 5, 3 ); // ramka 5 z czasem i stanem zapauzowania iPause = Global::iPause; } @@ -1050,10 +1038,9 @@ bool TWorld::Update() TSubModel::iInstance = 0; } - Ground.CheckQuery(); - - Ground.Update_Hidden(); - Ground.Update_Lights(); + simulation::Events.update(); + simulation::Region->update_events(); + simulation::Lights.update(); // render time routines follow: @@ -1094,7 +1081,9 @@ bool TWorld::Update() sound_man->update(Global::iPause ? 0.0f : dt); } + Timer::subsystem.sim_total.stop(); + simulation::Region->update_sounds(); GfxRenderer.Update( dt ); ResourceSweep(); @@ -1114,10 +1103,10 @@ TWorld::Update_Camera( double const Deltatime ) { Camera.LookAt = Controlled->GetPosition(); } else { - TDynamicObject *d = - Ground.DynamicNearest( Camera.Pos, 300 ); // szukaj w promieniu 300m + TDynamicObject *d = std::get( simulation::Region->find_vehicle( Global::pCameraPosition, 300, false, false ) ); if( !d ) - d = Ground.DynamicNearest( Camera.Pos, 1000 ); // dalej szukanie, jesli bliżej nie ma + d = std::get( simulation::Region->find_vehicle( Global::pCameraPosition, 1000, false, false ) ); // dalej szukanie, jesli bliżej nie ma + if( d && pDynamicNearest ) { // jeśli jakiś jest znaleziony wcześniej if( 100.0 * LengthSquared3( d->GetPosition() - Camera.Pos ) > LengthSquared3( pDynamicNearest->GetPosition() - Camera.Pos ) ) { @@ -1235,7 +1224,7 @@ void TWorld::Update_UI() { UITable->text_lines.clear(); - std::string uitextline1, uitextline2, uitextline3, uitextline4; + std::string uitextline1, uitextline2, uitextline3, uitextline4; UILayer.set_tooltip( "" ); if( ( Train != nullptr ) && ( false == FreeFlyModeFlag ) ) { @@ -1251,7 +1240,10 @@ TWorld::Update_UI() { } if( ( true == Global::ControlPicking ) && ( true == FreeFlyModeFlag ) && ( true == DebugModeFlag ) ) { auto const scenerynode = GfxRenderer.Pick_Node(); - UILayer.set_tooltip( ( scenerynode ? scenerynode->asName : "" ) ); + UILayer.set_tooltip( + ( scenerynode ? + scenerynode->name() : + "" ) ); } switch( Global::iTextMode ) { @@ -1268,7 +1260,8 @@ TWorld::Update_UI() { uitextline1 += " (paused)"; } - if( Controlled && ( Controlled->Mechanik != nullptr ) ) { + if( ( Controlled != nullptr ) + && ( Controlled->Mechanik != nullptr ) ) { auto const &mover = Controlled->MoverParameters; auto const &driver = Controlled->Mechanik; @@ -1290,6 +1283,11 @@ TWorld::Update_UI() { uitextline3 += " Pressure: " + to_string( mover->BrakePress * 100.0, 2 ) + " kPa" + " (train pipe: " + to_string( mover->PipePress * 100.0, 2 ) + " kPa)"; + + auto const trackblockdistance{ std::abs( Controlled->Mechanik->TrackBlock() ) }; + if( trackblockdistance <= 75.0 ) { + uitextline4 = " Another vehicle ahead (distance: " + to_string( trackblockdistance, 1 ) + " m)"; + } } } @@ -1298,17 +1296,17 @@ TWorld::Update_UI() { case( GLFW_KEY_F2 ) : { // timetable - TDynamicObject *tmp = + auto *vehicle { ( FreeFlyModeFlag ? - Ground.DynamicNearest( Camera.Pos ) : - Controlled ); // w trybie latania lokalizujemy wg mapy + std::get( simulation::Region->find_vehicle( Camera.Pos, 20, false, false ) ) : + Controlled ) }; // w trybie latania lokalizujemy wg mapy - if( tmp == nullptr ) { break; } + if( vehicle == nullptr ) { break; } // if the nearest located vehicle doesn't have a direct driver, try to query its owner auto const owner = ( - ( ( tmp->Mechanik != nullptr ) && ( tmp->Mechanik->Primary() ) ) ? - tmp->Mechanik : - tmp->ctOwner ); + ( ( vehicle->Mechanik != nullptr ) && ( vehicle->Mechanik->Primary() ) ) ? + vehicle->Mechanik : + vehicle->ctOwner ); if( owner == nullptr ){ break; } auto const table = owner->Timetable(); @@ -1379,49 +1377,48 @@ TWorld::Update_UI() { case( GLFW_KEY_F3 ) : { - TDynamicObject *tmp = + auto *vehicle{ ( FreeFlyModeFlag ? - Ground.DynamicNearest( Camera.Pos ) : - Controlled ); // w trybie latania lokalizujemy wg mapy + std::get( simulation::Region->find_vehicle( Camera.Pos, 20, false, false ) ) : + Controlled ) }; // w trybie latania lokalizujemy wg mapy - if( tmp != nullptr ) { - // + if( vehicle != nullptr ) { // jeśli domyślny ekran po pierwszym naciśnięciu - uitextline1 = "Vehicle name: " + tmp->MoverParameters->Name; + uitextline1 = "Vehicle name: " + vehicle->MoverParameters->Name; - if( ( tmp->Mechanik == nullptr ) && ( tmp->ctOwner ) ) { + if( ( vehicle->Mechanik == nullptr ) && ( vehicle->ctOwner ) ) { // for cars other than leading unit indicate the leader - uitextline1 += ", owned by " + tmp->ctOwner->OwnerName(); + uitextline1 += ", owned by " + vehicle->ctOwner->OwnerName(); } - uitextline1 += "; Status: " + tmp->MoverParameters->EngineDescription( 0 ); + uitextline1 += "; Status: " + vehicle->MoverParameters->EngineDescription( 0 ); // informacja o sprzęgach uitextline1 += "; C0:" + - ( tmp->PrevConnected ? - tmp->PrevConnected->GetName() + ":" + to_string( tmp->MoverParameters->Couplers[ 0 ].CouplingFlag ) + ( - tmp->MoverParameters->Couplers[ 0 ].CouplingFlag == 0 ? - " (" + to_string( tmp->MoverParameters->Couplers[ 0 ].CoupleDist, 1 ) + " m)" : + ( vehicle->PrevConnected ? + vehicle->PrevConnected->name() + ":" + to_string( vehicle->MoverParameters->Couplers[ 0 ].CouplingFlag ) + ( + vehicle->MoverParameters->Couplers[ 0 ].CouplingFlag == 0 ? + " (" + to_string( vehicle->MoverParameters->Couplers[ 0 ].CoupleDist, 1 ) + " m)" : "" ) : "none" ); uitextline1 += " C1:" + - ( tmp->NextConnected ? - tmp->NextConnected->GetName() + ":" + to_string( tmp->MoverParameters->Couplers[ 1 ].CouplingFlag ) + ( - tmp->MoverParameters->Couplers[ 1 ].CouplingFlag == 0 ? - " (" + to_string( tmp->MoverParameters->Couplers[ 1 ].CoupleDist, 1 ) + " m)" : + ( vehicle->NextConnected ? + vehicle->NextConnected->name() + ":" + to_string( vehicle->MoverParameters->Couplers[ 1 ].CouplingFlag ) + ( + vehicle->MoverParameters->Couplers[ 1 ].CouplingFlag == 0 ? + " (" + to_string( vehicle->MoverParameters->Couplers[ 1 ].CoupleDist, 1 ) + " m)" : "" ) : "none" ); // equipment flags - uitextline2 = ( tmp->MoverParameters->Battery ? "B" : "." ); - uitextline2 += ( tmp->MoverParameters->Mains ? "M" : "." ); - uitextline2 += ( tmp->MoverParameters->PantRearUp ? ( tmp->MoverParameters->PantRearVolt > 0.0 ? "O" : "o" ) : "." );; - uitextline2 += ( tmp->MoverParameters->PantFrontUp ? ( tmp->MoverParameters->PantFrontVolt > 0.0 ? "P" : "p" ) : "." );; - uitextline2 += ( tmp->MoverParameters->PantPressLockActive ? "!" : ( tmp->MoverParameters->PantPressSwitchActive ? "*" : "." ) ); - uitextline2 += ( false == tmp->MoverParameters->ConverterAllowLocal ? "-" : ( tmp->MoverParameters->ConverterAllow ? ( tmp->MoverParameters->ConverterFlag ? "X" : "x" ) : "." ) ); - uitextline2 += ( tmp->MoverParameters->ConvOvldFlag ? "!" : "." ); - uitextline2 += ( false == tmp->MoverParameters->CompressorAllowLocal ? "-" : ( tmp->MoverParameters->CompressorAllow ? ( tmp->MoverParameters->CompressorFlag ? "C" : "c" ) : "." ) ); - uitextline2 += ( tmp->MoverParameters->CompressorGovernorLock ? "!" : "." ); + uitextline2 = ( vehicle->MoverParameters->Battery ? "B" : "." ); + uitextline2 += ( vehicle->MoverParameters->Mains ? "M" : "." ); + uitextline2 += ( vehicle->MoverParameters->PantRearUp ? ( vehicle->MoverParameters->PantRearVolt > 0.0 ? "O" : "o" ) : "." );; + uitextline2 += ( vehicle->MoverParameters->PantFrontUp ? ( vehicle->MoverParameters->PantFrontVolt > 0.0 ? "P" : "p" ) : "." );; + uitextline2 += ( vehicle->MoverParameters->PantPressLockActive ? "!" : ( vehicle->MoverParameters->PantPressSwitchActive ? "*" : "." ) ); + uitextline2 += ( false == vehicle->MoverParameters->ConverterAllowLocal ? "-" : ( vehicle->MoverParameters->ConverterAllow ? ( vehicle->MoverParameters->ConverterFlag ? "X" : "x" ) : "." ) ); + uitextline2 += ( vehicle->MoverParameters->ConvOvldFlag ? "!" : "." ); + uitextline2 += ( false == vehicle->MoverParameters->CompressorAllowLocal ? "-" : ( vehicle->MoverParameters->CompressorAllow ? ( vehicle->MoverParameters->CompressorFlag ? "C" : "c" ) : "." ) ); + uitextline2 += ( vehicle->MoverParameters->CompressorGovernorLock ? "!" : "." ); /* uitextline2 += " AnlgB: " + to_string( tmp->MoverParameters->AnPos, 1 ) @@ -1429,72 +1426,72 @@ TWorld::Update_UI() { + to_string( tmp->MoverParameters->LocalBrakePosA, 1 ) */ uitextline2 += " Bdelay: "; - if( ( tmp->MoverParameters->BrakeDelayFlag & bdelay_G ) == bdelay_G ) + if( ( vehicle->MoverParameters->BrakeDelayFlag & bdelay_G ) == bdelay_G ) uitextline2 += "G"; - if( ( tmp->MoverParameters->BrakeDelayFlag & bdelay_P ) == bdelay_P ) + if( ( vehicle->MoverParameters->BrakeDelayFlag & bdelay_P ) == bdelay_P ) uitextline2 += "P"; - if( ( tmp->MoverParameters->BrakeDelayFlag & bdelay_R ) == bdelay_R ) + if( ( vehicle->MoverParameters->BrakeDelayFlag & bdelay_R ) == bdelay_R ) uitextline2 += "R"; - if( ( tmp->MoverParameters->BrakeDelayFlag & bdelay_M ) == bdelay_M ) + if( ( vehicle->MoverParameters->BrakeDelayFlag & bdelay_M ) == bdelay_M ) uitextline2 += "+Mg"; - uitextline2 += ", Load: " + to_string( tmp->MoverParameters->LoadFlag, 0 ); + uitextline2 += ", Load: " + to_string( vehicle->MoverParameters->LoadFlag, 0 ); uitextline2 += "; Pant: " - + to_string( tmp->MoverParameters->PantPress, 2 ) - + ( tmp->MoverParameters->bPantKurek3 ? "-ZG" : "|ZG" ); + + to_string( vehicle->MoverParameters->PantPress, 2 ) + + ( vehicle->MoverParameters->bPantKurek3 ? "-ZG" : "|ZG" ); uitextline2 += - "; Ft: " + to_string( tmp->MoverParameters->Ft * 0.001f * tmp->MoverParameters->ActiveCab, 1 ) - + ", Fb: " + to_string( tmp->MoverParameters->Fb * 0.001f, 1 ) - + ", Fr: " + to_string( tmp->MoverParameters->RunningTrack.friction, 2 ) - + ( tmp->MoverParameters->SlippingWheels ? " (!)" : "" ); + "; Ft: " + to_string( vehicle->MoverParameters->Ft * 0.001f * vehicle->MoverParameters->ActiveCab, 1 ) + + ", Fb: " + to_string( vehicle->MoverParameters->Fb * 0.001f, 1 ) + + ", Fr: " + to_string( vehicle->MoverParameters->Adhesive( vehicle->MoverParameters->RunningTrack.friction ), 2 ) + + ( vehicle->MoverParameters->SlippingWheels ? " (!)" : "" ); - if( tmp->Mechanik ) { - uitextline2 += "; Ag: " + to_string( tmp->Mechanik->fAccGravity, 2 ); + if( vehicle->Mechanik ) { + uitextline2 += "; Ag: " + to_string( vehicle->Mechanik->fAccGravity, 2 ); } uitextline2 += "; TC:" - + to_string( tmp->MoverParameters->TotalCurrent, 0 ); + + to_string( vehicle->MoverParameters->TotalCurrent, 0 ); auto const frontcouplerhighvoltage = - to_string( tmp->MoverParameters->Couplers[ TMoverParameters::side::front ].power_high.voltage, 0 ) + to_string( vehicle->MoverParameters->Couplers[ TMoverParameters::side::front ].power_high.voltage, 0 ) + "@" - + to_string( tmp->MoverParameters->Couplers[ TMoverParameters::side::front ].power_high.current, 0 ); + + to_string( vehicle->MoverParameters->Couplers[ TMoverParameters::side::front ].power_high.current, 0 ); auto const rearcouplerhighvoltage = - to_string( tmp->MoverParameters->Couplers[ TMoverParameters::side::rear ].power_high.voltage, 0 ) + to_string( vehicle->MoverParameters->Couplers[ TMoverParameters::side::rear ].power_high.voltage, 0 ) + "@" - + to_string( tmp->MoverParameters->Couplers[ TMoverParameters::side::rear ].power_high.current, 0 ); + + to_string( vehicle->MoverParameters->Couplers[ TMoverParameters::side::rear ].power_high.current, 0 ); uitextline2 += ", HV: "; - if( tmp->MoverParameters->Couplers[ TMoverParameters::side::front ].power_high.local == false ) { + if( vehicle->MoverParameters->Couplers[ TMoverParameters::side::front ].power_high.local == false ) { uitextline2 += "(" + frontcouplerhighvoltage + ")-" - + ":F" + ( tmp->DirectionGet() ? "<<" : ">>" ) + "R:" + + ":F" + ( vehicle->DirectionGet() ? "<<" : ">>" ) + "R:" + "-(" + rearcouplerhighvoltage + ")"; } else { uitextline2 += frontcouplerhighvoltage - + ":F" + ( tmp->DirectionGet() ? "<<" : ">>" ) + "R:" + + ":F" + ( vehicle->DirectionGet() ? "<<" : ">>" ) + "R:" + rearcouplerhighvoltage; } uitextline3 += - "TrB: " + to_string( tmp->MoverParameters->BrakePress, 2 ) - + ", " + to_hex_str( tmp->MoverParameters->Hamulec->GetBrakeStatus(), 2 ); + "TrB: " + to_string( vehicle->MoverParameters->BrakePress, 2 ) + + ", " + to_hex_str( vehicle->MoverParameters->Hamulec->GetBrakeStatus(), 2 ); uitextline3 += - "; LcB: " + to_string( tmp->MoverParameters->LocBrakePress, 2 ) - + "; pipes: " + to_string( tmp->MoverParameters->PipePress, 2 ) - + "/" + to_string( tmp->MoverParameters->ScndPipePress, 2 ) - + "/" + to_string( tmp->MoverParameters->EqvtPipePress, 2 ) - + ", MT: " + to_string( tmp->MoverParameters->CompressedVolume, 3 ) - + ", BT: " + to_string( tmp->MoverParameters->Volume, 3 ) - + ", CtlP: " + to_string( tmp->MoverParameters->CntrlPipePress, 3 ) - + ", CtlT: " + to_string( tmp->MoverParameters->Hamulec->GetCRP(), 3 ); + "; LcB: " + to_string( vehicle->MoverParameters->LocBrakePress, 2 ) + + "; pipes: " + to_string( vehicle->MoverParameters->PipePress, 2 ) + + "/" + to_string( vehicle->MoverParameters->ScndPipePress, 2 ) + + "/" + to_string( vehicle->MoverParameters->EqvtPipePress, 2 ) + + ", MT: " + to_string( vehicle->MoverParameters->CompressedVolume, 3 ) + + ", BT: " + to_string( vehicle->MoverParameters->Volume, 3 ) + + ", CtlP: " + to_string( vehicle->MoverParameters->CntrlPipePress, 3 ) + + ", CtlT: " + to_string( vehicle->MoverParameters->Hamulec->GetCRP(), 3 ); - if( tmp->MoverParameters->ManualBrakePos > 0 ) { + if( vehicle->MoverParameters->ManualBrakePos > 0 ) { uitextline3 += "; manual brake on"; } @@ -1508,51 +1505,51 @@ TWorld::Update_UI() { uitextline3 += ", local brake off"; } */ - if( tmp->Mechanik ) { + if( vehicle->Mechanik ) { // o ile jest ktoś w środku std::string flags = "cpapcplhhndoiefgvdpseil "; // flagi AI (definicja w Driver.h) for( int i = 0, j = 1; i < 23; ++i, j <<= 1 ) - if( false == ( tmp->Mechanik->DrivigFlags() & j ) ) // jak bit ustawiony + if( false == ( vehicle->Mechanik->DrivigFlags() & j ) ) // jak bit ustawiony flags[ i ] = '.';// std::toupper( flags[ i ] ); // ^= 0x20; // to zmiana na wielką literę uitextline4 = flags; uitextline4 += - "Driver: Vd=" + to_string( tmp->Mechanik->VelDesired, 0 ) - + " Ad=" + to_string( tmp->Mechanik->AccDesired, 2 ) - + " Ah=" + to_string( tmp->Mechanik->fAccThreshold, 2 ) - + "@" + to_string( tmp->Mechanik->fBrake_a0[0], 2 ) - + "+" + to_string( tmp->Mechanik->fBrake_a1[0], 2 ) - + " Bd=" + to_string( tmp->Mechanik->fBrakeDist, 0 ) - + " Pd=" + to_string( tmp->Mechanik->ActualProximityDist, 0 ) - + " Vn=" + to_string( tmp->Mechanik->VelNext, 0 ) - + " VSl=" + to_string( tmp->Mechanik->VelSignalLast, 0 ) - + " VLl=" + to_string( tmp->Mechanik->VelLimitLast, 0 ) - + " VRd=" + to_string( tmp->Mechanik->VelRoad, 0 ); + "Driver: Vd=" + to_string( vehicle->Mechanik->VelDesired, 0 ) + + " Ad=" + to_string( vehicle->Mechanik->AccDesired, 2 ) + + " Ah=" + to_string( vehicle->Mechanik->fAccThreshold, 2 ) + + "@" + to_string( vehicle->Mechanik->fBrake_a0[0], 2 ) + + "+" + to_string( vehicle->Mechanik->fBrake_a1[0], 2 ) + + " Bd=" + to_string( vehicle->Mechanik->fBrakeDist, 0 ) + + " Pd=" + to_string( vehicle->Mechanik->ActualProximityDist, 0 ) + + " Vn=" + to_string( vehicle->Mechanik->VelNext, 0 ) + + " VSl=" + to_string( vehicle->Mechanik->VelSignalLast, 0 ) + + " VLl=" + to_string( vehicle->Mechanik->VelLimitLast, 0 ) + + " VRd=" + to_string( vehicle->Mechanik->VelRoad, 0 ); - if( ( tmp->Mechanik->VelNext == 0.0 ) - && ( tmp->Mechanik->eSignNext ) ) { + if( ( vehicle->Mechanik->VelNext == 0.0 ) + && ( vehicle->Mechanik->eSignNext ) ) { // jeśli ma zapamiętany event semafora, nazwa eventu semafora uitextline4 += " (" - + Global::Bezogonkow( tmp->Mechanik->eSignNext->asName ) + + Global::Bezogonkow( vehicle->Mechanik->eSignNext->asName ) + ")"; } // biezaca komenda dla AI - uitextline4 += ", command: " + tmp->Mechanik->OrderCurrent(); + uitextline4 += ", command: " + vehicle->Mechanik->OrderCurrent(); } if( Global::iScreenMode[ Global::iTextMode - GLFW_KEY_F1 ] == 1 ) { // f2 screen, track scan mode - if( tmp->Mechanik == nullptr ) { + if( vehicle->Mechanik == nullptr ) { //żeby była tabelka, musi być AI break; } - std::size_t i = 0; std::size_t const speedtablesize = clamp( static_cast( tmp->Mechanik->TableSize() ) - 1, 0, 30 ); + std::size_t i = 0; std::size_t const speedtablesize = clamp( static_cast( vehicle->Mechanik->TableSize() ) - 1, 0, 30 ); do { - std::string scanline = tmp->Mechanik->TableText( i ); + std::string scanline = vehicle->Mechanik->TableText( i ); if( scanline.empty() ) { break; } UITable->text_lines.emplace_back( Global::Bezogonkow( scanline ), Global::UITextColor ); ++i; @@ -1591,22 +1588,18 @@ TWorld::Update_UI() { uitextline1 += " (slowmotion " + to_string( Global::iSlowMotion ) + ")"; } - - - // dump last opengl error, if any - GLenum glerror = ::glGetError(); - if( glerror != GL_NO_ERROR ) { - std::string glerrorstring( (char *)::gluErrorString( glerror ) ); - win1250_to_ascii( glerrorstring ); - Global::LastGLError = std::to_string( glerror ) + " (" + glerrorstring + ")"; - } + uitextline2 = + std::string( "Rendering mode: " ) + + "VBO" + + " "; if( false == Global::LastGLError.empty() ) { uitextline2 += "Last openGL error: " + Global::LastGLError; } // renderer stats - uitextline3 = GfxRenderer.Info(); + uitextline3 = GfxRenderer.info_times(); + uitextline4 = GfxRenderer.info_stats(); break; } @@ -1617,6 +1610,9 @@ TWorld::Update_UI() { if( Global::iMultiplayer ) { uitextline1 += " (multiplayer mode is active)"; } + uitextline3 = + "vehicles: " + to_string( Timer::subsystem.sim_dynamics.average(), 2 ) + " msec" + + " update total: " + to_string( Timer::subsystem.sim_total.average(), 2 ) + " msec"; break; } @@ -1642,64 +1638,64 @@ TWorld::Update_UI() { // ... unless we're in debug mode if( DebugModeFlag ) { - TDynamicObject *tmp = + auto *vehicle { ( FreeFlyModeFlag ? - Ground.DynamicNearest( Camera.Pos ) : - Controlled ); // w trybie latania lokalizujemy wg mapy - if( tmp == nullptr ) { + std::get( simulation::Region->find_vehicle( Camera.Pos, 20, false, false ) ) : + Controlled ) }; // w trybie latania lokalizujemy wg mapy + if( vehicle == nullptr ) { break; } - - uitextline1 = - "vel: " + to_string( tmp->GetVelocity(), 2 ) + " km/h" + ( tmp->MoverParameters->SlippingWheels ? " (!)" : "" ) - + "; dist: " + to_string( tmp->MoverParameters->DistCounter, 2 ) + " km" - + "; pos: (" - + to_string( tmp->GetPosition().x, 2 ) + ", " - + to_string( tmp->GetPosition().y, 2 ) + ", " - + to_string( tmp->GetPosition().z, 2 ) - + ")"; + uitextline1 = + "vel: " + to_string(vehicle->GetVelocity(), 2) + "/" + to_string(vehicle->MoverParameters->nrot* M_PI * vehicle->MoverParameters->WheelDiameter * 3.6, 2) + + " km/h" + (vehicle->MoverParameters->SlippingWheels ? " (!)" : " ") + + "; dist: " + to_string(vehicle->MoverParameters->DistCounter, 2) + " km" + + "; pos: (" + + to_string(vehicle->GetPosition().x, 2) + ", " + + to_string(vehicle->GetPosition().y, 2) + ", " + + to_string(vehicle->GetPosition().z, 2) + "), PM=" + + to_string(vehicle->MoverParameters->WheelFlat, 1) + " mm"; uitextline2 = - "HamZ=" + to_string( tmp->MoverParameters->fBrakeCtrlPos, 2 ) - + "; HamP=" + std::to_string( tmp->MoverParameters->LocalBrakePos ) + "/" + to_string( tmp->MoverParameters->LocalBrakePosA, 2 ) - + "; NasJ=" + std::to_string( tmp->MoverParameters->MainCtrlPos ) + "(" + std::to_string( tmp->MoverParameters->MainCtrlActualPos ) + ")" - + "; NasB=" + std::to_string( tmp->MoverParameters->ScndCtrlPos ) + "(" + std::to_string( tmp->MoverParameters->ScndCtrlActualPos ) + ")" + "HamZ=" + to_string( vehicle->MoverParameters->fBrakeCtrlPos, 2 ) + + "; HamP=" + std::to_string( vehicle->MoverParameters->LocalBrakePos ) + "/" + to_string( vehicle->MoverParameters->LocalBrakePosA, 2 ) + + "; NasJ=" + std::to_string( vehicle->MoverParameters->MainCtrlPos ) + "(" + std::to_string( vehicle->MoverParameters->MainCtrlActualPos ) + ")" + + "; NasB=" + std::to_string( vehicle->MoverParameters->ScndCtrlPos ) + "(" + std::to_string( vehicle->MoverParameters->ScndCtrlActualPos ) + ")" + "; I=" + - ( tmp->MoverParameters->TrainType == dt_EZT ? - std::to_string( int( tmp->MoverParameters->ShowCurrent( 0 ) ) ) : - std::to_string( int( tmp->MoverParameters->Im ) ) ) - + "; U=" + to_string( int( tmp->MoverParameters->RunningTraction.TractionVoltage + 0.5 ) ) + ( vehicle->MoverParameters->TrainType == dt_EZT ? + std::to_string( int( vehicle->MoverParameters->ShowCurrent( 0 ) ) ) : + std::to_string( int( vehicle->MoverParameters->Im ) ) ) + + "; U=" + to_string( int( vehicle->MoverParameters->RunningTraction.TractionVoltage + 0.5 ) ) + "; R=" + - ( std::abs( tmp->MoverParameters->RunningShape.R ) > 10000.0 ? + ( std::abs( vehicle->MoverParameters->RunningShape.R ) > 10000.0 ? "~0.0" : - to_string( tmp->MoverParameters->RunningShape.R, 1 ) ) - + " An=" + to_string( tmp->MoverParameters->AccN, 2 ); // przyspieszenie poprzeczne + to_string( vehicle->MoverParameters->RunningShape.R, 1 ) ) + + " An=" + to_string( vehicle->MoverParameters->AccN, 2 ); // przyspieszenie poprzeczne if( tprev != simulation::Time.data().wSecond ) { tprev = simulation::Time.data().wSecond; - Acc = ( tmp->MoverParameters->Vel - VelPrev ) / 3.6; - VelPrev = tmp->MoverParameters->Vel; + Acc = ( vehicle->MoverParameters->Vel - VelPrev ) / 3.6; + VelPrev = vehicle->MoverParameters->Vel; } uitextline2 += ( "; As=" ) + to_string( Acc, 2 ); // przyspieszenie wzdłużne uitextline3 = - "cyl.ham. " + to_string( tmp->MoverParameters->BrakePress, 2 ) - + "; prz.gl. " + to_string( tmp->MoverParameters->PipePress, 2 ) - + "; zb.gl. " + to_string( tmp->MoverParameters->CompressedVolume, 2 ) + "cyl.ham. " + to_string( vehicle->MoverParameters->BrakePress, 2 ) + + "; prz.gl. " + to_string( vehicle->MoverParameters->PipePress, 2 ) + + "; zb.gl. " + to_string( vehicle->MoverParameters->CompressedVolume, 2 ) // youBy - drugi wezyk - + "; p.zas. " + to_string( tmp->MoverParameters->ScndPipePress, 2 ); + + "; p.zas. " + to_string( vehicle->MoverParameters->ScndPipePress, 2 ); // McZapkie: warto wiedziec w jakim stanie sa przelaczniki - if( tmp->MoverParameters->ConvOvldFlag ) + if( vehicle->MoverParameters->ConvOvldFlag ) uitextline3 += " C! "; - else if( tmp->MoverParameters->FuseFlag ) + else if( vehicle->MoverParameters->FuseFlag ) uitextline3 += " F! "; - else if( !tmp->MoverParameters->Mains ) + else if( !vehicle->MoverParameters->Mains ) uitextline3 += " () "; else { switch( - tmp->MoverParameters->ActiveDir * - ( tmp->MoverParameters->Imin == tmp->MoverParameters->IminLo ? + vehicle->MoverParameters->ActiveDir * + ( vehicle->MoverParameters->Imin == vehicle->MoverParameters->IminLo ? 1 : 2 ) ) { case 2: { uitextline3 += " >> "; break; } @@ -1710,50 +1706,50 @@ TWorld::Update_UI() { } } // McZapkie: predkosc szlakowa - if( tmp->MoverParameters->RunningTrack.Velmax == -1 ) { + if( vehicle->MoverParameters->RunningTrack.Velmax == -1 ) { uitextline3 += " Vtrack=Vmax"; } else { - uitextline3 += " Vtrack " + to_string( tmp->MoverParameters->RunningTrack.Velmax, 2 ); + uitextline3 += " Vtrack " + to_string( vehicle->MoverParameters->RunningTrack.Velmax, 2 ); } - if( ( tmp->MoverParameters->EnginePowerSource.SourceType == CurrentCollector ) - || ( tmp->MoverParameters->TrainType == dt_EZT ) ) { + if( ( vehicle->MoverParameters->EnginePowerSource.SourceType == CurrentCollector ) + || ( vehicle->MoverParameters->TrainType == dt_EZT ) ) { uitextline3 += - "; pant. " + to_string( tmp->MoverParameters->PantPress, 2 ) - + ( tmp->MoverParameters->bPantKurek3 ? "=" : "^" ) + "ZG"; + "; pant. " + to_string( vehicle->MoverParameters->PantPress, 2 ) + + ( vehicle->MoverParameters->bPantKurek3 ? "=" : "^" ) + "ZG"; } // McZapkie: komenda i jej parametry - if( tmp->MoverParameters->CommandIn.Command != ( "" ) ) { + if( vehicle->MoverParameters->CommandIn.Command != ( "" ) ) { uitextline4 = - "C:" + tmp->MoverParameters->CommandIn.Command - + " V1=" + to_string( tmp->MoverParameters->CommandIn.Value1, 0 ) - + " V2=" + to_string( tmp->MoverParameters->CommandIn.Value2, 0 ); + "C:" + vehicle->MoverParameters->CommandIn.Command + + " V1=" + to_string( vehicle->MoverParameters->CommandIn.Value1, 0 ) + + " V2=" + to_string( vehicle->MoverParameters->CommandIn.Value2, 0 ); } - if( ( tmp->Mechanik ) - && ( tmp->Mechanik->AIControllFlag == AIdriver ) ) { + if( ( vehicle->Mechanik ) + && ( vehicle->Mechanik->AIControllFlag == AIdriver ) ) { uitextline4 += - "AI: Vd=" + to_string( tmp->Mechanik->VelDesired, 0 ) - + " ad=" + to_string(tmp->Mechanik->AccDesired, 2) - + "/" + to_string(tmp->Mechanik->AccDesired*tmp->Mechanik->BrakeAccFactor(), 2) - + " atrain=" + to_string(tmp->Mechanik->fBrake_a0[0], 2) - + "+" + to_string(tmp->Mechanik->fBrake_a1[0], 2) - + " aS=" + to_string(tmp->Mechanik->AbsAccS_pub, 2) - + " Pd=" + to_string( tmp->Mechanik->ActualProximityDist, 0 ) - + " Vn=" + to_string( tmp->Mechanik->VelNext, 0 ); + "AI: Vd=" + to_string( vehicle->Mechanik->VelDesired, 0 ) + + " ad=" + to_string(vehicle->Mechanik->AccDesired, 2) + + "/" + to_string(vehicle->Mechanik->AccDesired*vehicle->Mechanik->BrakeAccFactor(), 2) + + " atrain=" + to_string(vehicle->Mechanik->fBrake_a0[0], 2) + + "+" + to_string(vehicle->Mechanik->fBrake_a1[0], 2) + + " aS=" + to_string(vehicle->Mechanik->AbsAccS_pub, 2) + + " Pd=" + to_string( vehicle->Mechanik->ActualProximityDist, 0 ) + + " Vn=" + to_string( vehicle->Mechanik->VelNext, 0 ); } // induction motor data - if( tmp->MoverParameters->EngineType == ElectricInductionMotor ) { + if( vehicle->MoverParameters->EngineType == ElectricInductionMotor ) { UITable->text_lines.emplace_back( " eimc: eimv: press:", Global::UITextColor ); for( int i = 0; i <= 20; ++i ) { std::string parameters = - tmp->MoverParameters->eimc_labels[ i ] + to_string( tmp->MoverParameters->eimc[ i ], 2, 9 ) + vehicle->MoverParameters->eimc_labels[ i ] + to_string( vehicle->MoverParameters->eimc[ i ], 2, 9 ) + " | " - + tmp->MoverParameters->eimv_labels[ i ] + to_string( tmp->MoverParameters->eimv[ i ], 2, 9 ); + + vehicle->MoverParameters->eimv_labels[ i ] + to_string( vehicle->MoverParameters->eimv[ i ], 2, 9 ); if( i < 10 ) { parameters += " | " + Train->fPress_labels[i] + to_string( Train->fPress[ i ][ 0 ], 2, 9 ); @@ -1762,7 +1758,7 @@ TWorld::Update_UI() { parameters += " med:"; } else if( i >= 13 ) { - parameters += " | " + tmp->MED_labels[ i - 13 ] + to_string( tmp->MED[ 0 ][ i - 13 ], 2, 9 ); + parameters += " | " + vehicle->MED_labels[ i - 13 ] + to_string( vehicle->MED[ 0 ][ i - 13 ], 2, 9 ); } UITable->text_lines.emplace_back( parameters, Global::UITextColor ); @@ -1818,69 +1814,73 @@ TWorld::Update_UI() { } //--------------------------------------------------------------------------- -void TWorld::OnCommandGet(DaneRozkaz *pRozkaz) +void TWorld::OnCommandGet(multiplayer::DaneRozkaz *pRozkaz) { // odebranie komunikatu z serwera if (pRozkaz->iSygn == MAKE_ID4('E','U','0','7') ) switch (pRozkaz->iComm) { case 0: // odesłanie identyfikatora wersji CommLog( Now() + " " + std::to_string(pRozkaz->iComm) + " version" + " rcvd"); - Ground.WyslijString(Global::asVersion, 0); // przedsatwienie się + multiplayer::WyslijString(Global::asVersion, 0); // przedsatwienie się break; case 1: // odesłanie identyfikatora wersji CommLog( Now() + " " + std::to_string(pRozkaz->iComm) + " scenery" + " rcvd"); - Ground.WyslijString(Global::SceneryFile, 1); // nazwa scenerii + multiplayer::WyslijString(Global::SceneryFile, 1); // nazwa scenerii break; - case 2: // event - CommLog( Now() + " " + std::to_string(pRozkaz->iComm) + " " + - std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0])) + " rcvd"); - if (Global::iMultiplayer) - { // WriteLog("Komunikat: "+AnsiString(pRozkaz->Name1)); - TEvent *e = Ground.FindEvent( - std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0]))); - if (e) - if ((e->Type == tp_Multiple) || (e->Type == tp_Lights) || - (e->evJoined != 0)) // tylko jawne albo niejawne Multiple - Ground.AddToQuery(e, NULL); // drugi parametr to dynamic wywołujący - tu brak + case 2: { + // event + CommLog( Now() + " " + std::to_string( pRozkaz->iComm ) + " " + + std::string( pRozkaz->cString + 1, (unsigned)( pRozkaz->cString[ 0 ] ) ) + " rcvd" ); + + if( Global::iMultiplayer ) { + auto *event = simulation::Events.FindEvent( std::string( pRozkaz->cString + 1, (unsigned)( pRozkaz->cString[ 0 ] ) ) ); + if( event != nullptr ) { + if( ( event->Type == tp_Multiple ) + || ( event->Type == tp_Lights ) + || ( event->evJoined != 0 ) ) { + // tylko jawne albo niejawne Multiple + simulation::Events.AddToQuery( event, nullptr ); // drugi parametr to dynamic wywołujący - tu brak + } + } } break; + } case 3: // rozkaz dla AI if (Global::iMultiplayer) { - int i = - int(pRozkaz->cString[8]); // długość pierwszego łańcucha (z przodu dwa floaty) + int i = int(pRozkaz->cString[8]); // długość pierwszego łańcucha (z przodu dwa floaty) CommLog( Now() + " " + to_string(pRozkaz->iComm) + " " + std::string(pRozkaz->cString + 11 + i, (unsigned)(pRozkaz->cString[10 + i])) + " rcvd"); - TGroundNode *t = Ground.DynamicFind( - std::string(pRozkaz->cString + 11 + i, - (unsigned)pRozkaz->cString[10 + i])); // nazwa pojazdu jest druga - if (t) - if (t->DynamicObject->Mechanik) - { - t->DynamicObject->Mechanik->PutCommand(std::string(pRozkaz->cString + 9, i), - pRozkaz->fPar[0], pRozkaz->fPar[1], - NULL, stopExt); // floaty są z przodu - WriteLog("AI command: " + std::string(pRozkaz->cString + 9, i)); - } + // nazwa pojazdu jest druga + auto *vehicle = simulation::Vehicles.find( { pRozkaz->cString + 11 + i, (unsigned)pRozkaz->cString[ 10 + i ] } ); + if( ( vehicle != nullptr ) + && ( vehicle->Mechanik != nullptr ) ) { + vehicle->Mechanik->PutCommand( + { pRozkaz->cString + 9, static_cast(i) }, + pRozkaz->fPar[0], pRozkaz->fPar[1], + nullptr, + stopExt ); // floaty są z przodu + WriteLog("AI command: " + std::string(pRozkaz->cString + 9, i)); + } } break; case 4: // badanie zajętości toru { CommLog(Now() + " " + to_string(pRozkaz->iComm) + " " + std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0])) + " rcvd"); - TGroundNode *t = Ground.FindGroundNode( - std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0])), TP_TRACK); - if (t) - if (t->pTrack->IsEmpty()) - Ground.WyslijWolny(t->asName); + + auto *track = simulation::Paths.find( std::string( pRozkaz->cString + 1, (unsigned)( pRozkaz->cString[ 0 ] ) ) ); + if( ( track != nullptr ) + && ( track->IsEmpty() ) ) { + multiplayer::WyslijWolny( track->name() ); + } } break; case 5: // ustawienie parametrów { - CommLog(Now() + " " + to_string(pRozkaz->iComm) + " params " + - to_string(*pRozkaz->iPar) + " rcvd"); + CommLog(Now() + " " + to_string(pRozkaz->iComm) + " params " + to_string(*pRozkaz->iPar) + " rcvd"); if (*pRozkaz->iPar == 0) // sprawdzenie czasu if (*pRozkaz->iPar & 1) // ustawienie czasu { @@ -1899,86 +1899,76 @@ void TWorld::OnCommandGet(DaneRozkaz *pRozkaz) } break; case 6: // pobranie parametrów ruchu pojazdu - if (Global::iMultiplayer) - { // Ra 2014-12: to ma działać również dla pojazdów bez obsady - CommLog(Now() + " " + to_string(pRozkaz->iComm) + " " + - std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0])) + - " rcvd"); - if (pRozkaz->cString[0]) // jeśli długość nazwy jest niezerowa - { // szukamy pierwszego pojazdu o takiej nazwie i odsyłamy parametry ramką #7 - TGroundNode *t; - if (pRozkaz->cString[1] == '*') - t = Ground.DynamicFind( - Global::asHumanCtrlVehicle); // nazwa pojazdu użytkownika - else - t = Ground.DynamicFindAny(std::string( - pRozkaz->cString + 1, (unsigned)pRozkaz->cString[0])); // nazwa pojazdu - if (t) - Ground.WyslijNamiary(t); // wysłanie informacji o pojeździe + if (Global::iMultiplayer) { + // Ra 2014-12: to ma działać również dla pojazdów bez obsady + CommLog( + Now() + " " + + to_string( pRozkaz->iComm ) + " " + + std::string{ pRozkaz->cString + 1, (unsigned)( pRozkaz->cString[ 0 ] ) } + + " rcvd" ); + if (pRozkaz->cString[0]) { + // jeśli długość nazwy jest niezerowa szukamy pierwszego pojazdu o takiej nazwie i odsyłamy parametry ramką #7 + auto *vehicle = ( + pRozkaz->cString[ 1 ] == '*' ? + simulation::Vehicles.find( Global::asHumanCtrlVehicle ) : + simulation::Vehicles.find( std::string{ pRozkaz->cString + 1, (unsigned)pRozkaz->cString[ 0 ] } ) ); + if( vehicle != nullptr ) { + multiplayer::WyslijNamiary( vehicle ); // wysłanie informacji o pojeździe + } } - else - { // dla pustego wysyłamy ramki 6 z nazwami pojazdów AI (jeśli potrzebne wszystkie, - // to rozpoznać np. "*") - Ground.DynamicList(); + else { + // dla pustego wysyłamy ramki 6 z nazwami pojazdów AI (jeśli potrzebne wszystkie, to rozpoznać np. "*") + simulation::Vehicles.DynamicList(); } } break; case 8: // ponowne wysłanie informacji o zajętych odcinkach toru CommLog(Now() + " " + to_string(pRozkaz->iComm) + " all busy track" + " rcvd"); - Ground.TrackBusyList(); + simulation::Paths.TrackBusyList(); break; case 9: // ponowne wysłanie informacji o zajętych odcinkach izolowanych CommLog(Now() + " " + to_string(pRozkaz->iComm) + " all busy isolated" + " rcvd"); - Ground.IsolatedBusyList(); + simulation::Paths.IsolatedBusyList(); break; case 10: // badanie zajętości jednego odcinka izolowanego CommLog(Now() + " " + to_string(pRozkaz->iComm) + " " + std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0])) + " rcvd"); - Ground.IsolatedBusy(std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0]))); + simulation::Paths.IsolatedBusy( std::string( pRozkaz->cString + 1, (unsigned)( pRozkaz->cString[ 0 ] ) ) ); break; case 11: // ustawienie parametrów ruchu pojazdu // Ground.IsolatedBusy(AnsiString(pRozkaz->cString+1,(unsigned)(pRozkaz->cString[0]))); break; case 12: // skrocona ramka parametrow pojazdow AI (wszystkich!!) CommLog(Now() + " " + to_string(pRozkaz->iComm) + " obsadzone" + " rcvd"); - Ground.WyslijObsadzone(); + multiplayer::WyslijObsadzone(); // Ground.IsolatedBusy(AnsiString(pRozkaz->cString+1,(unsigned)(pRozkaz->cString[0]))); break; case 13: // ramka uszkodzenia i innych stanow pojazdu, np. wylaczenie CA, wlaczenie recznego itd. - // WriteLog("Przyszlo 13!"); - // WriteLog(pRozkaz->cString); - CommLog(Now() + " " + to_string(pRozkaz->iComm) + " " + - std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0])) + - " rcvd"); - if (pRozkaz->cString[1]) // jeśli długość nazwy jest niezerowa - { // szukamy pierwszego pojazdu o takiej nazwie i odsyłamy parametry ramką #13 - TGroundNode *t; - if (pRozkaz->cString[2] == '*') - t = Ground.DynamicFind( - Global::asHumanCtrlVehicle); // nazwa pojazdu użytkownika - else - t = Ground.DynamicFindAny( - std::string(pRozkaz->cString + 2, - (unsigned)pRozkaz->cString[1])); // nazwa pojazdu - if (t) - { - TDynamicObject *d = t->DynamicObject; - while (d) - { - d->Damage(pRozkaz->cString[0]); - d = d->Next(); // pozostałe też - } - d = t->DynamicObject->Prev(); - while (d) - { - d->Damage(pRozkaz->cString[0]); - d = d->Prev(); // w drugą stronę też - } - Ground.WyslijUszkodzenia(t->asName, t->DynamicObject->MoverParameters->EngDmgFlag); // zwrot informacji o pojeździe - } - } - // Ground.IsolatedBusy(AnsiString(pRozkaz->cString+1,(unsigned)(pRozkaz->cString[0]))); + CommLog(Now() + " " + to_string(pRozkaz->iComm) + " " + + std::string(pRozkaz->cString + 1, (unsigned)(pRozkaz->cString[0])) + + " rcvd"); + if( pRozkaz->cString[ 1 ] ) // jeśli długość nazwy jest niezerowa + { // szukamy pierwszego pojazdu o takiej nazwie i odsyłamy parametry ramką #13 + auto *lookup = ( + pRozkaz->cString[ 2 ] == '*' ? + simulation::Vehicles.find( Global::asHumanCtrlVehicle ) : // nazwa pojazdu użytkownika + simulation::Vehicles.find( std::string( pRozkaz->cString + 2, (unsigned)pRozkaz->cString[ 1 ] ) ) ); // nazwa pojazdu + if( lookup == nullptr ) { break; } // nothing found, nothing to do + auto *d { lookup }; + while( d != nullptr ) { + d->Damage( pRozkaz->cString[ 0 ] ); + d = d->Next(); // pozostałe też + } + d = lookup->Prev(); + while( d != nullptr ) { + d->Damage( pRozkaz->cString[ 0 ] ); + d = d->Prev(); // w drugą stronę też + } + multiplayer::WyslijUszkodzenia( lookup->asName, lookup->MoverParameters->EngDmgFlag ); // zwrot informacji o pojeździe + } break; + default: + break; } }; @@ -2025,10 +2015,9 @@ void TWorld::CreateE3D(std::string const &Path, bool Dynamic) shift += 10.0; // następny tor będzie deczko dalej, aby nie zabić FPS at = 400.0; } - TGroundNode *tmp = new TGroundNode(); - tmp->DynamicObject = new TDynamicObject(); + auto *dynamic = new TDynamicObject(); - at -= tmp->DynamicObject->Init( + at -= dynamic->Init( "", Path.substr( 8, Path.size() - 9 ), // skip leading "dynamic/" and trailing slash "none", @@ -2038,7 +2027,7 @@ void TWorld::CreateE3D(std::string const &Path, bool Dynamic) "nobody", 0.0, "none", 0.0, "", false, "" ); // po wczytaniu CHK zrobić pętlę po ładunkach, aby każdy z nich skonwertować - cParser loadparser( tmp->DynamicObject->MoverParameters->LoadAccepted ); // typy ładunków + cParser loadparser( dynamic->MoverParameters->LoadAccepted ); // typy ładunków std::string loadname; loadparser.getTokens( 1, true, "," ); loadparser >> loadname; while( loadname != "" ) { @@ -2046,7 +2035,7 @@ void TWorld::CreateE3D(std::string const &Path, bool Dynamic) if( ( true == FileExists( Path + loadname + ".t3d" ) ) && ( false == FileExists( Path + loadname + ".e3d" ) ) ) { // a nie ma jeszcze odpowiednika binarnego - at -= tmp->DynamicObject->Init( + at -= dynamic->Init( "", Path.substr( 8, Path.size() - 9 ), // skip leading "dynamic/" and trailing slash "none", @@ -2060,20 +2049,20 @@ void TWorld::CreateE3D(std::string const &Path, bool Dynamic) loadparser.getTokens( 1, true, "," ); loadparser >> loadname; } - if( tmp->DynamicObject->iCabs ) { // jeśli ma jakąkolwiek kabinę + if( dynamic->iCabs ) { // jeśli ma jakąkolwiek kabinę delete Train; Train = new TTrain(); - if( tmp->DynamicObject->iCabs & 1 ) { - tmp->DynamicObject->MoverParameters->ActiveCab = 1; - Train->Init( tmp->DynamicObject, true ); + if( dynamic->iCabs & 1 ) { + dynamic->MoverParameters->ActiveCab = 1; + Train->Init( dynamic, true ); } - if( tmp->DynamicObject->iCabs & 4 ) { - tmp->DynamicObject->MoverParameters->ActiveCab = -1; - Train->Init( tmp->DynamicObject, true ); + if( dynamic->iCabs & 4 ) { + dynamic->MoverParameters->ActiveCab = -1; + Train->Init( dynamic, true ); } - if( tmp->DynamicObject->iCabs & 2 ) { - tmp->DynamicObject->MoverParameters->ActiveCab = 0; - Train->Init( tmp->DynamicObject, true ); + if( dynamic->iCabs & 2 ) { + dynamic->MoverParameters->ActiveCab = 0; + Train->Init( dynamic, true ); } } // z powrotem defaultowa sciezka do tekstur @@ -2129,7 +2118,7 @@ void TWorld::ChangeDynamic() { Train->DynamicSet( temp ); Controlled = temp; mvControlled = Controlled->ControlledFind()->MoverParameters; - Global::asHumanCtrlVehicle = Train->Dynamic()->GetName(); + Global::asHumanCtrlVehicle = Train->Dynamic()->name(); if( Train->Dynamic()->Mechanik ) // AI może sobie samo pójść if( !Train->Dynamic()->Mechanik->AIControllFlag ) // tylko jeśli ręcznie prowadzony { @@ -2153,7 +2142,6 @@ void TWorld::ChangeDynamic() { } Global::changeDynObj = NULL; } -//--------------------------------------------------------------------------- void TWorld::ToggleDaylight() { @@ -2170,6 +2158,30 @@ TWorld::ToggleDaylight() { } } +// calculates current season of the year based on set simulation date +void +TWorld::compute_season( int const Yearday ) const { + + using dayseasonpair = std::pair; + + std::vector seasonsequence { + { 65, "winter" }, + { 158, "spring" }, + { 252, "summer" }, + { 341, "autumn" }, + { 366, "winter" } }; + auto const lookup = + std::lower_bound( + std::begin( seasonsequence ), std::end( seasonsequence ), + clamp( Yearday, 1, seasonsequence.back().first ), + []( dayseasonpair const &Left, const int Right ) { + return Left.first < Right; } ); + + Global::Season = lookup->second + ":"; +} + + + void world_environment::init() { // m_skydome.init(); diff --git a/World.h b/World.h index b471dfdc..9bdc70e6 100644 --- a/World.h +++ b/World.h @@ -11,14 +11,16 @@ http://mozilla.org/MPL/2.0/. #include #include + #include "Camera.h" -#include "Ground.h" +#include "scene.h" #include "sky.h" #include "sun.h" #include "moon.h" #include "stars.h" #include "skydome.h" #include "McZapkie/MOVER.h" +#include "messaging.h" // wrapper for simulation time class simulation_time { @@ -105,12 +107,15 @@ TWorld(); void OnKeyDown(int cKey); // void UpdateWindow(); void OnMouseMove(double x, double y); - void OnCommandGet(DaneRozkaz *pRozkaz); + void OnCommandGet(multiplayer::DaneRozkaz *pRozkaz); bool Update(); void TrainDelete(TDynamicObject *d = NULL); TTrain* train() { return Train; } // switches between static and dynamic daylight calculation void ToggleDaylight(); + // calculates current season of the year based on set simulation date + void compute_season( int const Yearday ) const; + private: void Update_Environment(); @@ -144,8 +149,6 @@ private: void CabChange(TDynamicObject *old, TDynamicObject *now); // handles vehicle change flag void ChangeDynamic(); - - TGround Ground; //m7todo: tmp }; //--------------------------------------------------------------------------- diff --git a/frustum.h b/frustum.h index 71e880b1..fabd9c11 100644 --- a/frustum.h +++ b/frustum.h @@ -45,6 +45,9 @@ public: point_inside( float const X, float const Y, float const Z ) const; // tests if the sphere is in frustum, returns the distance between origin and sphere centre inline + float + sphere_inside( glm::dvec3 const &Center, float const Radius ) const { return sphere_inside( static_cast( Center.x ), static_cast( Center.y ), static_cast( Center.z ), Radius ); } + inline float sphere_inside( glm::vec3 const &Center, float const Radius ) const { return sphere_inside( Center.x, Center.y, Center.z, Radius ); } inline diff --git a/lua.cpp b/lua.cpp index f29d9d89..ce539649 100644 --- a/lua.cpp +++ b/lua.cpp @@ -6,6 +6,7 @@ #include "World.h" #include "Driver.h" #include "lua_ffi.h" +#include "simulation.h" extern TWorld World; @@ -72,29 +73,31 @@ extern "C" event->asName = std::string(name); event->fDelay = delay; event->Params[0].asPointer = (void*)handler; - World.Ground.add_event(event); - return event; + if (simulation::Events.insert(event)) + return event; + else + return nullptr; } EXPORT TEvent* scriptapi_event_find(const char* name) { std::string str(name); - TEvent *e = World.Ground.FindEvent(str); + TEvent *e = simulation::Events.FindEvent(str); if (e) return e; else - WriteLog("missing event: " + str); + WriteLog("lua: missing event: " + str); return nullptr; } EXPORT TTrack* scriptapi_track_find(const char* name) { std::string str(name); - TGroundNode *n = World.Ground.FindGroundNode(str, TP_TRACK); - if (n) - return n->pTrack; + TTrack *track = simulation::Paths.find(str); + if (track) + return track; else - WriteLog("missing track: " + str); + WriteLog("lua: missing track: " + str); return nullptr; } @@ -122,7 +125,7 @@ extern "C" EXPORT void scriptapi_event_dispatch(TEvent *e, TDynamicObject *activator) { if (e) - World.Ground.AddToQuery(e, activator); + simulation::Events.AddToQuery(e, activator); } EXPORT double scriptapi_random(double a, double b) @@ -132,7 +135,7 @@ extern "C" EXPORT void scriptapi_writelog(const char* txt) { - WriteLog("lua log: " + std::string(txt)); + WriteLog("lua: log: " + std::string(txt)); } struct memcell_values { const char *str; double num1; double num2; }; @@ -140,11 +143,11 @@ extern "C" EXPORT TMemCell* scriptapi_memcell_find(const char *name) { std::string str(name); - TGroundNode *n = World.Ground.FindGroundNode(str, TP_MEMCELL); - if (n) - return n->MemCell; + TMemCell *mc = simulation::Memory.find(str); + if (mc) + return mc; else - WriteLog("missing memcell: " + str); + WriteLog("lua: missing memcell: " + str); return nullptr; } diff --git a/material.cpp b/material.cpp index 1ff157b8..be93cf66 100644 --- a/material.cpp +++ b/material.cpp @@ -18,7 +18,7 @@ bool opengl_material::deserialize( cParser &Input, bool const Loadnow ) { bool result { false }; - while( true == deserialize_mapping( Input, Loadnow ) ) { + while( true == deserialize_mapping( Input, 0, Loadnow ) ) { result = true; // once would suffice but, eh } @@ -32,7 +32,7 @@ opengl_material::deserialize( cParser &Input, bool const Loadnow ) { // imports member data pair from the config file bool -opengl_material::deserialize_mapping( cParser &Input, bool const Loadnow ) { +opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ) { if( false == Input.getTokens( 2, true, "\n\r\t;, " ) ) { return false; @@ -48,8 +48,30 @@ opengl_material::deserialize_mapping( cParser &Input, bool const Loadnow ) { >> key >> value; - if( key == "texture1:" ) { texture1 = GfxRenderer.Fetch_Texture( path + value, Loadnow ); } - else if( key == "texture2:" ) { texture2 = GfxRenderer.Fetch_Texture( path + value, Loadnow ); } + if( value == "{" ) { + // detect and optionally process config blocks + cParser blockparser( Input.getToken( false, "}" ) ); + if( key == Global::Season ) { + // seasonal textures override generic textures + while( true == deserialize_mapping( blockparser, 1, Loadnow ) ) { + ; // all work is done in the header + } + } + } + else if( key == "texture1:" ) { + // TODO: full-fledged priority system + if( ( texture1 == null_handle ) + || ( Priority > 0 ) ) { + texture1 = GfxRenderer.Fetch_Texture( path + value, Loadnow ); + } + } + else if( key == "texture2:" ) { + // TODO: full-fledged priority system + if( ( texture2 == null_handle ) + || ( Priority > 0 ) ) { + texture2 = GfxRenderer.Fetch_Texture( path + value, Loadnow ); + } + } return true; // return value marks a key: value pair was extracted, nothing about whether it's recognized } @@ -65,7 +87,8 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) { if( filename.find( '|' ) != std::string::npos ) filename.erase( filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury - if( filename.rfind( '.' ) != std::string::npos ) { + if( ( filename.rfind( '.' ) != std::string::npos ) + && ( filename.rfind( '.' ) != filename.rfind( ".." ) + 1 ) ) { // we can get extension for .mat or, in legacy files, some image format. just trim it and set it to material file extension filename.erase( filename.rfind( '.' ) ); } diff --git a/material.h b/material.h index 777044d9..16614a7b 100644 --- a/material.h +++ b/material.h @@ -28,9 +28,9 @@ struct opengl_material { bool deserialize( cParser &Input, bool const Loadnow ); private: - // imports member data pair from the config file + // imports member data pair from the config file, overriding existing parameter values of lower priority bool - deserialize_mapping( cParser &Input, bool const Loadnow ); + deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ); }; class material_manager { diff --git a/messaging.cpp b/messaging.cpp new file mode 100644 index 00000000..0eaf885b --- /dev/null +++ b/messaging.cpp @@ -0,0 +1,258 @@ +/* +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/. +*/ + +#include "stdafx.h" +#include "messaging.h" + +#include "Globals.h" +#include "simulation.h" +#include "mtable.h" +#include "Logs.h" + +#ifdef _WIN32 +extern "C" +{ + GLFWAPI HWND glfwGetWin32Window( GLFWwindow* window ); //m7todo: potrzebne do directsound +} +#endif + +namespace multiplayer { + +void +Navigate(std::string const &ClassName, UINT Msg, WPARAM wParam, LPARAM lParam) { +#ifdef _WIN32 + // 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); +#endif +} + +void +WyslijEvent(const std::string &e, const std::string &d) +{ // Ra: jeszcze do wyczyszczenia +#ifdef _WIN32 + DaneRozkaz r; + r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); + r.iComm = 2; // 2 - event + size_t 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 = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura + cData.cbData = (DWORD)(12 + i + j); // 8+dwa liczniki i dwa zera kończące + cData.lpData = &r; + Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); + CommLog( Now() + " " + std::to_string(r.iComm) + " " + e + " sent" ); +#endif +} + +void +WyslijUszkodzenia(const std::string &t, char fl) +{ // wysłanie informacji w postaci pojedynczego tekstu +#ifdef _WIN32 + DaneRozkaz r; + r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); + r.iComm = 13; // numer komunikatu + size_t 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 = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura + cData.cbData = (DWORD)(11 + i); // 8+licznik i zero kończące + cData.lpData = &r; + Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); + CommLog( Now() + " " + std::to_string(r.iComm) + " " + t + " sent"); +#endif +} + +void +WyslijString(const std::string &t, int n) +{ // wysłanie informacji w postaci pojedynczego tekstu +#ifdef _WIN32 + DaneRozkaz r; + r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); + r.iComm = n; // numer komunikatu + size_t i = t.length(); + r.cString[0] = char(i); + strcpy(r.cString + 1, t.c_str()); // z zerem kończącym + COPYDATASTRUCT cData; + cData.dwData = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura + cData.cbData = (DWORD)(10 + i); // 8+licznik i zero kończące + cData.lpData = &r; + Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); + CommLog( Now() + " " + std::to_string(r.iComm) + " " + t + " sent"); +#endif +} + +void +WyslijWolny(const std::string &t) +{ // Ra: jeszcze do wyczyszczenia + WyslijString(t, 4); // tor wolny +} + +void +WyslijNamiary(TDynamicObject const *Vehicle) +{ // wysłanie informacji o pojeździe - (float), długość ramki będzie zwiększana w miarę potrzeby +#ifdef _WIN32 + // WriteLog("Wysylam pojazd"); + DaneRozkaz r; + r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); + r.iComm = 7; // 7 - dane pojazdu + int i = 32; + size_t j = Vehicle->asName.length(); + r.iPar[0] = i; // ilość danych liczbowych + r.fPar[1] = Global::fTimeAngleDeg / 360.0; // aktualny czas (1.0=doba) + r.fPar[2] = Vehicle->MoverParameters->Loc.X; // pozycja X + r.fPar[3] = Vehicle->MoverParameters->Loc.Y; // pozycja Y + r.fPar[4] = Vehicle->MoverParameters->Loc.Z; // pozycja Z + r.fPar[5] = Vehicle->MoverParameters->V; // prędkość ruchu X + r.fPar[6] = Vehicle->MoverParameters->nrot * M_PI * + Vehicle->MoverParameters->WheelDiameter; // prędkość obrotowa kóŁ + r.fPar[7] = 0; // prędkość ruchu Z + r.fPar[8] = Vehicle->MoverParameters->AccS; // przyspieszenie X + r.fPar[9] = Vehicle->MoverParameters->AccN; // przyspieszenie Y //na razie nie + r.fPar[10] = Vehicle->MoverParameters->AccV; // przyspieszenie Z + r.fPar[11] = Vehicle->MoverParameters->DistCounter; // przejechana odległość w km + r.fPar[12] = Vehicle->MoverParameters->PipePress; // ciśnienie w PG + r.fPar[13] = Vehicle->MoverParameters->ScndPipePress; // ciśnienie w PZ + r.fPar[14] = Vehicle->MoverParameters->BrakePress; // ciśnienie w CH + r.fPar[15] = Vehicle->MoverParameters->Compressor; // ciśnienie w ZG + r.fPar[16] = Vehicle->MoverParameters->Itot; // Prąd całkowity + r.iPar[17] = Vehicle->MoverParameters->MainCtrlPos; // Pozycja NJ + r.iPar[18] = Vehicle->MoverParameters->ScndCtrlPos; // Pozycja NB + r.iPar[19] = Vehicle->MoverParameters->MainCtrlActualPos; // Pozycja jezdna + r.iPar[20] = Vehicle->MoverParameters->ScndCtrlActualPos; // Pozycja bocznikowania + r.iPar[21] = Vehicle->MoverParameters->ScndCtrlActualPos; // Pozycja bocznikowania + r.iPar[22] = Vehicle->MoverParameters->ResistorsFlag * 1 + + Vehicle->MoverParameters->ConverterFlag * 2 + + +Vehicle->MoverParameters->CompressorFlag * 4 + + Vehicle->MoverParameters->Mains * 8 + + +Vehicle->MoverParameters->DoorLeftOpened * 16 + + Vehicle->MoverParameters->DoorRightOpened * 32 + + +Vehicle->MoverParameters->FuseFlag * 64 + + Vehicle->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 < Vehicle->iAnimType[ANIM_PANTS]) + { + r.fPar[23 + p] = Vehicle->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] = + Vehicle->MoverParameters->ShowCurrent(p + 1); // amperomierze kolejnych grup + // WriteLog("zapisalem prady"); + r.iPar[30] = Vehicle->MoverParameters->WarningSignal; // trabienie + r.fPar[31] = Vehicle->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, Vehicle->asName.c_str()); // zakończony zerem + COPYDATASTRUCT cData; + cData.dwData = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura + cData.cbData = (DWORD)(10 + i + j); // 8+licznik i zero kończące + cData.lpData = &r; + // WriteLog("Ramka gotowa"); + Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); + // WriteLog("Ramka poszla!"); + CommLog( Now() + " " + std::to_string(r.iComm) + " " + Vehicle->asName + " sent"); +#endif +} + +void +WyslijObsadzone() +{ // wysłanie informacji o pojeździe +#ifdef _WIN32 + DaneRozkaz2 r; + r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); + r.iComm = 12; // kod 12 + for (int i=0; i<1984; ++i) r.cString[i] = 0; + + // TODO: clean this up, we shouldn't be relying on direct list access + auto &vehiclelist = simulation::Vehicles.sequence(); + + int i = 0; + for( auto *vehicle : vehiclelist ) { + if( vehicle->Mechanik ) { + strcpy( r.cString + 64 * i, vehicle->asName.c_str() ); + r.fPar[ 16 * i + 4 ] = vehicle->GetPosition().x; + r.fPar[ 16 * i + 5 ] = vehicle->GetPosition().y; + r.fPar[ 16 * i + 6 ] = vehicle->GetPosition().z; + r.iPar[ 16 * i + 7 ] = vehicle->Mechanik->GetAction(); + strcpy( r.cString + 64 * i + 32, vehicle->GetTrack()->IsolatedName().c_str() ); + strcpy( r.cString + 64 * i + 48, vehicle->Mechanik->Timetable()->TrainName.c_str() ); + i++; + if( i > 30 ) break; + } + } + while (i <= 30) + { + strcpy(r.cString + 64 * i, "none"); + 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, "none"); + strcpy(r.cString + 64 * i + 48, "none"); + i++; + } + + COPYDATASTRUCT cData; + cData.dwData = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura + cData.cbData = 8 + 1984; // 8+licznik i zero kończące + cData.lpData = &r; + // WriteLog("Ramka gotowa"); + Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); + CommLog( Now() + " " + std::to_string(r.iComm) + " obsadzone" + " sent"); +#endif +} + +void +WyslijParam(int nr, int fl) +{ // wysłanie parametrów symulacji w ramce (nr) z flagami (fl) +#ifdef _WIN32 + DaneRozkaz r; + r.iSygn = MAKE_ID4( 'E', 'U', '0', '7' ); + 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 = MAKE_ID4( 'E', 'U', '0', '7' ); // sygnatura + cData.cbData = 12 + i; // 12+rozmiar danych + cData.lpData = &r; + Navigate( "TEU07SRK", WM_COPYDATA, (WPARAM)glfwGetWin32Window( Global::window ), (LPARAM)&cData ); +#endif +} + +} // multiplayer + +//--------------------------------------------------------------------------- diff --git a/messaging.h b/messaging.h new file mode 100644 index 00000000..6cb94aa4 --- /dev/null +++ b/messaging.h @@ -0,0 +1,51 @@ +/* +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/. +*/ + +#pragma once + +#include +#include "winheaders.h" + +class TDynamicObject; + +namespace multiplayer { + +struct DaneRozkaz { // struktura komunikacji z EU07.EXE + int iSygn; // sygnatura 'EU07' + int iComm; // rozkaz/status (kod ramki) + union { + float fPar[ 62 ]; + int iPar[ 62 ]; + char cString[ 248 ]; // upakowane stringi + }; +}; + +struct DaneRozkaz2 { // struktura komunikacji z EU07.EXE + int iSygn; // sygnatura 'EU07' + int iComm; // rozkaz/status (kod ramki) + union { + float fPar[ 496 ]; + int iPar[ 496 ]; + char cString[ 1984 ]; // upakowane stringi + }; +}; + +void Navigate( std::string const &ClassName, UINT Msg, WPARAM wParam, LPARAM lParam ); + +void WyslijEvent( const std::string &e, const std::string &d ); +void WyslijString( const std::string &t, int n ); +void WyslijWolny( const std::string &t ); +void WyslijNamiary( TDynamicObject const *Vehicle ); +void WyslijParam( int nr, int fl ); +void WyslijUszkodzenia( const std::string &t, char fl ); +void WyslijObsadzone(); // -> skladanie wielu pojazdow + +} // multiplayer + +//--------------------------------------------------------------------------- diff --git a/mouseinput.cpp b/mouseinput.cpp index 4bb05301..99e0684c 100644 --- a/mouseinput.cpp +++ b/mouseinput.cpp @@ -363,6 +363,12 @@ mouse_input::default_bindings() { { "pantselectedoff_sw:", { user_command::none, user_command::none } }, // TODO: lower selected pantograp(s) command + { "pantcompressor_sw:", { + user_command::pantographcompressoractivate, + user_command::none } }, + { "pantcompressorvalve_sw:", { + user_command::pantographcompressorvalvetoggle, + user_command::none } }, { "trainheating_sw:", { user_command::heatingtoggle, user_command::none } }, diff --git a/openglgeometrybank.h b/openglgeometrybank.h index 14d345cc..06a26c49 100644 --- a/openglgeometrybank.h +++ b/openglgeometrybank.h @@ -25,8 +25,8 @@ struct basic_vertex { glm::vec2 texture; // uv space basic_vertex() = default; - basic_vertex( glm::vec3 const &Position, glm::vec3 const &Normal, glm::vec2 const &Texture ) : - position( Position ), normal( Normal ), texture( Texture ) + basic_vertex( glm::vec3 Position, glm::vec3 Normal, glm::vec2 Texture ) : + position( Position ), normal( Normal ), texture( Texture ) {} void serialize( std::ostream& ) const; void deserialize( std::istream& ); @@ -58,8 +58,8 @@ struct geometry_handle { geometry_handle() : bank( 0 ), chunk( 0 ) {} - geometry_handle( std::uint32_t const Bank, std::uint32_t const Chunk ) : - bank( Bank ), chunk( Chunk ) + geometry_handle( std::uint32_t Bank, std::uint32_t Chunk ) : + bank( Bank ), chunk( Chunk ) {} // methods inline @@ -121,8 +121,8 @@ protected: unsigned int type; // kind of geometry used by the chunk vertex_array vertices; // geometry data // NOTE: constructor doesn't copy provided vertex data, but moves it - geometry_chunk( vertex_array &Vertices, unsigned int const Type ) : - type( Type ) + geometry_chunk( vertex_array &Vertices, unsigned int Type ) : + type( Type ) { vertices.swap( Vertices ); } diff --git a/parser.cpp b/parser.cpp index c570369f..1623933f 100644 --- a/parser.cpp +++ b/parser.cpp @@ -21,13 +21,14 @@ http://mozilla.org/MPL/2.0/. // cParser -- generic class for parsing text data. // constructors -cParser::cParser( std::string const &Stream, buffertype const Type, std::string Path, bool const Loadtraction ) : +cParser::cParser( std::string const &Stream, buffertype const Type, std::string Path, bool const Loadtraction, std::vector Parameters ) : mPath(Path), LoadTraction( Loadtraction ) { // build comments map mComments.insert(commentmap::value_type("/*", "*/")); mComments.insert(commentmap::value_type("//", "\n")); // mComments.insert(commentmap::value_type("--","\n")); //Ra: to chyba nie używane + // store to calculate sub-sequent includes from relative path if( Type == buffertype::buffer_FILE ) { mFile = Stream; @@ -60,6 +61,10 @@ cParser::cParser( std::string const &Stream, buffertype const Type, std::string mLine = 1; } } + // set parameter set if one was provided + if( false == Parameters.empty() ) { + parameters.swap( Parameters ); + } } // destructor @@ -146,91 +151,83 @@ bool cParser::getTokens(unsigned int Count, bool ToLower, const char *Break) return true; } -std::string cParser::readToken(bool ToLower, const char *Break) -{ - std::string token = ""; - size_t pos; // początek podmienianego ciągu - // see if there's include parsing going on. clean up when it's done. - if (mIncludeParser) - { - token = mIncludeParser->readToken(ToLower, Break); - if (!token.empty()) - { - pos = token.find("(p"); - // check if the token is a parameter which should be replaced with stored true value - if (pos != std::string::npos) //!=npos to znalezione - { - std::string parameter = - token.substr(pos + 2, token.find(")", pos) - pos + 2); // numer parametru - token.erase(pos, token.find(")", pos) - pos + 1); // najpierw usunięcie "(pN)" - size_t nr = atoi(parameter.c_str()) - 1; - if (nr < parameters.size()) - { - token.insert(pos, parameters.at(nr)); // wklejenie wartości parametru - if (ToLower) - for (; pos < token.length(); ++pos) - token[pos] = tolower(token[pos]); - } - else - token.insert(pos, "none"); // zabezpieczenie przed brakiem parametru - } - return token; - } - else - { - mIncludeParser = NULL; - parameters.clear(); +std::string cParser::readToken( bool ToLower, const char *Break ) { + + std::string token; + if( mIncludeParser ) { + // see if there's include parsing going on. clean up when it's done. + token = mIncludeParser->readToken( ToLower, Break ); + if( true == token.empty() ) { + mIncludeParser = nullptr; } } - // get the token yourself if there's no child to delegate it to. - char c { 0 }; - do - { - while (mStream->peek() != EOF && strchr(Break, c = mStream->get()) == NULL) - { - if (ToLower) - c = tolower(c); - token += c; - if (findQuotes(token)) // do glue together words enclosed in quotes - break; - if (trimComments(token)) // don't glue together words separated with comment - break; - } - if( c == '\n' ) { - // update line counter - ++mLine; - } - } while (token == "" && mStream->peek() != EOF); // double check to deal with trailing spaces - // launch child parser if include directive found. - // NOTE: parameter collecting uses default set of token separators. - if (token.compare("include") == 0) - { // obsługa include - std::string includefile = readToken(ToLower); // nazwa pliku - if (LoadTraction ? true : ((includefile.find("tr/") == std::string::npos) && - (includefile.find("tra/") == std::string::npos))) - { - // std::string trtest2="niemaproblema"; //nazwa odporna na znalezienie "tr/" - // if (trtest=="x") //jeśli nie wczytywać drutów - // trtest2=includefile; //kopiowanie ścieżki do pliku - std::string parameter = readToken(false); // w parametrach nie zmniejszamy - while( (parameter.empty() == false) - && (parameter.compare("end") != 0) ) - { - parameters.push_back(parameter); - parameter = readToken(false); + if( true == token.empty() ) { + // get the token yourself if the delegation attempt failed + char c { 0 }; + do { + while( mStream->peek() != EOF && strchr( Break, c = mStream->get() ) == NULL ) { + if( ToLower ) + c = tolower( c ); + token += c; + if( findQuotes( token ) ) // do glue together words enclosed in quotes + break; + if( trimComments( token ) ) // don't glue together words separated with comment + break; + } + if( c == '\n' ) { + // update line counter + ++mLine; + } + } while( token == "" && mStream->peek() != EOF ); // double check to deal with trailing spaces + } + + if( false == parameters.empty() ) { + // if there's parameter list, check the token for potential parameters to replace + size_t pos; // początek podmienianego ciągu + while( ( pos = token.find( "(p" ) ) != std::string::npos ) { + // check if the token is a parameter which should be replaced with stored true value + auto const parameter{ token.substr( pos + 2, token.find( ")", pos ) - ( pos + 2 ) ) }; // numer parametru + token.erase( pos, token.find( ")", pos ) - pos + 1 ); // najpierw usunięcie "(pN)" + size_t nr = atoi( parameter.c_str() ) - 1; + if( nr < parameters.size() ) { + token.insert( pos, parameters.at( nr ) ); // wklejenie wartości parametru + if( ToLower ) + for( ; pos < parameters.at( nr ).size(); ++pos ) + token[ pos ] = tolower( token[ pos ] ); + } + else + token.insert( pos, "none" ); // zabezpieczenie przed brakiem parametru + } + } + + if( token == "include" ) { + // launch child parser if include directive found. + // NOTE: parameter collecting uses default set of token separators. + std::string includefile = readToken(ToLower); // nazwa pliku + if( ( true == LoadTraction ) + || ( ( includefile.find( "tr/" ) == std::string::npos ) + && ( includefile.find( "tra/" ) == std::string::npos ) ) ) { + // get parameter list for the child parser + std::vector includeparameters; + std::string parameter = readToken( false ); // w parametrach nie zmniejszamy + while( ( parameter.empty() == false ) + && ( parameter != "end" ) ) { + includeparameters.emplace_back( parameter ); + parameter = readToken( false ); + } + mIncludeParser = std::make_shared( includefile, buffer_FILE, mPath, LoadTraction, includeparameters ); + if( mIncludeParser->mSize <= 0 ) { + ErrorLog( "Bad include: can't open file \"" + includefile + "\"" ); } - // if (trtest2.find("tr/")!=0) - mIncludeParser = std::make_shared(includefile, buffer_FILE, mPath, LoadTraction); - if (mIncludeParser->mSize <= 0) - ErrorLog("Missed include: " + includefile); } else { - while( token.compare( "end" ) != 0 ) { + while( token != "end" ) { token = readToken( true ); // minimize risk of case mismatch on comparison } } token = readToken(ToLower, Break); } + // all done return token; } diff --git a/parser.h b/parser.h index 5709d16c..4799a7d2 100644 --- a/parser.h +++ b/parser.h @@ -28,7 +28,7 @@ class cParser //: public std::stringstream buffer_TEXT }; // constructors: - cParser(std::string const &Stream, buffertype const Type = buffer_TEXT, std::string Path = "", bool const Loadtraction = true ); + cParser(std::string const &Stream, buffertype const Type = buffer_TEXT, std::string Path = "", bool const Loadtraction = true, std::vector Parameters = std::vector() ); // destructor: virtual ~cParser(); // methods: diff --git a/renderer.cpp b/renderer.cpp index f06d2a3b..030a0e85 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/. #include "DynObj.h" #include "AnimModel.h" #include "Traction.h" +#include "simulation.h" #include "uilayer.h" #include "Logs.h" #include "usefull.h" @@ -53,9 +54,9 @@ opengl_camera::update_frustum( glm::mat4 const &Projection, glm::mat4 const &Mod // returns true if specified object is within camera frustum, false otherwise bool -opengl_camera::visible( bounding_area const &Area ) const { +opengl_camera::visible( scene::bounding_area const &Area ) const { - return ( m_frustum.sphere_inside( Area.center, Area.radius ) > 0.0f ); + return ( m_frustum.sphere_inside( Area.center, Area.radius ) > 0.f ); } bool @@ -316,23 +317,32 @@ opengl_renderer::Init( GLFWwindow *Window ) { bool opengl_renderer::Render() { - if( m_drawstart != std::chrono::steady_clock::time_point() ) { - m_drawtime = std::max( 20.f, 0.95f * m_drawtime + std::chrono::duration_cast( ( std::chrono::steady_clock::now() - m_drawstart ) ).count() / 1000.f ); - } - m_drawstart = std::chrono::steady_clock::now(); - auto const drawstartcolorpass = m_drawstart; + Timer::subsystem.gfx_total.stop(); + Timer::subsystem.gfx_total.start(); // note: gfx_total is actually frame total, clean this up + Timer::subsystem.gfx_color.start(); m_renderpass.draw_mode = rendermode::none; // force setup anew - m_debuginfo.clear(); - ++m_framestamp; + m_debugtimestext.clear(); + m_debugstats = debug_stats(); Render_pass( rendermode::color ); + Timer::subsystem.gfx_color.stop(); - m_drawcount = m_drawqueue.size(); - // accumulate last 20 frames worth of render time (cap at 1000 fps to prevent calculations going awry) - m_drawtimecolorpass = std::max( 20.f, 0.95f * m_drawtimecolorpass + std::chrono::duration_cast( ( std::chrono::steady_clock::now() - drawstartcolorpass ) ).count() / 1000.f ); - m_debuginfo += "frame total: " + to_string( m_drawtimecolorpass / 20.f, 2 ) + " msec (" + std::to_string( m_drawqueue.size() ) + " sectors) "; - + Timer::subsystem.gfx_swap.start(); glfwSwapBuffers( m_window ); + Timer::subsystem.gfx_swap.stop(); + + m_drawcount = m_cellqueue.size(); + m_debugtimestext + += "frame: " + to_string( Timer::subsystem.gfx_color.average(), 2 ) + " msec (" + std::to_string( m_cellqueue.size() ) + " sectors) " + += "gpu side: " + to_string( Timer::subsystem.gfx_swap.average(), 2 ) + " msec " + += "(" + to_string( Timer::subsystem.gfx_color.average() + Timer::subsystem.gfx_swap.average(), 2 ) + " msec total)"; + m_debugstatstext = + "drawcalls: " + to_string( m_debugstats.drawcalls ) + + "; dyn: " + to_string( m_debugstats.dynamics ) + " mod: " + to_string( m_debugstats.models ) + " sub: " + to_string( m_debugstats.submodels ) + + "; trk: " + to_string( m_debugstats.paths ) + " shp: " + to_string( m_debugstats.shapes ) + + " trc: " + to_string( m_debugstats.traction ) + " lin: " + to_string( m_debugstats.lines ); + + ++m_framestamp; return true; // for now always succeed } @@ -341,6 +351,15 @@ opengl_renderer::Render() { void opengl_renderer::Render_pass( rendermode const Mode ) { +#ifdef EU07_USE_DEBUG_CAMERA + // setup world camera for potential visualization + setup_pass( + m_worldcamera, + rendermode::color, + 0.f, + 1.0, + true ); +#endif setup_pass( m_renderpass, Mode ); switch( m_renderpass.draw_mode ) { @@ -348,6 +367,7 @@ opengl_renderer::Render_pass( rendermode const Mode ) { opengl_camera shadowcamera; // temporary helper, remove once ortho shadowmap code is done if( ( true == Global::RenderShadows ) + && ( false == Global::bWireFrame ) && ( true == World.InitPerformed() ) && ( m_shadowcolor != colors::white ) ) { // run shadowmap pass before color @@ -357,19 +377,6 @@ opengl_renderer::Render_pass( rendermode const Mode ) { #endif shadowcamera = m_renderpass.camera; // cache shadow camera placement for visualization setup_pass( m_renderpass, Mode ); // restore draw mode. TBD, TODO: render mode stack -#ifdef EU07_USE_DEBUG_CAMERA - setup_pass( - m_worldcamera, - rendermode::color, - 0.f, - std::min( - 1.f, - Global::shadowtune.depth / ( Global::BaseDrawRange * Global::fDistanceFactor ) - * std::max( - 1.f, - Global::ZoomFactor * 0.5f ) ), - true ); -#endif // setup shadowmap matrix m_shadowtexturematrix = //bias from [-1, 1] to [0, 1] }; @@ -380,7 +387,6 @@ opengl_renderer::Render_pass( rendermode const Mode ) { glm::mat4{ glm::mat3{ shadowcamera.modelview() } }, glm::vec3{ m_renderpass.camera.position() - shadowcamera.position() } ); } - if( ( true == m_environmentcubetexturesupport ) && ( true == World.InitPerformed() ) ) { // potentially update environmental cube map @@ -388,7 +394,6 @@ opengl_renderer::Render_pass( rendermode const Mode ) { setup_pass( m_renderpass, Mode ); // restore draw mode. TBD, TODO: render mode stack } } - ::glViewport( 0, 0, Global::iWindowWidth, Global::iWindowHeight ); if( World.InitPerformed() ) { @@ -417,7 +422,9 @@ opengl_renderer::Render_pass( rendermode const Mode ) { ::glColor4f( 1.f, 0.9f, 0.8f, 1.f ); ::glDisable( GL_LIGHTING ); ::glDisable( GL_TEXTURE_2D ); - shadowcamera.draw( m_renderpass.camera.position() - shadowcamera.position() ); + if( ( true == Global::RenderShadows ) && ( false == Global::bWireFrame ) ) { + shadowcamera.draw( m_renderpass.camera.position() - shadowcamera.position() ); + } if( DebugCameraFlag ) { ::glColor4f( 0.8f, 1.f, 0.9f, 1.f ); m_worldcamera.camera.draw( m_renderpass.camera.position() - m_worldcamera.camera.position() ); @@ -435,10 +442,10 @@ opengl_renderer::Render_pass( rendermode const Mode ) { } #endif switch_units( true, true, true ); - Render( &World.Ground ); + Render( simulation::Region ); // ...translucent parts setup_drawing( true ); - Render_Alpha( &World.Ground ); + Render_Alpha( simulation::Region ); if( World.Train != nullptr ) { // cab render is performed without shadows, due to low resolution and number of models without windows :| switch_units( true, false, false ); @@ -462,7 +469,7 @@ opengl_renderer::Render_pass( rendermode const Mode ) { if( World.InitPerformed() ) { // setup - auto const shadowdrawstart = std::chrono::steady_clock::now(); + Timer::subsystem.gfx_shadows.start(); ::glBindFramebufferEXT( GL_FRAMEBUFFER, m_shadowframebuffer ); @@ -488,15 +495,14 @@ opengl_renderer::Render_pass( rendermode const Mode ) { #else setup_units( false, false, false ); #endif - Render( &World.Ground ); + Render( simulation::Region ); // post-render restore ::glDisable( GL_POLYGON_OFFSET_FILL ); ::glDisable( GL_SCISSOR_TEST ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target - - m_drawtimeshadowpass = 0.95f * m_drawtimeshadowpass + std::chrono::duration_cast( ( std::chrono::steady_clock::now() - shadowdrawstart ) ).count() / 1000.f; - m_debuginfo += "shadows: " + to_string( m_drawtimeshadowpass / 20.f, 2 ) + " msec (" + std::to_string( m_drawqueue.size() ) + " sectors) "; + Timer::subsystem.gfx_shadows.stop(); + m_debugtimestext += "shadows: " + to_string( Timer::subsystem.gfx_shadows.average(), 2 ) + " msec (" + std::to_string( m_cellqueue.size() ) + " sectors) "; } break; } @@ -518,7 +524,7 @@ opengl_renderer::Render_pass( rendermode const Mode ) { // opaque parts... setup_drawing( false ); setup_units( true, true, true ); - Render( &World.Ground ); + Render( simulation::Region ); /* // reflections are limited to sky and ground only, the update rate is too low for anything else // ...translucent parts @@ -571,7 +577,7 @@ opengl_renderer::Render_pass( rendermode const Mode ) { // opaque parts... setup_drawing( false ); setup_units( false, false, false ); - Render( &World.Ground ); + Render( simulation::Region ); // post-render cleanup } break; @@ -588,7 +594,7 @@ bool opengl_renderer::Render_reflections() { auto const &time = simulation::Time.data(); - auto const timestamp = time.wDay * 60 * 24 + time.wHour * 60 + time.wMinute; + auto const timestamp = time.wDay * 24 * 60 + time.wHour * 60 + time.wMinute; if( ( timestamp - m_environmentupdatetime < 5 ) && ( glm::length( m_renderpass.camera.position() - m_environmentupdatelocation ) < 1000.0 ) ) { // run update every 5+ mins of simulation time, or at least 1km from the last location @@ -834,7 +840,7 @@ opengl_renderer::setup_units( bool const Diffuse, bool const Shadows, bool const Active_Texture( m_helpertextureunit ); if( ( true == Reflections ) - || ( ( true == Global::RenderShadows ) && ( true == Shadows ) ) ) { + || ( ( true == Global::RenderShadows ) && ( true == Shadows ) && ( false == Global::bWireFrame ) ) ) { // we need to have texture on the helper for either the reflection and shadow generation (or both) if( true == m_environmentcubetexturesupport ) { // bind dynamic environment cube if it's enabled... @@ -857,7 +863,7 @@ opengl_renderer::setup_units( bool const Diffuse, bool const Shadows, bool const } } - if( ( true == Global::RenderShadows ) && ( true == Shadows ) ) { + if( ( true == Global::RenderShadows ) && ( true == Shadows ) && ( false == Global::bWireFrame ) ) { ::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); ::glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, glm::value_ptr( m_shadowcolor ) ); // TODO: dynamically calculated shadow colour, based on sun height @@ -915,6 +921,7 @@ opengl_renderer::setup_units( bool const Diffuse, bool const Shadows, bool const if( m_shadowtextureunit >= 0 ) { if( ( true == Global::RenderShadows ) && ( true == Shadows ) + && ( false == Global::bWireFrame ) && ( m_shadowcolor != colors::white ) ) { Active_Texture( m_shadowtextureunit ); @@ -1026,6 +1033,7 @@ opengl_renderer::switch_units( bool const Diffuse, bool const Shadows, bool cons if( ( true == Reflections ) || ( ( true == Global::RenderShadows ) && ( true == Shadows ) + && ( false == Global::bWireFrame ) && ( m_shadowcolor != colors::white ) ) ) { if( true == m_environmentcubetexturesupport ) { ::glEnable( GL_TEXTURE_CUBE_MAP ); @@ -1045,7 +1053,7 @@ opengl_renderer::switch_units( bool const Diffuse, bool const Shadows, bool cons } // shadow texture unit. if( m_shadowtextureunit >= 0 ) { - if( ( true == Global::RenderShadows ) && ( true == Shadows ) ) { + if( ( true == Global::RenderShadows ) && ( true == Shadows ) && ( false == Global::bWireFrame ) ) { Active_Texture( m_shadowtextureunit ); ::glEnable( GL_TEXTURE_2D ); @@ -1338,455 +1346,462 @@ opengl_renderer::Texture( texture_handle const Texture ) const { return m_textures.texture( Texture ); } +void +opengl_renderer::Render( scene::basic_region *Region ) { - -bool -opengl_renderer::Render( TGround *Ground ) { - - m_drawqueue.clear(); - + m_sectionqueue.clear(); + m_cellqueue.clear(); +/* + for( auto *section : Region->sections( m_renderpass.camera.position(), m_renderpass.draw_range * Global::fDistanceFactor ) ) { +#ifdef EU07_USE_DEBUG_CULLING + if( m_worldcamera.camera.visible( section->m_area ) ) { +#else + if( m_renderpass.camera.visible( section->m_area ) ) { +#endif + m_sectionqueue.emplace_back( section ); + } + } +*/ + // build a list of region sections to render glm::vec3 const cameraposition { m_renderpass.camera.position() }; - int const camerax = static_cast( std::floor( cameraposition.x / 1000.0f ) + iNumRects / 2 ); - int const cameraz = static_cast( std::floor( cameraposition.z / 1000.0f ) + iNumRects / 2 ); - int const segmentcount = 2 * static_cast(std::ceil( m_renderpass.draw_range * Global::fDistanceFactor / 1000.0f )); - int const originx = std::max( 0, camerax - segmentcount / 2 ); - int const originz = std::max( 0, cameraz - segmentcount / 2 ); + auto const camerax = static_cast( std::floor( cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) ); + auto const cameraz = static_cast( std::floor( cameraposition.z / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) ); + int const segmentcount = 2 * static_cast( std::ceil( m_renderpass.draw_range * Global::fDistanceFactor / scene::EU07_SECTIONSIZE ) ); + int const originx = camerax - segmentcount / 2; + int const originz = cameraz - segmentcount / 2; + + for( int row = originz; row <= originz + segmentcount; ++row ) { + if( row < 0 ) { continue; } + if( row >= scene::EU07_REGIONSIDESECTIONCOUNT ) { break; } + for( int column = originx; column <= originx + segmentcount; ++column ) { + if( column < 0 ) { continue; } + if( column >= scene::EU07_REGIONSIDESECTIONCOUNT ) { break; } + auto *section { Region->m_sections[ row * scene::EU07_REGIONSIDESECTIONCOUNT + column ] }; + if( ( section != nullptr ) +#ifdef EU07_USE_DEBUG_CULLING + && ( m_worldcamera.camera.visible( section->m_area ) ) ) { +#else + && ( m_renderpass.camera.visible( section->m_area ) ) ) { +#endif + m_sectionqueue.emplace_back( section ); + } + } + } switch( m_renderpass.draw_mode ) { case rendermode::color: { - Update_Lights( Ground->m_lights ); + Update_Lights( simulation::Lights ); - for( int column = originx; column <= originx + segmentcount; ++column ) { - for( int row = originz; row <= originz + segmentcount; ++row ) { - - auto *cell = &Ground->Rects[ column ][ row ]; - if( m_renderpass.camera.visible( cell->m_area ) ) { - Render( cell ); - } - } - } - // draw queue was filled while rendering content of ground cells. now sort the nodes based on their distance to viewer... - std::sort( - std::begin( m_drawqueue ), - std::end( m_drawqueue ), - []( distancesubcell_pair const &Left, distancesubcell_pair const &Right ) { - return ( Left.first ) < ( Right.first ); } ); - // ...then render the opaque content of the visible subcells. - for( auto subcellpair : m_drawqueue ) { - Render( subcellpair.second ); - } - break; - } - case rendermode::reflections: { - // reflections render only terrain geometry - for( int column = originx; column <= originx + segmentcount; ++column ) { - for( int row = originz; row <= originz + segmentcount; ++row ) { - - auto *cell = &Ground->Rects[ column ][ row ]; - if( m_renderpass.camera.visible( cell->m_area ) ) { - Render( cell ); - } - } - } + Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) ); + // draw queue is filled while rendering sections + Render( std::begin( m_cellqueue ), std::end( m_cellqueue ) ); break; } case rendermode::shadows: case rendermode::pickscenery: { - // these render modes don't bother with anything non-visual, or lights - for( int column = originx; column <= originx + segmentcount; ++column ) { - for( int row = originz; row <= originz + segmentcount; ++row ) { - - auto *cell = &Ground->Rects[ column ][ row ]; - if( m_renderpass.camera.visible( cell->m_area ) ) { - Render( cell ); - } - } - } + // these render modes don't bother with lights + Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) ); // they can also skip queue sorting, as they only deal with opaque geometry // NOTE: there's benefit from rendering front-to-back, but is it significant enough? TODO: investigate - for( auto subcellpair : m_drawqueue ) { - Render( subcellpair.second ); - } + Render( std::begin( m_cellqueue ), std::end( m_cellqueue ) ); + break; + } + case rendermode::reflections: { + // for the time being reflections render only terrain geometry + Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) ); break; } case rendermode::pickcontrols: default: { + // no need to render anything ourside of the cab in control picking mode break; } } - - return true; } -bool -opengl_renderer::Render( TGroundRect *Groundcell ) { - - bool result { false }; // will be true if we do any rendering - - Groundcell->LoadNodes(); // ewentualne tworzenie siatek +void +opengl_renderer::Render( section_sequence::iterator First, section_sequence::iterator Last ) { switch( m_renderpass.draw_mode ) { + case rendermode::color: + case rendermode::reflections: { + + break; + } + case rendermode::shadows: { + // experimental, for shadows render both back and front faces, to supply back faces of the 'forest strips' + ::glDisable( GL_CULL_FACE ); + break; } case rendermode::pickscenery: { // non-interactive scenery elements get neutral colour ::glColor3fv( glm::value_ptr( colors::none ) ); - } - case rendermode::color: - case rendermode::reflections: { - if( Groundcell->nRenderRect != nullptr ) { - // nieprzezroczyste trójkąty kwadratu kilometrowego - for( TGroundNode *node = Groundcell->nRenderRect; node != nullptr; node = node->nNext3 ) { - Render( node ); - } - result = true; - } break; } - case rendermode::shadows: { - if( Groundcell->nRenderRect != nullptr ) { - // experimental, for shadows render both back and front faces, to supply back faces of the 'forest strips' - ::glDisable( GL_CULL_FACE ); - // nieprzezroczyste trójkąty kwadratu kilometrowego - for( TGroundNode *node = Groundcell->nRenderRect; node != nullptr; node = node->nNext3 ) { - Render( node ); - } - result = true; - ::glEnable( GL_CULL_FACE ); - } - } - case rendermode::pickcontrols: default: { - break; - } + break; } } -#ifdef EU07_USE_OLD_TERRAINCODE - if( Groundcell->nTerrain ) { - Render( Groundcell->nTerrain ); - } + while( First != Last ) { + + auto *section = *First; + section->create_geometry(); + + // render shapes held by the section + switch( m_renderpass.draw_mode ) { + case rendermode::color: + case rendermode::reflections: + case rendermode::shadows: + case rendermode::pickscenery: { + if( false == section->m_shapes.empty() ) { + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset { section->m_area.center - m_renderpass.camera.position() }; + ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); + // render +#ifdef EU07_USE_DEBUG_CULLING + // debug + ::glLineWidth( 2.f ); + float const width = section->m_area.radius; + float const height = section->m_area.radius * 0.2f; + glDisable( GL_LIGHTING ); + glDisable( GL_TEXTURE_2D ); + glColor3ub( 255, 128, 128 ); + glBegin( GL_LINE_LOOP ); + glVertex3f( -width, height, width ); + glVertex3f( -width, height, -width ); + glVertex3f( width, height, -width ); + glVertex3f( width, height, width ); + glEnd(); + glBegin( GL_LINE_LOOP ); + glVertex3f( -width, 0, width ); + glVertex3f( -width, 0, -width ); + glVertex3f( width, 0, -width ); + glVertex3f( width, 0, width ); + glEnd(); + glBegin( GL_LINES ); + glVertex3f( -width, height, width ); glVertex3f( -width, 0, width ); + glVertex3f( -width, height, -width ); glVertex3f( -width, 0, -width ); + glVertex3f( width, height, -width ); glVertex3f( width, 0, -width ); + glVertex3f( width, height, width ); glVertex3f( width, 0, width ); + glEnd(); + glColor3ub( 255, 255, 255 ); + glEnable( GL_TEXTURE_2D ); + glEnable( GL_LIGHTING ); + glLineWidth( 1.f ); #endif + // shapes + for( auto const &shape : section->m_shapes ) { Render( shape, true ); } + // post-render cleanup + ::glPopMatrix(); + } + break; + } + case rendermode::pickcontrols: + default: { + break; + } + } - // add the subcells of the cell to the draw queue - switch( m_renderpass.draw_mode ) { - case rendermode::color: - case rendermode::shadows: - case rendermode::pickscenery: { - if( Groundcell->pSubRects != nullptr ) { - for( std::size_t subcellindex = 0; subcellindex < iNumSubRects * iNumSubRects; ++subcellindex ) { - auto subcell = Groundcell->pSubRects + subcellindex; - if( subcell->iNodeCount ) { - // o ile są jakieś obiekty, bo po co puste sektory przelatywać - m_drawqueue.emplace_back( - glm::length2( m_renderpass.camera.position() - glm::dvec3( subcell->m_area.center ) ), - subcell ); + // add the section's cells to the cell queue + switch( m_renderpass.draw_mode ) { + case rendermode::color: + case rendermode::shadows: + case rendermode::pickscenery: { + for( auto &cell : section->m_cells ) { + if( ( true == cell.m_active ) +#ifdef EU07_USE_DEBUG_CULLING + && ( m_worldcamera.camera.visible( cell.m_area ) ) ) { +#else + && ( m_renderpass.camera.visible( cell.m_area ) ) ) { +#endif + // store visible cells with content as well as their current distance, for sorting later + m_cellqueue.emplace_back( + glm::length2( m_renderpass.camera.position() - cell.m_area.center ), + &cell ); } } + break; + } + case rendermode::reflections: + case rendermode::pickcontrols: + default: { + break; } - break; - } - case rendermode::reflections: - case rendermode::pickcontrols: - default: { - break; } + // proceed to next section + ++First; } - return result; -} - -bool -opengl_renderer::Render( TSubRect *Groundsubcell ) { - - // oznaczanie aktywnych sektorów - Groundsubcell->LoadNodes(); - - // przeliczenia animacji torów w sektorze - Groundsubcell->RaAnimate( m_framestamp ); - - TGroundNode *node; switch( m_renderpass.draw_mode ) { - case rendermode::color: case rendermode::shadows: { - // nieprzezroczyste obiekty terenu - for( node = Groundsubcell->nRenderRect; node != nullptr; node = node->nNext3 ) { - Render( node ); - } - // nieprzezroczyste obiekty (oprócz pojazdów) - for( node = Groundsubcell->nRender; node != nullptr; node = node->nNext3 ) { - Render( node ); - } - // nieprzezroczyste z mieszanych modeli - for( node = Groundsubcell->nRenderMixed; node != nullptr; node = node->nNext3 ) { - Render( node ); - } - // nieprzezroczyste fragmenty pojazdów na torach - for( int trackidx = 0; trackidx < Groundsubcell->iTracks; ++trackidx ) { - for( auto dynamic : Groundsubcell->tTracks[ trackidx ]->Dynamics ) { - Render( dynamic ); - } - } -#ifdef EU07_SCENERY_EDITOR - // memcells - if( EditorModeFlag ) { - for( auto const memcell : Groundsubcell->m_memcells ) { - Render( memcell ); - } - } -#endif - break; - } - case rendermode::pickscenery: { - // same procedure like with regular render, but each node receives custom colour used for picking - // nieprzezroczyste obiekty terenu - for( node = Groundsubcell->nRenderRect; node != nullptr; node = node->nNext3 ) { - ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); - Render( node ); - } - // nieprzezroczyste obiekty (oprócz pojazdów) - for( node = Groundsubcell->nRender; node != nullptr; node = node->nNext3 ) { - ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); - Render( node ); - } - // nieprzezroczyste z mieszanych modeli - for( node = Groundsubcell->nRenderMixed; node != nullptr; node = node->nNext3 ) { - ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); - Render( node ); - } - // nieprzezroczyste fragmenty pojazdów na torach - for( int trackidx = 0; trackidx < Groundsubcell->iTracks; ++trackidx ) { - for( auto dynamic : Groundsubcell->tTracks[ trackidx ]->Dynamics ) { - ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); - Render( dynamic ); - } - } -#ifdef EU07_SCENERY_EDITOR - // memcells - if( EditorModeFlag ) { - for( auto const memcell : Groundsubcell->m_memcells ) { - ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); - Render( memcell ); - } - } + // restore standard face cull mode + ::glEnable( GL_CULL_FACE ); + break; } + default: { + break; } + } +} + +void +opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator Last ) { + + // cache initial iterator for the second sweep + auto first { First }; + // first pass draws elements which we know are located in section banks, to reduce vbo switching + while( First != Last ) { + + auto *cell = First->second; + // przeliczenia animacji torów w sektorze + cell->RaAnimate( m_framestamp ); + + switch( m_renderpass.draw_mode ) { + case rendermode::color: { + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; + ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); + + // render +#ifdef EU07_USE_DEBUG_CULLING + // debug + float const width = cell->m_area.radius; + float const height = cell->m_area.radius * 0.15f; + glDisable( GL_LIGHTING ); + glDisable( GL_TEXTURE_2D ); + glColor3ub( 255, 255, 0 ); + glBegin( GL_LINE_LOOP ); + glVertex3f( -width, height, width ); + glVertex3f( -width, height, -width ); + glVertex3f( width, height, -width ); + glVertex3f( width, height, width ); + glEnd(); + glBegin( GL_LINE_LOOP ); + glVertex3f( -width, 0, width ); + glVertex3f( -width, 0, -width ); + glVertex3f( width, 0, -width ); + glVertex3f( width, 0, width ); + glEnd(); + glBegin( GL_LINES ); + glVertex3f( -width, height, width ); glVertex3f( -width, 0, width ); + glVertex3f( -width, height, -width ); glVertex3f( -width, 0, -width ); + glVertex3f( width, height, -width ); glVertex3f( width, 0, -width ); + glVertex3f( width, height, width ); glVertex3f( width, 0, width ); + glEnd(); + glColor3ub( 255, 255, 255 ); + glEnable( GL_TEXTURE_2D ); + glEnable( GL_LIGHTING ); #endif + // opaque non-instanced shapes + for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } + // tracks + // TODO: update after path node refactoring + Render( std::begin( cell->m_paths ), std::end( cell->m_paths ) ); +#ifdef EU07_SCENERY_EDITOR + // TODO: re-implement + // memcells + if( EditorModeFlag ) { + for( auto const memcell : Groundsubcell->m_memcells ) { + Render( memcell ); + } + } +#endif + // post-render cleanup + ::glPopMatrix(); + + break; + } + case rendermode::shadows: { + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; + ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); + + // render + // opaque non-instanced shapes + for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } + // tracks + Render( std::begin( cell->m_paths ), std::end( cell->m_paths ) ); + + // post-render cleanup + ::glPopMatrix(); + + break; + } + case rendermode::pickscenery: { + // same procedure like with regular render, but editor-enabled nodes receive custom colour used for picking + // since all shapes of the section share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; + ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); + // render + // opaque non-instanced shapes + // non-interactive scenery elements get neutral colour + ::glColor3fv( glm::value_ptr( colors::none ) ); + for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } + // tracks + for( auto *path : cell->m_paths ) { + ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); + Render( path ); + } +#ifdef EU07_SCENERY_EDITOR + // memcells + // TODO: re-implement + if( EditorModeFlag ) { + for( auto const memcell : Groundsubcell->m_memcells ) { + ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); + Render( memcell ); + } + } +#endif + // post-render cleanup + ::glPopMatrix(); + + break; + } + case rendermode::reflections: + case rendermode::pickcontrols: + default: { + break; + } + } + + ++First; + } + // second pass draws elements with their own vbos + while( first != Last ) { + + auto const *cell = first->second; + + switch( m_renderpass.draw_mode ) { + case rendermode::color: + case rendermode::shadows: { + // opaque parts of instanced models + for( auto *instance : cell->m_instancesopaque ) { Render( instance ); } + // opaque parts of vehicles + for( auto *path : cell->m_paths ) { + for( auto *dynamic : path->Dynamics ) { + Render( dynamic ); + } + } + break; + } + case rendermode::pickscenery: { + // opaque parts of instanced models + // same procedure like with regular render, but each node receives custom colour used for picking + for( auto *instance : cell->m_instancesopaque ) { + ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); + Render( instance ); + } + // vehicles aren't included in scenery picking for the time being + break; + } + case rendermode::reflections: + case rendermode::pickcontrols: + default: { + break; + } + } + + ++first; + } +} + +void +opengl_renderer::Render( scene::shape_node const &Shape, bool const Ignorerange ) { + + auto const &data { Shape.data() }; + + if( false == Ignorerange ) { + double distancesquared; + switch( m_renderpass.draw_mode ) { + case rendermode::shadows: { + // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees + distancesquared = SquareMagnitude( ( data.area.center - Global::pCameraPosition ) / Global::ZoomFactor ) / Global::fDistanceFactor; + break; + } + default: { + distancesquared = SquareMagnitude( ( data.area.center - m_renderpass.camera.position() ) / Global::ZoomFactor ) / Global::fDistanceFactor; + break; + } + } + if( ( distancesquared < data.rangesquared_min ) + || ( distancesquared >= data.rangesquared_max ) ) { + return; + } + } + + // setup + Bind_Material( data.material ); + switch( m_renderpass.draw_mode ) { + case rendermode::color: + case rendermode::reflections: { + ::glColor3fv( glm::value_ptr( data.lighting.diffuse ) ); +/* + // NOTE: ambient component is set by diffuse component + // NOTE: for the time being non-instanced shapes are rendered without specular component due to wrong/arbitrary values set in legacy scenarios + // TBD, TODO: find a way to resolve this with the least amount of tears? + ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( data.lighting.specular * Global::DayLight.specular.a * m_specularopaquescalefactor ) ); +*/ break; } + // pick modes are painted with custom colours, and shadow pass doesn't use any + case rendermode::shadows: + case rendermode::pickscenery: case rendermode::pickcontrols: default: { break; } } - - return true; + // render + m_geometry.draw( data.geometry ); + // debug data + ++m_debugstats.shapes; + ++m_debugstats.drawcalls; } -bool -opengl_renderer::Render( TGroundNode *Node ) { -#ifdef EU07_USE_OLD_TERRAINCODE - switch (Node->iType) - { // obiekty renderowane niezależnie od odległości - case TP_SUBMODEL: - ::glPushMatrix(); - auto const originoffset = Node->pCenter - m_renderpass.camera.position(); - ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); - TSubModel::fSquareDist = 0; - Render( Node->smTerrain ); - ::glPopMatrix(); - return true; - } -#endif +void +opengl_renderer::Render( TAnimModel *Instance ) { + double distancesquared; switch( m_renderpass.draw_mode ) { case rendermode::shadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees - distancesquared = SquareMagnitude( ( Node->pCenter - Global::pCameraPosition ) / Global::ZoomFactor ) / Global::fDistanceFactor; + distancesquared = SquareMagnitude( ( Instance->location() - Global::pCameraPosition ) / Global::ZoomFactor ) / Global::fDistanceFactor; break; } default: { - distancesquared = SquareMagnitude( ( Node->pCenter - m_renderpass.camera.position() ) / Global::ZoomFactor ) / Global::fDistanceFactor; + distancesquared = SquareMagnitude( ( Instance->location() - m_renderpass.camera.position() ) / Global::ZoomFactor ) / Global::fDistanceFactor; break; } } - if( ( distancesquared < Node->fSquareMinRadius ) - || ( distancesquared >= Node->fSquareRadius ) ) { - return false; + if( ( distancesquared < Instance->m_rangesquaredmin ) + || ( distancesquared >= Instance->m_rangesquaredmax ) ) { + return; } - switch (Node->iType) { - - case TP_TRACK: { - // setup - switch( m_renderpass.draw_mode ) { - case rendermode::shadows: { - return false; - } - case rendermode::pickscenery: { - // add the node to the pick list - m_picksceneryitems.emplace_back( Node ); - break; - } - default: { - break; - } - } - ::glPushMatrix(); - auto const originoffset = Node->m_rootposition - m_renderpass.camera.position(); - ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); - // render - Render( Node->pTrack ); - // post-render cleanup - ::glPopMatrix(); - return true; + switch( m_renderpass.draw_mode ) { + case rendermode::pickscenery: { + // add the node to the pick list + m_picksceneryitems.emplace_back( Instance ); + break; } - - case TP_MODEL: { - switch( m_renderpass.draw_mode ) { - case rendermode::pickscenery: { - // add the node to the pick list - m_picksceneryitems.emplace_back( Node ); - break; - } - default: { - break; - } - } - Node->Model->RaAnimate( m_framestamp ); // jednorazowe przeliczenie animacji - Node->Model->RaPrepare(); - if( Node->Model->pModel ) { - // renderowanie rekurencyjne submodeli - Render( - Node->Model->pModel, - Node->Model->Material(), - distancesquared, - Node->pCenter - m_renderpass.camera.position(), - Node->Model->vAngle ); - } - return true; + default: { + break; } - - case GL_LINES: { - if( ( Node->Piece->geometry == null_handle ) - || ( Node->fLineThickness > 0.0 ) ) { - return false; - } - // setup - auto const distance = std::sqrt( distancesquared ); - auto const linealpha = - 10.0 * Node->fLineThickness - / std::max( - 0.5 * Node->m_radius + 1.0, - distance - ( 0.5 * Node->m_radius ) ); - switch( m_renderpass.draw_mode ) { - // wire colouring is disabled for modes other than colour - case rendermode::color: { - ::glColor4fv( - glm::value_ptr( - glm::vec4( - Node->Diffuse * glm::vec3( Global::DayLight.ambient ), // w zaleznosci od koloru swiatla - 1.0 ) ) ); // if the thickness is defined negative, lines are always drawn opaque - break; - } - case rendermode::shadows: - case rendermode::pickcontrols: - case rendermode::pickscenery: - default: { - break; - } - } - auto const linewidth = clamp( 0.5 * linealpha + Node->fLineThickness * Node->m_radius / 1000.0, 1.0, 8.0 ); - if( linewidth > 1.0 ) { - ::glLineWidth( static_cast( linewidth ) ); - } - - GfxRenderer.Bind_Material( null_handle ); - - ::glPushMatrix(); - auto const originoffset = Node->m_rootposition - m_renderpass.camera.position(); - ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); - - switch( m_renderpass.draw_mode ) { - case rendermode::pickscenery: { - // add the node to the pick list - m_picksceneryitems.emplace_back( Node ); - break; - } - default: { - break; - } - } - // render - m_geometry.draw( Node->Piece->geometry ); - - // post-render cleanup - ::glPopMatrix(); - - if( linewidth > 1.0 ) { ::glLineWidth( 1.0f ); } - - return true; - } - - case GL_TRIANGLES: { - if( ( Node->Piece->geometry == null_handle ) - || ( ( Node->iFlags & 0x10 ) == 0 ) ) { - return false; - } - // setup - Bind_Material( Node->m_material ); - switch( m_renderpass.draw_mode ) { - case rendermode::color: { - ::glColor3fv( glm::value_ptr( Node->Diffuse ) ); - break; - } - // pick modes get custom colours, and shadow pass doesn't use any - case rendermode::shadows: - case rendermode::pickcontrols: - case rendermode::pickscenery: - default: { - break; - } - } - - ::glPushMatrix(); - auto const originoffset = Node->m_rootposition - m_renderpass.camera.position(); - ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); - - switch( m_renderpass.draw_mode ) { - case rendermode::pickscenery: { - // add the node to the pick list - m_picksceneryitems.emplace_back( Node ); - break; - } - default: { - break; - } - } - // render - m_geometry.draw( Node->Piece->geometry ); - - // post-render cleanup - ::glPopMatrix(); - - return true; - } - - case TP_MEMCELL: { - switch( m_renderpass.draw_mode ) { - case rendermode::pickscenery: { - // add the node to the pick list - m_picksceneryitems.emplace_back( Node ); - break; - } - default: { - break; - } - } - Render( Node->MemCell ); - return true; - } - - default: { break; } } - // in theory we shouldn't ever get here but, eh - return false; + + Instance->RaAnimate( m_framestamp ); // jednorazowe przeliczenie animacji + Instance->RaPrepare(); + if( Instance->pModel ) { + // renderowanie rekurencyjne submodeli + Render( + Instance->pModel, + Instance->Material(), + distancesquared, + Instance->location() - m_renderpass.camera.position(), + Instance->vAngle ); + } } bool @@ -1796,6 +1811,9 @@ opengl_renderer::Render( TDynamicObject *Dynamic ) { if( false == Dynamic->renderme ) { return false; } + // debug data + ++m_debugstats.dynamics; + // setup TSubModel::iInstance = ( size_t )this; //żeby nie robić cudzych animacji glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.camera.position(); @@ -1997,6 +2015,9 @@ opengl_renderer::Render( TModel3d *Model, material_data const *Material, float c // render Render( Model->Root ); + // debug data + ++m_debugstats.models; + // post-render cleanup return true; @@ -2028,6 +2049,10 @@ opengl_renderer::Render( TSubModel *Submodel ) { && ( TSubModel::fSquareDist >= Submodel->fSquareMinDist ) && ( TSubModel::fSquareDist < Submodel->fSquareMaxDist ) ) { + // debug data + ++m_debugstats.submodels; + ++m_debugstats.drawcalls; + if( Submodel->iFlags & 0xC000 ) { ::glPushMatrix(); if( Submodel->fMatrix ) @@ -2273,6 +2298,12 @@ opengl_renderer::Render( TTrack *Track ) { && ( Track->m_material2 == 0 ) ) { return; } + if( false == Track->m_visible ) { + return; + } + + ++m_debugstats.paths; + ++m_debugstats.drawcalls; switch( m_renderpass.draw_mode ) { case rendermode::color: @@ -2289,8 +2320,15 @@ opengl_renderer::Render( TTrack *Track ) { Track->EnvironmentReset(); break; } - case rendermode::shadows: + case rendermode::shadows: { + // shadow pass includes trackbeds but not tracks themselves due to low resolution of the map + // TODO: implement + break; + } case rendermode::pickscenery: { + // add the node to the pick list + m_picksceneryitems.emplace_back( Track ); + if( Track->m_material1 != 0 ) { Bind_Material( Track->m_material1 ); m_geometry.draw( std::begin( Track->Geometry1 ), std::end( Track->Geometry1 ) ); @@ -2308,11 +2346,120 @@ opengl_renderer::Render( TTrack *Track ) { } } +// experimental, does track rendering in two passes, to take advantage of reduced texture switching +void +opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last ) { + + ::glColor3fv( glm::value_ptr( colors::white ) ); + // setup + switch( m_renderpass.draw_mode ) { + case rendermode::shadows: { + // NOTE: roads-based platforms tend to miss parts of shadows if rendered with either back or front culling + ::glDisable( GL_CULL_FACE ); + break; + } + default: { + break; + } + } + + // first pass, material 1 + for( auto first { First }; first != Last; ++first ) { + + auto const track { *first }; + + if( track->m_material1 == 0 ) { + continue; + } + if( false == track->m_visible ) { + continue; + } + + ++m_debugstats.paths; + ++m_debugstats.drawcalls; + + switch( m_renderpass.draw_mode ) { + case rendermode::color: + case rendermode::reflections: { + track->EnvironmentSet(); + Bind_Material( track->m_material1 ); + m_geometry.draw( std::begin( track->Geometry1 ), std::end( track->Geometry1 ) ); + track->EnvironmentReset(); + break; + } + case rendermode::shadows: { + if( ( std::abs( track->fTexHeight1 ) < 0.35f ) + || ( track->iCategoryFlag != 2 ) ) { + // shadows are only calculated for high enough roads, typically meaning track platforms + continue; + } + Bind_Material( track->m_material1 ); + m_geometry.draw( std::begin( track->Geometry1 ), std::end( track->Geometry1 ) ); + break; + } + case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach + case rendermode::pickcontrols: + default: { + break; + } + } + } + // second pass, material 2 + for( auto first { First }; first != Last; ++first ) { + + auto const track { *first }; + + if( track->m_material2 == 0 ) { + continue; + } + if( false == track->m_visible ) { + continue; + } + + switch( m_renderpass.draw_mode ) { + case rendermode::color: + case rendermode::reflections: { + track->EnvironmentSet(); + Bind_Material( track->m_material2 ); + m_geometry.draw( std::begin( track->Geometry2 ), std::end( track->Geometry2 ) ); + track->EnvironmentReset(); + break; + } + case rendermode::shadows: { + if( ( std::abs( track->fTexHeight1 ) < 0.35f ) + || ( ( track->iCategoryFlag == 1 ) + && ( track->eType != tt_Normal ) ) ) { + // shadows are only calculated for high enough trackbeds + continue; + } + Bind_Material( track->m_material2 ); + m_geometry.draw( std::begin( track->Geometry2 ), std::end( track->Geometry2 ) ); + break; + } + case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach + case rendermode::pickcontrols: + default: { + break; + } + } + } + // post-render reset + switch( m_renderpass.draw_mode ) { + case rendermode::shadows: { + ::glEnable( GL_CULL_FACE ); + break; + } + default: { + break; + } + } +} + void opengl_renderer::Render( TMemCell *Memcell ) { ::glPushMatrix(); - auto const position = Memcell->Position() - m_renderpass.camera.position(); + auto const position = Memcell->location() - m_renderpass.camera.position(); ::glTranslated( position.x, position.y + 0.5, position.z ); switch( m_renderpass.draw_mode ) { @@ -2343,203 +2490,237 @@ opengl_renderer::Render( TMemCell *Memcell ) { ::glPopMatrix(); } -bool -opengl_renderer::Render_Alpha( TGround *Ground ) { +void +opengl_renderer::Render_Alpha( scene::basic_region *Region ) { - TGroundNode *node; - TSubRect *tmp; - // Ra: renderowanie progresywne - zależne od FPS oraz kierunku patrzenia - for( auto subcellpair = std::rbegin( m_drawqueue ); subcellpair != std::rend( m_drawqueue ); ++subcellpair ) { - // przezroczyste trójkąty w oddzielnym cyklu przed modelami - tmp = subcellpair->second; - for( node = tmp->nRenderRectAlpha; node; node = node->nNext3 ) { - Render_Alpha( node ); - } - } - for( auto subcellpair = std::rbegin( m_drawqueue ); subcellpair != std::rend( m_drawqueue ); ++subcellpair ) - { // renderowanie przezroczystych modeli oraz pojazdów - Render_Alpha( subcellpair->second ); - } + // sort the nodes based on their distance to viewer + std::sort( + std::begin( m_cellqueue ), + std::end( m_cellqueue ), + []( distancecell_pair const &Left, distancecell_pair const &Right ) { + return ( Left.first ) < ( Right.first ); } ); - ::glDisable( GL_LIGHTING ); // linie nie powinny świecić - - for( auto subcellpair = std::rbegin( m_drawqueue ); subcellpair != std::rend( m_drawqueue ); ++subcellpair ) { - // druty na końcu, żeby się nie robiły białe plamy na tle lasu - tmp = subcellpair->second; - for( node = tmp->nRenderWires; node; node = node->nNext3 ) { - Render_Alpha( node ); - } - } - - ::glEnable( GL_LIGHTING ); - - return true; + Render_Alpha( std::rbegin( m_cellqueue ), std::rend( m_cellqueue ) ); } -bool -opengl_renderer::Render_Alpha( TSubRect *Groundsubcell ) { +void +opengl_renderer::Render_Alpha( cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last ) { - TGroundNode *node; - for( node = Groundsubcell->nRenderMixed; node; node = node->nNext3 ) - Render_Alpha( node ); // przezroczyste z mieszanych modeli - for( node = Groundsubcell->nRenderAlpha; node; node = node->nNext3 ) - Render_Alpha( node ); // przezroczyste modele - for( int trackidx = 0; trackidx < Groundsubcell->iTracks; ++trackidx ) { - for( auto dynamic : Groundsubcell->tTracks[ trackidx ]->Dynamics ) { - Render_Alpha( dynamic ); // przezroczyste fragmenty pojazdów na torach + // NOTE: this method is launched only during color pass therefore we don't bother with mode test here + // first pass draws elements which we know are located in section banks, to reduce vbo switching + { + auto first { First }; + while( first != Last ) { + + auto const *cell = first->second; + + if( false == cell->m_shapestranslucent.empty() ) { + // since all shapes of the cell share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset{ cell->m_area.center - m_renderpass.camera.position() }; + ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); + // render + // NOTE: we can reuse the method used to draw opaque geometry + for( auto const &shape : cell->m_shapestranslucent ) { Render( shape, false ); } + // post-render cleanup + ::glPopMatrix(); + } + + ++first; } } + // second pass draws elements with their own vbos + { + auto first { First }; + while( first != Last ) { - return true; + auto const *cell = first->second; + + // translucent parts of instanced models + for( auto *instance : cell->m_instancetranslucent ) { Render_Alpha( instance ); } + // translucent parts of vehicles + for( auto *path : cell->m_paths ) { + for( auto *dynamic : path->Dynamics ) { + Render_Alpha( dynamic ); + } + } + + ++first; + } + } + // third pass draws the wires; + // wires use section vbos, but for the time being we want to draw them at the very end + { + ::glDisable( GL_LIGHTING ); // linie nie powinny świecić + + auto first{ First }; + while( first != Last ) { + + auto const *cell = first->second; + + if( ( false == cell->m_traction.empty() + || ( false == cell->m_lines.empty() ) ) ) { + // since all shapes of the cell share center point we can optimize out a few calls here + ::glPushMatrix(); + auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; + ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); + if( !Global::bSmoothTraction ) { + // na liniach kiepsko wygląda - robi gradient + ::glDisable( GL_LINE_SMOOTH ); + } + Bind_Material( null_handle ); + // render + for( auto *traction : cell->m_traction ) { Render_Alpha( traction ); } + for( auto &lines : cell->m_lines ) { Render_Alpha( lines ); } + // post-render cleanup + ::glLineWidth( 1.0 ); + if( !Global::bSmoothTraction ) { + ::glEnable( GL_LINE_SMOOTH ); + } + ::glPopMatrix(); + } + + ++first; + } + + ::glEnable( GL_LIGHTING ); + } } -bool -opengl_renderer::Render_Alpha( TGroundNode *Node ) { +void +opengl_renderer::Render_Alpha( TAnimModel *Instance ) { double distancesquared; switch( m_renderpass.draw_mode ) { case rendermode::shadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees - distancesquared = SquareMagnitude( ( Node->pCenter - Global::pCameraPosition ) / Global::ZoomFactor ) / Global::fDistanceFactor; + distancesquared = SquareMagnitude( ( Instance->location() - Global::pCameraPosition ) / Global::ZoomFactor ) / Global::fDistanceFactor; break; } default: { - distancesquared = SquareMagnitude( ( Node->pCenter - m_renderpass.camera.position() ) / Global::ZoomFactor ) / Global::fDistanceFactor; + distancesquared = SquareMagnitude( ( Instance->location() - m_renderpass.camera.position() ) / Global::ZoomFactor ) / Global::fDistanceFactor; break; } } - if( ( distancesquared < Node->fSquareMinRadius ) - || ( distancesquared >= Node->fSquareRadius ) ) { - return false; + if( ( distancesquared < Instance->m_rangesquaredmin ) + || ( distancesquared >= Instance->m_rangesquaredmax ) ) { + return; } - switch (Node->iType) - { - case TP_TRACTION: { - if( Node->bVisible ) { - // rysuj jesli sa druty i nie zerwana - if( ( Node->hvTraction->Wires == 0 ) - || ( true == TestFlag( Node->hvTraction->DamageFlag, 128 ) ) ) { - return false; - } - // setup - if( !Global::bSmoothTraction ) { - // na liniach kiepsko wygląda - robi gradient - ::glDisable( GL_LINE_SMOOTH ); - } - float const linealpha = static_cast( - std::min( - 1.25, - 5000 * Node->hvTraction->WireThickness / ( distancesquared + 1.0 ) ) ); // zbyt grube nie są dobre - ::glLineWidth( linealpha ); - // McZapkie-261102: kolor zalezy od materialu i zasniedzenia - auto const color { Node->hvTraction->wire_color() }; - ::glColor4f( color.r, color.g, color.b, linealpha ); - - Bind_Material( null_handle ); - - ::glPushMatrix(); - auto const originoffset = Node->m_rootposition - m_renderpass.camera.position(); - ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); - - // render - m_geometry.draw( Node->hvTraction->m_geometry ); - - // post-render cleanup - ::glPopMatrix(); - - ::glLineWidth( 1.0 ); - if( !Global::bSmoothTraction ) { - ::glEnable( GL_LINE_SMOOTH ); - } - - return true; - } - else { - return false; - } - } - case TP_MODEL: { - - Node->Model->RaPrepare(); - if( Node->Model->pModel ) { - // renderowanie rekurencyjne submodeli - Render_Alpha( - Node->Model->pModel, - Node->Model->Material(), - distancesquared, - Node->pCenter - m_renderpass.camera.position(), - Node->Model->vAngle ); - } - return true; - } - - case GL_LINES: { - if( ( Node->Piece->geometry == null_handle ) - || ( Node->fLineThickness < 0.0 ) ) { - return false; - } - // setup - auto const distance = std::sqrt( distancesquared ); - auto const linealpha = - 10.0 * Node->fLineThickness - / std::max( - 0.5 * Node->m_radius + 1.0, - distance - ( 0.5 * Node->m_radius ) ); - ::glColor4fv( - glm::value_ptr( - glm::vec4( - Node->Diffuse * glm::vec3( Global::DayLight.ambient ), // w zaleznosci od koloru swiatla - std::min( 1.0, linealpha ) ) ) ); - auto const linewidth = clamp( 0.5 * linealpha + Node->fLineThickness * Node->m_radius / 1000.0, 1.0, 8.0 ); - if( linewidth > 1.0 ) { - ::glLineWidth( static_cast(linewidth) ); - } - - GfxRenderer.Bind_Material( null_handle ); - - ::glPushMatrix(); - auto const originoffset = Node->m_rootposition - m_renderpass.camera.position(); - ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); - - // render - m_geometry.draw( Node->Piece->geometry ); - - // post-render cleanup - ::glPopMatrix(); - - if( linewidth > 1.0 ) { ::glLineWidth( 1.0f ); } - - return true; - } - - case GL_TRIANGLES: { - if( ( Node->Piece->geometry == null_handle ) - || ( ( Node->iFlags & 0x20 ) == 0 ) ) { - return false; - } - // setup - ::glColor3fv( glm::value_ptr( Node->Diffuse ) ); - - Bind_Material( Node->m_material ); - - ::glPushMatrix(); - auto const originoffset = Node->m_rootposition - m_renderpass.camera.position(); - ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); - - // render - m_geometry.draw( Node->Piece->geometry ); - - // post-render cleanup - ::glPopMatrix(); - - return true; - } - - default: { break; } + Instance->RaPrepare(); + if( Instance->pModel ) { + // renderowanie rekurencyjne submodeli + Render_Alpha( + Instance->pModel, + Instance->Material(), + distancesquared, + Instance->location() - m_renderpass.camera.position(), + Instance->vAngle ); } - // in theory we shouldn't ever get here but, eh - return false; +} + +void +opengl_renderer::Render_Alpha( TTraction *Traction ) { + + double distancesquared; + switch( m_renderpass.draw_mode ) { + case rendermode::shadows: { + // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees + distancesquared = SquareMagnitude( ( Traction->location() - Global::pCameraPosition ) / Global::ZoomFactor ) / Global::fDistanceFactor; + break; + } + default: { + distancesquared = SquareMagnitude( ( Traction->location() - m_renderpass.camera.position() ) / Global::ZoomFactor ) / Global::fDistanceFactor; + break; + } + } + if( ( distancesquared < Traction->m_rangesquaredmin ) + || ( distancesquared >= Traction->m_rangesquaredmax ) ) { + return; + } + + if( false == Traction->m_visible ) { + return; + } + // rysuj jesli sa druty i nie zerwana + if( ( Traction->Wires == 0 ) + || ( true == TestFlag( Traction->DamageFlag, 128 ) ) ) { + return; + } + // setup +/* + float const linealpha = static_cast( + std::min( + 1.25, + 5000 * Traction->WireThickness / ( distancesquared + 1.0 ) ) ); // zbyt grube nie są dobre + ::glLineWidth( linealpha ); +*/ + auto const distance { static_cast( std::sqrt( distancesquared ) ) }; + auto const linealpha { + 20.f * Traction->WireThickness + / std::max( + 0.5f * Traction->radius() + 1.f, + distance - ( 0.5f * Traction->radius() ) ) }; + ::glLineWidth( + clamp( + 0.5f * linealpha + Traction->WireThickness * Traction->radius() / 1000.f, + 1.f, 1.5f ) ); + // McZapkie-261102: kolor zalezy od materialu i zasniedzenia + ::glColor4fv( + glm::value_ptr( + glm::vec4{ + Traction->wire_color(), + linealpha } ) ); + // render + m_geometry.draw( Traction->m_geometry ); + // debug data + ++m_debugstats.traction; + ++m_debugstats.drawcalls; +} + +void +opengl_renderer::Render_Alpha( scene::lines_node const &Lines ) { + + auto const &data { Lines.data() }; + + double distancesquared; + switch( m_renderpass.draw_mode ) { + case rendermode::shadows: { + // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees + distancesquared = SquareMagnitude( ( data.area.center - Global::pCameraPosition ) / Global::ZoomFactor ) / Global::fDistanceFactor; + break; + } + default: { + distancesquared = SquareMagnitude( ( data.area.center - m_renderpass.camera.position() ) / Global::ZoomFactor ) / Global::fDistanceFactor; + break; + } + } + if( ( distancesquared < data.rangesquared_min ) + || ( distancesquared >= data.rangesquared_max ) ) { + return; + } + // setup + auto const distance { static_cast( std::sqrt( distancesquared ) ) }; + auto const linealpha = ( + data.line_width > 0.f ? + 10.f * data.line_width + / std::max( + 0.5f * data.area.radius + 1.f, + distance - ( 0.5f * data.area.radius ) ) : + 1.f ); // negative width means the lines are always opague + ::glLineWidth( + clamp( + 0.5f * linealpha + data.line_width * data.area.radius / 1000.f, + 1.f, 8.f ) ); + ::glColor4fv( + glm::value_ptr( + glm::vec4{ + glm::vec3{ data.lighting.diffuse * Global::DayLight.ambient }, // w zaleznosci od koloru swiatla + std::min( 1.f, linealpha ) } ) ); + // render + m_geometry.draw( data.geometry ); + ++m_debugstats.lines; + ++m_debugstats.drawcalls; } bool @@ -2673,6 +2854,10 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { && ( TSubModel::fSquareDist >= Submodel->fSquareMinDist ) && ( TSubModel::fSquareDist < Submodel->fSquareMaxDist ) ) { + // debug data + ++m_debugstats.submodels; + ++m_debugstats.drawcalls; + if( Submodel->iFlags & 0xC000 ) { ::glPushMatrix(); if( Submodel->fMatrix ) @@ -2858,6 +3043,8 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { Render_Alpha( Submodel->Next ); }; + + // utility methods TSubModel const * opengl_renderer::Update_Pick_Control() { @@ -2906,7 +3093,7 @@ opengl_renderer::Update_Pick_Control() { return control; } -TGroundNode const * +editor::basic_node const * opengl_renderer::Update_Pick_Node() { #ifdef EU07_USE_PICKING_FRAMEBUFFER @@ -2941,7 +3128,7 @@ opengl_renderer::Update_Pick_Node() { unsigned char pickreadout[4]; ::glReadPixels( pickbufferpos.x, pickbufferpos.y, 1, 1, GL_BGRA, GL_UNSIGNED_BYTE, pickreadout ); auto const nodeindex = pick_index( glm::ivec3{ pickreadout[ 2 ], pickreadout[ 1 ], pickreadout[ 0 ] } ); - TGroundNode const *node { nullptr }; + editor::basic_node const *node { nullptr }; if( ( nodeindex > 0 ) && ( nodeindex <= m_picksceneryitems.size() ) ) { node = m_picksceneryitems[ nodeindex - 1 ]; @@ -2966,10 +3153,10 @@ opengl_renderer::Update( double const Deltatime ) { } m_updateaccumulator = 0.0; - m_framerate = 1000.f / ( m_drawtime / 20.f ); + m_framerate = 1000.f / ( Timer::subsystem.gfx_total.average() ); // adjust draw ranges etc, based on recent performance - auto const framerate = 1000.f / (m_drawtimecolorpass / 20.f); + auto const framerate = 1000.f / Timer::subsystem.gfx_color.average(); float targetfactor; if( framerate > 90.0 ) { targetfactor = 3.0f; } @@ -3006,7 +3193,7 @@ opengl_renderer::Update( double const Deltatime ) { } if( true == DebugModeFlag ) { - m_debuginfo += m_textures.info(); + m_debugtimestext += m_textures.info(); } if( ( true == Global::ControlPicking ) @@ -3025,13 +3212,26 @@ opengl_renderer::Update( double const Deltatime ) { else { m_picksceneryitem = nullptr; } -}; + // dump last opengl error, if any + auto const glerror = ::glGetError(); + if( glerror != GL_NO_ERROR ) { + std::string glerrorstring( ( char * )::gluErrorString( glerror ) ); + win1250_to_ascii( glerrorstring ); + Global::LastGLError = std::to_string( glerror ) + " (" + glerrorstring + ")"; + } +} // debug performance string std::string const & -opengl_renderer::Info() const { +opengl_renderer::info_times() const { - return m_debuginfo; + return m_debugtimestext; +} + +std::string const & +opengl_renderer::info_stats() const { + + return m_debugstatstext; } void diff --git a/renderer.h b/renderer.h index 3d026d07..9797ff21 100644 --- a/renderer.h +++ b/renderer.h @@ -17,10 +17,12 @@ http://mozilla.org/MPL/2.0/. #include "frustum.h" #include "World.h" #include "MemCell.h" +#include "scene.h" #define EU07_USE_PICKING_FRAMEBUFFER //#define EU07_USE_DEBUG_SHADOWMAP //#define EU07_USE_DEBUG_CAMERA +//#define EU07_USE_DEBUG_CULLING struct opengl_light { @@ -84,7 +86,7 @@ public: void update_frustum( glm::mat4 const &Projection, glm::mat4 const &Modelview ); bool - visible( bounding_area const &Area ) const; + visible( scene::bounding_area const &Area ) const; bool visible( TDynamicObject const *Dynamic ) const; inline @@ -190,18 +192,20 @@ public: // utility methods TSubModel const * Pick_Control() const { return m_pickcontrolitem; } - TGroundNode const * + editor::basic_node const * Pick_Node() const { return m_picksceneryitem; } // maintenance jobs void Update( double const Deltatime ); TSubModel const * Update_Pick_Control(); - TGroundNode const * + editor::basic_node const * Update_Pick_Node(); // debug performance string std::string const & - Info() const; + info_times() const; + std::string const & + info_stats() const; // members GLenum static const sunlight{ GL_LIGHT0 }; @@ -225,7 +229,20 @@ private: diffuse }; - typedef std::pair< double, TSubRect * > distancesubcell_pair; + struct debug_stats { + int dynamics { 0 }; + int models { 0 }; + int submodels { 0 }; + int paths { 0 }; + int traction { 0 }; + int shapes { 0 }; + int lines { 0 }; + int drawcalls { 0 }; + }; + + using section_sequence = std::vector; + using distancecell_pair = std::pair; + using cell_sequence = std::vector; struct renderpass_config { @@ -266,14 +283,16 @@ private: Render_reflections(); bool Render( world_environment *Environment ); - bool - Render( TGround *Ground ); - bool - Render( TGroundRect *Groundcell ); - bool - Render( TSubRect *Groundsubcell ); - bool - Render( TGroundNode *Node ); + void + Render( scene::basic_region *Region ); + void + Render( section_sequence::iterator First, section_sequence::iterator Last ); + void + Render( cell_sequence::iterator First, cell_sequence::iterator Last ); + void + Render( scene::shape_node const &Shape, bool const Ignorerange ); + void + Render( TAnimModel *Instance ); bool Render( TDynamicObject *Dynamic ); bool @@ -284,16 +303,22 @@ private: Render( TSubModel *Submodel ); void Render( TTrack *Track ); + void + Render( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last ); bool Render_cab( TDynamicObject *Dynamic, bool const Alpha = false ); void Render( TMemCell *Memcell ); - bool - Render_Alpha( TGround *Ground ); - bool - Render_Alpha( TSubRect *Groundsubcell ); - bool - Render_Alpha( TGroundNode *Node ); + void + Render_Alpha( scene::basic_region *Region ); + void + Render_Alpha( cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last ); + void + Render_Alpha( TAnimModel *Instance ); + void + Render_Alpha( TTraction *Traction ); + void + Render_Alpha( scene::lines_node const &Lines ); bool Render_Alpha( TDynamicObject *Dynamic ); bool @@ -351,28 +376,26 @@ private: units_state m_unitstate; unsigned int m_framestamp; // id of currently rendered gfx frame - float m_drawtime { 1000.f / 30.f * 20.f }; // start with presumed 'neutral' average of 30 fps - std::chrono::steady_clock::time_point m_drawstart; // cached start time of previous frame float m_framerate; - float m_drawtimecolorpass { 1000.f / 30.f * 20.f }; - float m_drawtimeshadowpass { 0.f }; double m_updateaccumulator { 0.0 }; - std::string m_debuginfo; + std::string m_debugtimestext; std::string m_pickdebuginfo; + debug_stats m_debugstats; + std::string m_debugstatstext; glm::vec4 m_baseambient { 0.0f, 0.0f, 0.0f, 1.0f }; - glm::vec4 m_shadowcolor { 0.5f, 0.5f, 0.5f, 1.f }; + glm::vec4 m_shadowcolor { 0.65f, 0.65f, 0.65f, 1.f }; float m_specularopaquescalefactor { 1.f }; float m_speculartranslucentscalefactor { 1.f }; bool m_renderspecular{ false }; // controls whether to include specular component in the calculations renderpass_config m_renderpass; - std::vector m_drawqueue; // list of subcells to be drawn in current render pass - - std::vector m_picksceneryitems; + section_sequence m_sectionqueue; // list of sections in current render pass + cell_sequence m_cellqueue; std::vector m_pickcontrolsitems; TSubModel const *m_pickcontrolitem { nullptr }; - TGroundNode const *m_picksceneryitem { nullptr }; + std::vector m_picksceneryitems; + editor::basic_node const *m_picksceneryitem { nullptr }; #ifdef EU07_USE_DEBUG_CAMERA renderpass_config m_worldcamera; // debug item #endif diff --git a/resource.h b/resource.h index 302a3451..d8e43de2 100644 --- a/resource.h +++ b/resource.h @@ -2,6 +2,7 @@ // Microsoft Visual C++ generated include file. // Used by eu07.rc // +//#define GLFW_ICON 101 // Next default values for new objects // diff --git a/scene.cpp b/scene.cpp new file mode 100644 index 00000000..8dca1429 --- /dev/null +++ b/scene.cpp @@ -0,0 +1,1539 @@ +/* +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/. +*/ + +#include "stdafx.h" +#include "scene.h" + +#include "simulation.h" +#include "Globals.h" +#include "Timer.h" +#include "Logs.h" +#include "sn_utils.h" + +namespace scene { + +std::string const EU07_FILEEXTENSION_REGION { ".sbt" }; + +// legacy method, finds and assigns traction piece to specified pantograph of provided vehicle +void +basic_cell::update_traction( TDynamicObject *Vehicle, int const Pantographindex ) { + // Winger 170204 - szukanie trakcji nad pantografami + auto const vFront = glm::make_vec3( Vehicle->VectorFront().getArray() ); // wektor normalny dla płaszczyzny ruchu pantografu + auto const vUp = glm::make_vec3( Vehicle->VectorUp().getArray() ); // wektor pionu pudła (pochylony od pionu na przechyłce) + auto const vLeft = glm::make_vec3( Vehicle->VectorLeft().getArray() ); // wektor odległości w bok (odchylony od poziomu na przechyłce) + auto const position = glm::dvec3 { Vehicle->GetPosition() }; // współrzędne środka pojazdu + + auto pantograph = Vehicle->pants[ Pantographindex ].fParamPants; + auto const pantographposition = position + ( vLeft * pantograph->vPos.z ) + ( vUp * pantograph->vPos.y ) + ( vFront * pantograph->vPos.x ); + + for( auto *traction : m_directories.traction ) { + + // współczynniki równania parametrycznego + auto const paramfrontdot = glm::dot( traction->vParametric, vFront ); + auto const fRaParam = + -( glm::dot( traction->pPoint1, vFront ) - glm::dot( pantographposition, vFront ) ) + / ( paramfrontdot != 0.0 ? + paramfrontdot : + 0.001 ); // div0 trap + + if( ( fRaParam < -0.001 ) + || ( fRaParam > 1.001 ) ) { continue; } + // jeśli tylko jest w przedziale, wyznaczyć odległość wzdłuż wektorów vUp i vLeft + // punkt styku płaszczyzny z drutem (dla generatora łuku el.) + auto const vStyk = traction->pPoint1 + fRaParam * traction->vParametric; + // wektor musi się mieścić w przedziale ruchu pantografu + auto const vGdzie = vStyk - pantographposition; + auto fVertical = glm::dot( vGdzie, vUp ); + if( fVertical >= 0.0 ) { + // jeśli ponad pantografem (bo może łapać druty spod wiaduktu) + auto const fHorizontal = std::abs( glm::dot( vGdzie, vLeft ) ) - pantograph->fWidth; + + if( ( Global::bEnableTraction ) + && ( fVertical < pantograph->PantWys - 0.15 ) ) { + // 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 + // i do tego jeszcze wejdzie pod ślizg + if( fHorizontal <= 0.0 ) { + // 0.635 dla AKP-1 AKP-4E + pantograph->PantWys = -1.0; // ujemna liczba oznacza połamanie + pantograph->hvPowerWire = nullptr; // bo inaczej się zasila w nieskończoność z połamanego + if( Vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) { + // liczba pantografów teraz będzie mniejsza + --Vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo; + } + if( DebugModeFlag ) { + ErrorLog( "Bad traction: " + Vehicle->name() + " broke pantograph at " + to_string( pantographposition ) ); + } + } + } + else if( fVertical < pantograph->PantTraction ) { + // ale niżej, niż poprzednio znaleziony + if( fHorizontal <= 0.0 ) { + // 0.635 dla AKP-1 AKP-4E + // to się musi mieścić w przedziale zaleznym od szerokości pantografu + pantograph->hvPowerWire = traction; // jakiś znaleziony + pantograph->PantTraction = fVertical; // zapamiętanie nowej wysokości + } + else if( fHorizontal < pantograph->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 + // korekta wysokości o nabieżnik - drut nad nabieżnikiem jest geometrycznie jakby nieco wyżej + fVertical += 0.15 * fHorizontal / pantograph->fWidthExtra; + if( fVertical < pantograph->PantTraction ) { + // gdy po korekcie jest niżej, niż poprzednio znaleziony + // gdyby to wystarczyło, to możemy go uznać + pantograph->hvPowerWire = traction; // może być + pantograph->PantTraction = fVertical; // na razie liniowo na nabieżniku, dokładność poprawi się później + } + } + } + } + } +} + +// legacy method, updates sounds and polls event launchers within radius around specified point +void +basic_cell::update_events() { + + // event launchers + for( auto *launcher : m_eventlaunchers ) { + if( ( true == launcher->check_conditions() ) + && ( SquareMagnitude( launcher->location() - Global::pCameraPosition ) < launcher->dRadius ) ) { + + WriteLog( "Eventlauncher " + launcher->name() ); + if( ( true == Global::shiftState ) + && ( launcher->Event2 != nullptr ) ) { + simulation::Events.AddToQuery( launcher->Event2, nullptr ); + } + else if( launcher->Event1 ) { + simulation::Events.AddToQuery( launcher->Event1, nullptr ); + } + } + } +} + +// legacy method, updates sounds and polls event launchers within radius around specified point +void +basic_cell::update_sounds() { + // TBD, TODO: move to sound renderer + for( auto *path : m_paths ) { + // dźwięki pojazdów, również niewidocznych + path->RenderDynSounds(); + } +} + +// legacy method, triggers radio-stop procedure for all vehicles located on paths in the cell +void +basic_cell::radio_stop() { + + for( auto *path : m_paths ) { + path->RadioStop(); + } +} + +// legacy method, adds specified path to the list of pieces undergoing state change +bool +basic_cell::RaTrackAnimAdd( TTrack *Track ) { + + if( false == m_geometrycreated ) { + // nie ma animacji, gdy nie widać + return true; + } + if (tTrackAnim) + tTrackAnim->RaAnimListAdd(Track); + else + tTrackAnim = Track; + return false; // będzie animowane... +} + +// legacy method, updates geometry for pieces in the animation list +void +basic_cell::RaAnimate( unsigned int const Framestamp ) { + + if( ( tTrackAnim == nullptr ) + || ( Framestamp == m_framestamp ) ) { + // nie ma nic do animowania + return; + } + tTrackAnim = tTrackAnim->RaAnimate(); // przeliczenie animacji kolejnego + + m_framestamp = Framestamp; +} + +// sends content of the class to provided stream +void +basic_cell::serialize( std::ostream &Output ) const { + + // region file version 0, cell data + // bounding area + m_area.serialize( Output ); + // NOTE: cell activation flag is set dynamically on load + // cell shapes + // shape count followed by opaque shape data + sn_utils::ls_uint32( Output, m_shapesopaque.size() ); + for( auto const &shape : m_shapesopaque ) { + shape.serialize( Output ); + } + // shape count followed by translucent shape data + sn_utils::ls_uint32( Output, m_shapestranslucent.size() ); + for( auto const &shape : m_shapestranslucent ) { + shape.serialize( Output ); + } + // cell lines + // line count followed by lines data + sn_utils::ls_uint32( Output, m_lines.size() ); + for( auto const &lines : m_lines ) { + lines.serialize( Output ); + } +} + +// restores content of the class from provided stream +void +basic_cell::deserialize( std::istream &Input ) { + + // region file version 0, cell data + // bounding area + m_area.deserialize( Input ); + // cell shapes + // shape count followed by opaque shape data + auto itemcount { sn_utils::ld_uint32( Input ) }; + while( itemcount-- ) { + m_shapesopaque.emplace_back( shape_node().deserialize( Input ) ); + } + itemcount = sn_utils::ld_uint32( Input ); + while( itemcount-- ) { + m_shapestranslucent.emplace_back( shape_node().deserialize( Input ) ); + } + itemcount = sn_utils::ld_uint32( Input ); + while( itemcount-- ) { + m_lines.emplace_back( lines_node().deserialize( Input ) ); + } + // cell activation flag + m_active = ( + ( false == m_shapesopaque.empty() ) + || ( false == m_shapestranslucent.empty() ) + || ( false == m_lines.empty() ) ); +} + +// adds provided shape to the cell +void +basic_cell::insert( shape_node Shape ) { + + m_active = true; + + // re-calculate cell radius, in case shape geometry extends outside the cell's boundaries + m_area.radius = std::max( + m_area.radius, + glm::length( m_area.center - Shape.data().area.center ) + Shape.data().area.radius ); + + auto const &shapedata { Shape.data() }; + auto &shapes = ( + shapedata.translucent ? + m_shapestranslucent : + m_shapesopaque ); + for( auto &targetshape : shapes ) { + // try to merge shapes with matching view ranges... + auto const &targetshapedata { targetshape.data() }; + if( ( shapedata.rangesquared_min == targetshapedata.rangesquared_min ) + && ( shapedata.rangesquared_max == targetshapedata.rangesquared_max ) + // ...and located close to each other (within arbitrary limit of 25m) + && ( glm::length( shapedata.area.center - targetshapedata.area.center ) < 25.0 ) ) { + + if( true == targetshape.merge( Shape ) ) { + // if the shape was merged there's nothing left to do + return; + } + } + } + // otherwise add the shape to the relevant list + Shape.origin( m_area.center ); + shapes.emplace_back( Shape ); +} + +// adds provided lines to the cell +void +basic_cell::insert( lines_node Lines ) { + + m_active = true; + + auto const &linesdata { Lines.data() }; + for( auto &targetlines : m_lines ) { + // try to merge shapes with matching view ranges... + auto const &targetlinesdata { targetlines.data() }; + if( ( linesdata.rangesquared_min == targetlinesdata.rangesquared_min ) + && ( linesdata.rangesquared_max == targetlinesdata.rangesquared_max ) + // ...and located close to each other (within arbitrary limit of 10m) + && ( glm::length( linesdata.area.center - targetlinesdata.area.center ) < 10.0 ) ) { + + if( true == targetlines.merge( Lines ) ) { + // if the shape was merged there's nothing left to do + return; + } + } + } + // otherwise add the shape to the relevant list + Lines.origin( m_area.center ); + m_lines.emplace_back( Lines ); +} + +// adds provided path to the cell +void +basic_cell::insert( TTrack *Path ) { + + m_active = true; + + Path->origin( m_area.center ); + m_paths.emplace_back( Path ); + // animation hook + Path->RaOwnerSet( this ); + // re-calculate cell radius, in case track extends outside the cell's boundaries + m_area.radius = std::max( + m_area.radius, + static_cast( glm::length( m_area.center - Path->location() ) + Path->radius() + 25.f ) ); // extra margin to prevent driven vehicle from flicking +} + +// adds provided traction piece to the cell +void +basic_cell::insert( TTraction *Traction ) { + + m_active = true; + + Traction->origin( m_area.center ); + m_traction.emplace_back( Traction ); + // re-calculate cell bounding area, in case traction piece extends outside the cell's boundaries + enclose_area( Traction ); +} + +// adds provided model instance to the cell +void +basic_cell::insert( TAnimModel *Instance ) { + + m_active = true; + + auto const flags = Instance->Flags(); + auto alpha = + ( Instance->Material() != nullptr ? + Instance->Material()->textures_alpha : + 0x30300030 ); + + // assign model to appropriate render phases + if( alpha & flags & 0x2F2F002F ) { + // translucent pieces + m_instancetranslucent.emplace_back( Instance ); + } + alpha ^= 0x0F0F000F; // odwrócenie flag tekstur, aby wyłapać nieprzezroczyste + if( alpha & flags & 0x1F1F001F ) { + // opaque pieces + m_instancesopaque.emplace_back( Instance ); + } + // re-calculate cell bounding area, in case model extends outside the cell's boundaries + enclose_area( Instance ); +} + +// adds provided sound instance to the cell +void +basic_cell::insert( sound *Sound ) { + + m_active = true; + + m_sounds.emplace_back( Sound ); +} + +// adds provided sound instance to the cell +void +basic_cell::insert( TEventLauncher *Launcher ) { + + m_active = true; + + m_eventlaunchers.emplace_back( Launcher ); + // re-calculate cell bounding area, in case launcher range extends outside the cell's boundaries + enclose_area( Launcher ); +} + +// registers provided path in the lookup directory of the cell +void +basic_cell::register_end( TTrack *Path ) { + + m_directories.paths.emplace_back( Path ); + // eliminate potential duplicates + m_directories.paths.erase( + std::unique( + std::begin( m_directories.paths ), + std::end( m_directories.paths ) ), + std::end( m_directories.paths ) ); +} + +// registers provided traction piece in the lookup directory of the cell +void +basic_cell::register_end( TTraction *Traction ) { + + m_directories.traction.emplace_back( Traction ); + // eliminate potential duplicates + m_directories.traction.erase( + std::unique( + std::begin( m_directories.traction ), + std::end( m_directories.traction ) ), + std::end( m_directories.traction ) ); +} + +// find a vehicle located nearest to specified point, within specified radius, optionally ignoring vehicles without drivers. reurns: located vehicle and distance +std::tuple +basic_cell::find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) { + + TDynamicObject *vehiclenearest { nullptr }; + float leastdistance { std::numeric_limits::max() }; + float distance; + float const distancecutoff { Radius * Radius }; // we'll ignore vehicles farther than this + + for( auto *path : m_paths ) { + for( auto *vehicle : path->Dynamics ) { + if( ( true == Onlycontrolled ) + && ( vehicle->Mechanik == nullptr ) ) { + continue; + } + if( false == Findbycoupler ) { + // basic search, checks vehicles' center points + distance = glm::length2( glm::dvec3{ vehicle->GetPosition() } - Point ); + } + else { + // alternative search, checks positions of vehicles' couplers + distance = std::min( + glm::length2( glm::dvec3{ vehicle->HeadPosition() } - Point ), + glm::length2( glm::dvec3{ vehicle->RearPosition() } - Point ) ); + } + if( ( distance > distancecutoff ) + || ( distance > leastdistance ) ){ + continue; + } + std::tie( vehiclenearest, leastdistance ) = std::tie( vehicle, distance ); + } + } + return std::tie( vehiclenearest, leastdistance ); +} + +// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint +std::tuple +basic_cell::find( glm::dvec3 const &Point, TTrack const *Exclude ) { + + Math3D::vector3 point { Point.x, Point.y, Point.z }; // sad workaround until math classes unification + int endpointid; + + for( auto *path : m_directories.paths ) { + + if( path == Exclude ) { continue; } + + endpointid = path->TestPoint( &point ); + if( endpointid >= 0 ) { + + return std::tie( path, endpointid ); + } + } + return { nullptr, -1 }; +} + +// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint +std::tuple +basic_cell::find( glm::dvec3 const &Point, TTraction const *Exclude ) { + + int endpointid; + + for( auto *traction : m_directories.traction ) { + + if( traction == Exclude ) { continue; } + + endpointid = traction->TestPoint( Point ); + if( endpointid >= 0 ) { + + return std::tie( traction, endpointid ); + } + } + return { nullptr, -1 }; +} + +// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece +std::tuple +basic_cell::find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ) { + + TTraction + *tractionnearest { nullptr }; + float + distance, + distancenearest { std::numeric_limits::max() }; + int endpoint, + endpointnearest { -1 }; + + for( auto *traction : m_directories.traction ) { + + if( ( traction == Other ) + || ( traction->psSection != Other->psSection ) + || ( traction == Other->hvNext[ 0 ] ) + || ( traction == Other->hvNext[ 1 ] ) ) { + // ignore pieces from different sections, and ones connected to the other piece + continue; + } + endpoint = ( + glm::dot( traction->vParametric, Other->vParametric ) >= 0.0 ? + Currentdirection ^ 1 : + Currentdirection ); + if( ( traction->psPower[ endpoint ] == nullptr ) + || ( traction->fResistance[ endpoint ] < 0.0 ) ) { + continue; + } + distance = glm::length2( traction->location() - Point ); + if( distance < distancenearest ) { + std::tie( tractionnearest, endpointnearest, distancenearest ) = std::tie( traction, endpoint, distance ); + } + } + return { tractionnearest, endpointnearest, distancenearest }; +} + +// sets center point of the section +void +basic_cell::center( glm::dvec3 Center ) { + + m_area.center = Center; + // NOTE: we should also update origin point for the contained nodes, but in practice we can skip this + // as all nodes will be added only after the proper center point was set, and won't change +} + +// generates renderable version of held non-instanced geometry +void +basic_cell::create_geometry( geometrybank_handle const &Bank ) { + + if( false == m_active ) { return; } // nothing to do here + + for( auto &shape : m_shapesopaque ) { shape.create_geometry( Bank ); } + for( auto &shape : m_shapestranslucent ) { shape.create_geometry( Bank ); } + for( auto *path : m_paths ) { path->create_geometry( Bank ); } + for( auto *traction : m_traction ) { traction->create_geometry( Bank ); } + for( auto &lines : m_lines ) { lines.create_geometry( Bank ); } + // arrange content by assigned materials to minimize state switching + std::sort( + std::begin( m_paths ), std::end( m_paths ), + TTrack::sort_by_material ); + + m_geometrycreated = true; // helper for legacy animation code, get rid of it after refactoring +} + +// adjusts cell bounding area to enclose specified node +void +basic_cell::enclose_area( editor::basic_node *Node ) { + + m_area.radius = std::max( + m_area.radius, + static_cast( glm::length( m_area.center - Node->location() ) + Node->radius() ) ); +} + + + +// legacy method, finds and assigns traction piece(s) to pantographs of provided vehicle +void +basic_section::update_traction( TDynamicObject *Vehicle, int const Pantographindex ) { + + auto const vFront = glm::make_vec3( Vehicle->VectorFront().getArray() ); // wektor normalny dla płaszczyzny ruchu pantografu + auto const vUp = glm::make_vec3( Vehicle->VectorUp().getArray() ); // wektor pionu pudła (pochylony od pionu na przechyłce) + auto const vLeft = glm::make_vec3( Vehicle->VectorLeft().getArray() ); // wektor odległości w bok (odchylony od poziomu na przechyłce) + auto const position = glm::dvec3{ Vehicle->GetPosition() }; // współrzędne środka pojazdu + + auto pantograph = Vehicle->pants[ Pantographindex ].fParamPants; + auto const pantographposition = position + ( vLeft * pantograph->vPos.z ) + ( vUp * pantograph->vPos.y ) + ( vFront * pantograph->vPos.x ); + + auto const radius { EU07_CELLSIZE * 0.5 }; // redius around point of interest + + for( auto &cell : m_cells ) { + // we reject early cells which aren't within our area of interest + if( glm::length2( cell.area().center - pantographposition ) < ( ( cell.area().radius + radius ) * ( cell.area().radius + radius ) ) ) { + cell.update_traction( Vehicle, Pantographindex ); + } + } +} + +// legacy method, polls event launchers within radius around specified point +void +basic_section::update_events( glm::dvec3 const &Location, float const Radius ) { + + for( auto &cell : m_cells ) { + + if( glm::length2( cell.area().center - Location ) < ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) { + // we reject cells which aren't within our area of interest + cell.update_events(); + } + } +} + +// legacy method, updates sounds within radius around specified point +void +basic_section::update_sounds( glm::dvec3 const &Location, float const Radius ) { + + for( auto &cell : m_cells ) { + + if( glm::length2( cell.area().center - Location ) < ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) { + // we reject cells which aren't within our area of interest + cell.update_sounds(); + } + } +} + +// legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location +void +basic_section::radio_stop( glm::dvec3 const &Location, float const Radius ) { + + for( auto &cell : m_cells ) { + + if( glm::length2( cell.area().center - Location ) < ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) { + // we reject cells which aren't within our area of interest + cell.radio_stop(); + } + } +} + +// sends content of the class to provided stream +void +basic_section::serialize( std::ostream &Output ) const { + + auto const sectionstartpos { Output.tellp() }; + + // region file version 0, section data + // section size + sn_utils::ls_uint32( Output, 0 ); + // bounding area + m_area.serialize( Output ); + // section shapes: shape count followed by shape data + sn_utils::ls_uint32( Output, m_shapes.size() ); + for( auto const &shape : m_shapes ) { + shape.serialize( Output ); + } + // partitioned data + for( auto const &cell : m_cells ) { + cell.serialize( Output ); + } + // all done; calculate and record section size + auto const sectionendpos { Output.tellp() }; + Output.seekp( sectionstartpos ); + sn_utils::ls_uint32( Output, static_cast( ( sizeof( uint32_t ) + ( sectionendpos - sectionstartpos ) ) ) ); + Output.seekp( sectionendpos ); +} + +// restores content of the class from provided stream +void +basic_section::deserialize( std::istream &Input ) { + + // region file version 0, section data + // bounding area + m_area.deserialize( Input ); + // section shapes: shape count followed by shape data + auto shapecount { sn_utils::ld_uint32( Input ) }; + while( shapecount-- ) { + m_shapes.emplace_back( shape_node().deserialize( Input ) ); + } + // partitioned data + for( auto &cell : m_cells ) { + cell.deserialize( Input ); + } +} + +// adds provided shape to the section +void +basic_section::insert( shape_node Shape ) { + + auto const &shapedata = Shape.data(); + if( ( true == shapedata.translucent ) + || ( shapedata.rangesquared_max <= 90000.0 ) + || ( shapedata.rangesquared_min > 0.0 ) ) { + // small, translucent or not always visible shapes are placed in the sub-cells + cell( shapedata.area.center ).insert( Shape ); + } + else { + // large, opaque shapes are placed on section level + // re-calculate section radius, in case shape geometry extends outside the section's boundaries + m_area.radius = std::max( + m_area.radius, + static_cast( glm::length( m_area.center - Shape.data().area.center ) + Shape.data().area.radius ) ); + + for( auto &shape : m_shapes ) { + // check first if the shape can't be merged with one of the shapes already present in the section + if( true == shape.merge( Shape ) ) { + // if the shape was merged there's nothing left to do + return; + } + } + // otherwise add the shape to the section's list + Shape.origin( m_area.center ); + m_shapes.emplace_back( Shape ); + } +} + +// adds provided lines to the section +void +basic_section::insert( lines_node Lines ) { + + cell( Lines.data().area.center ).insert( Lines ); +} + +// find a vehicle located nearest to specified point, within specified radius, optionally ignoring vehicles without drivers. reurns: located vehicle and distance +std::tuple +basic_section::find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) { + + // go through sections within radius of interest, and pick the nearest candidate + TDynamicObject + *vehiclefound, + *vehiclenearest { nullptr }; + float + distancefound, + distancenearest { std::numeric_limits::max() }; + + for( auto &cell : m_cells ) { + // we reject early cells which aren't within our area of interest + if( glm::length2( cell.area().center - Point ) > ( ( cell.area().radius + Radius ) * ( cell.area().radius + Radius ) ) ) { + continue; + } + std::tie( vehiclefound, distancefound ) = cell.find( Point, Radius, Onlycontrolled, Findbycoupler ); + if( ( vehiclefound != nullptr ) + && ( distancefound < distancenearest ) ) { + + std::tie( vehiclenearest, distancenearest ) = std::tie( vehiclefound, distancefound ); + } + } + return std::tie( vehiclenearest, distancenearest ); +} + +// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint +std::tuple +basic_section::find( glm::dvec3 const &Point, TTrack const *Exclude ) { + + return cell( Point ).find( Point, Exclude ); +} + +// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint +std::tuple +basic_section::find( glm::dvec3 const &Point, TTraction const *Exclude ) { + + return cell( Point ).find( Point, Exclude ); +} + +// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece +std::tuple +basic_section::find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ) { + + // go through sections within radius of interest, and pick the nearest candidate + TTraction + *tractionfound, + *tractionnearest { nullptr }; + float + distancefound, + distancenearest { std::numeric_limits::max() }; + int + endpointfound, + endpointnearest { -1 }; + + auto const radius { 0.0 }; // { EU07_CELLSIZE * 0.5 }; // experimentally limited, check if it has any negative effect + + for( auto &cell : m_cells ) { + // we reject early cells which aren't within our area of interest + if( glm::length2( cell.area().center - Point ) > ( ( cell.area().radius + radius ) * ( cell.area().radius + radius ) ) ) { + continue; + } + std::tie( tractionfound, endpointfound, distancefound ) = cell.find( Point, Other, Currentdirection ); + if( ( tractionfound != nullptr ) + && ( distancefound < distancenearest ) ) { + + std::tie( tractionnearest, endpointnearest, distancenearest ) = std::tie( tractionfound, endpointfound, distancefound ); + } + } + return { tractionnearest, endpointnearest, distancenearest }; +} + +// sets center point of the section +void +basic_section::center( glm::dvec3 Center ) { + + m_area.center = Center; + // set accordingly center points of the section's partitioning cells + // NOTE: we should also update origin point for the contained nodes, but in practice we can skip this + // as all nodes will be added only after the proper center point was set, and won't change + auto const centeroffset = -( EU07_SECTIONSIZE / EU07_CELLSIZE / 2 * EU07_CELLSIZE ) + EU07_CELLSIZE / 2; + glm::dvec3 sectioncornercenter { m_area.center + glm::dvec3{ centeroffset, 0, centeroffset } }; + auto row { 0 }, column { 0 }; + for( auto &cell : m_cells ) { + cell.center( sectioncornercenter + glm::dvec3{ column * EU07_CELLSIZE, 0.0, row * EU07_CELLSIZE } ); + if( ++column >= EU07_SECTIONSIZE / EU07_CELLSIZE ) { + ++row; + column = 0; + } + } +} + +// generates renderable version of held non-instanced geometry +void +basic_section::create_geometry() { + + if( true == m_geometrycreated ) { return; } + else { + // mark it done for future checks + m_geometrycreated = true; + } + + // since sections can be empty, we're doing lazy initialization of the geometry bank, when something may actually use it + if( m_geometrybank == null_handle ) { + m_geometrybank = GfxRenderer.Create_Bank(); + } + + for( auto &shape : m_shapes ) { + shape.create_geometry( m_geometrybank ); + } + for( auto &cell : m_cells ) { + cell.create_geometry( m_geometrybank ); + } +} + +// provides access to section enclosing specified point +basic_cell & +basic_section::cell( glm::dvec3 const &Location ) { + + auto const column = static_cast( std::floor( ( Location.x - ( m_area.center.x - EU07_SECTIONSIZE / 2 ) ) / EU07_CELLSIZE ) ); + auto const row = static_cast( std::floor( ( Location.z - ( m_area.center.z - EU07_SECTIONSIZE / 2 ) ) / EU07_CELLSIZE ) ); + + return + m_cells[ + clamp( row, 0, ( EU07_SECTIONSIZE / EU07_CELLSIZE ) - 1 ) * ( EU07_SECTIONSIZE / EU07_CELLSIZE ) + + clamp( column, 0, ( EU07_SECTIONSIZE / EU07_CELLSIZE ) - 1 ) ] ; +} + + + +basic_region::basic_region() { + + m_sections.fill( nullptr ); +} + +basic_region::~basic_region() { + + for( auto *section : m_sections ) { if( section != nullptr ) { delete section; } } +} + +// legacy method, polls event launchers around camera +void +basic_region::update_events() { + // render events and sounds from sectors near enough to the viewer + auto const range = EU07_SECTIONSIZE; // arbitrary range + auto const §ionlist = sections( Global::pCameraPosition, range ); + for( auto *section : sectionlist ) { + section->update_events( Global::pCameraPosition, range ); + } +} + +// legacy method, updates sounds and polls event launchers around camera +void +basic_region::update_sounds() { + // render events and sounds from sectors near enough to the viewer + auto const range = 2750.f; // audible range of 100 db sound + auto const §ionlist = sections( Global::pCameraPosition, range ); + for( auto *section : sectionlist ) { + section->update_sounds( Global::pCameraPosition, range ); + } +} + +// legacy method, finds and assigns traction piece(s) to pantographs of provided vehicle +void +basic_region::update_traction( TDynamicObject *Vehicle, int const Pantographindex ) { + // TODO: convert vectors to transformation matrix and pass them down the chain along with calculated position + auto const vFront = glm::make_vec3( Vehicle->VectorFront().getArray() ); // wektor normalny dla płaszczyzny ruchu pantografu + auto const vUp = glm::make_vec3( Vehicle->VectorUp().getArray() ); // wektor pionu pudła (pochylony od pionu na przechyłce) + auto const vLeft = glm::make_vec3( Vehicle->VectorLeft().getArray() ); // wektor odległości w bok (odchylony od poziomu na przechyłce) + auto const position = glm::dvec3 { Vehicle->GetPosition() }; // współrzędne środka pojazdu + + auto p = Vehicle->pants[ Pantographindex ].fParamPants; + auto const pant0 = position + ( vLeft * p->vPos.z ) + ( vUp * p->vPos.y ) + ( vFront * p->vPos.x ); + p->PantTraction = std::numeric_limits::max(); // taka za duża wartość + + auto const §ionlist = sections( pant0, EU07_CELLSIZE * 0.5 ); + for( auto *section : sectionlist ) { + section->update_traction( Vehicle, Pantographindex ); + } +} + +// stores content of the class in file with specified name +void +basic_region::serialize( std::string const &Scenariofile ) const { + + auto filename { Global::asCurrentSceneryPath + Scenariofile }; + if( ( filename.rfind( '.' ) != std::string::npos ) + && ( filename.rfind( '.' ) != filename.rfind( ".." ) + 1 ) ) { + // trim extension, it's typically going to be for different file type + filename.erase( filename.rfind( '.' ) ); + } + filename += EU07_FILEEXTENSION_REGION; + + std::ofstream output { filename, std::ios::binary }; + + // region file version 1 + // header: EU07SBT + version (0-255) + sn_utils::ls_uint32( output, MAKE_ID4( 'E', 'U', '0', '7' ) ); + sn_utils::ls_uint32( output, MAKE_ID4( 'S', 'B', 'T', 1 ) ); + // sections + // TBD, TODO: build table of sections and file offsets, if we postpone section loading until they're within range + std::uint32_t sectioncount { 0 }; + for( auto *section : m_sections ) { + if( section != nullptr ) { + ++sectioncount; + } + } + // section count, followed by section data + sn_utils::ls_uint32( output, sectioncount ); + std::uint32_t sectionindex { 0 }; + for( auto *section : m_sections ) { + // section data: section index, followed by length of section data, followed by section data + if( section != nullptr ) { + sn_utils::ls_uint32( output, sectionindex ); + section->serialize( output ); } + ++sectionindex; + } +} + +// restores content of the class from file with specified name. returns: true on success, false otherwise +bool +basic_region::deserialize( std::string const &Scenariofile ) { + + auto filename { Global::asCurrentSceneryPath + Scenariofile }; + if( ( filename.rfind( '.' ) != std::string::npos ) + && ( filename.rfind( '.' ) != filename.rfind( ".." ) + 1 ) ) { + // trim extension, it's typically going to be for different file type + filename.erase( filename.rfind( '.' ) ); + } + filename += EU07_FILEEXTENSION_REGION; + + if( false == FileExists( filename ) ) { + return false; + } + // region file version 1 + // file type and version check + std::ifstream input( filename, std::ios::binary ); + + uint32_t headermain { sn_utils::ld_uint32( input ) }; + uint32_t headertype { sn_utils::ld_uint32( input ) }; + + if( ( headermain != MAKE_ID4( 'E', 'U', '0', '7' ) + || ( headertype != MAKE_ID4( 'S', 'B', 'T', 1 ) ) ) ) { + // wrong file type + WriteLog( "Bad file: \"" + filename + "\" is of either unrecognized type or version" ); + return false; + } + // sections + // TBD, TODO: build table of sections and file offsets, if we postpone section loading until they're within range + // section count + auto sectioncount { sn_utils::ld_uint32( input ) }; + while( sectioncount-- ) { + // section index, followed by section data size, followed by section data + auto *§ion { m_sections[ sn_utils::ld_uint32( input ) ] }; + auto const sectionsize { sn_utils::ld_uint32( input ) }; + section = new basic_section(); + section->deserialize( input ); + } + + return true; +} + +// legacy method, links specified path piece with potential neighbours +void +basic_region::TrackJoin( TTrack *Track ) { + // wyszukiwanie sąsiednich torów do podłączenia (wydzielone na użytek obrotnicy) + TTrack *matchingtrack; + int endpointid; + if( Track->CurrentPrev() == nullptr ) { + std::tie( matchingtrack, endpointid ) = find_path( Track->CurrentSegment()->FastGetPoint_0(), Track ); + switch( endpointid ) { + case 0: + Track->ConnectPrevPrev( matchingtrack, 0 ); + break; + case 1: + Track->ConnectPrevNext( matchingtrack, 1 ); + break; + } + } + if( Track->CurrentNext() == nullptr ) { + std::tie( matchingtrack, endpointid ) = find_path( Track->CurrentSegment()->FastGetPoint_1(), Track ); + switch( endpointid ) { + case 0: + Track->ConnectNextPrev( matchingtrack, 0 ); + break; + case 1: + Track->ConnectNextNext( matchingtrack, 1 ); + break; + } + } +} + +// legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location +void +basic_region::RadioStop( glm::dvec3 const &Location ) { + + auto const range = 2000.f; + auto const §ionlist = sections( Location, range ); + for( auto *section : sectionlist ) { + section->radio_stop( Location, range ); + } +} + +void +basic_region::insert_shape( shape_node Shape, scratch_data &Scratchpad, bool const Transform ) { + + // shape might need to be split into smaller pieces, so we create list of nodes instead of just single one + // using deque so we can do single pass iterating and addding generated pieces without invalidating anything + std::deque shapes { Shape }; + auto &shape = shapes.front(); + if( shape.m_data.vertices.empty() ) { return; } + + // adjust input if necessary: + if( true == Transform ) { + // shapes generated from legacy terrain come with world space coordinates and don't need processing + if( Scratchpad.location.rotation != glm::vec3( 0, 0, 0 ) ) { + // rotate... + auto const rotation = glm::radians( Scratchpad.location.rotation ); + for( auto &vertex : shape.m_data.vertices ) { + vertex.position = glm::rotateZ( vertex.position, rotation.z ); + vertex.position = glm::rotateX( vertex.position, rotation.x ); + vertex.position = glm::rotateY( vertex.position, rotation.y ); + vertex.normal = glm::rotateZ( vertex.normal, rotation.z ); + vertex.normal = glm::rotateX( vertex.normal, rotation.x ); + vertex.normal = glm::rotateY( vertex.normal, rotation.y ); + } + } + if( ( false == Scratchpad.location.offset.empty() ) + && ( Scratchpad.location.offset.top() != glm::dvec3( 0, 0, 0 ) ) ) { + // ...and move + auto const offset = Scratchpad.location.offset.top(); + for( auto &vertex : shape.m_data.vertices ) { + vertex.position += offset; + } + } + // calculate bounding area + for( auto const &vertex : shape.m_data.vertices ) { + shape.m_data.area.center += vertex.position; + } + shape.m_data.area.center /= shape.m_data.vertices.size(); + // trim the shape if needed. trimmed parts will be added to list as separate nodes + for( std::size_t index = 0; index < shapes.size(); ++index ) { + while( true == RaTriangleDivider( shapes[ index ], shapes ) ) { + ; // all work is done during expression check + } + // with the trimming done we can calculate shape's bounding radius + shape.compute_radius(); + } + } + // move the data into appropriate section(s) + for( auto &shape : shapes ) { + + if( point_inside( shape.m_data.area.center ) ) { + // NOTE: nodes placed outside of region boundaries are discarded + section( shape.m_data.area.center ).insert( shape ); + } + else { + ErrorLog( + "Bad scenario: shape node" + ( + shape.m_name.empty() ? + "" : + " \"" + shape.m_name + "\"" ) + + " placed in location outside region bounds (" + to_string( shape.m_data.area.center ) + ")" ); + } + } +} + +// inserts provided lines in the region +void +basic_region::insert_lines( lines_node Lines, scratch_data &Scratchpad ) { + + if( Lines.m_data.vertices.empty() ) { return; } + // transform point coordinates if needed + if( Scratchpad.location.rotation != glm::vec3( 0, 0, 0 ) ) { + // rotate... + auto const rotation = glm::radians( Scratchpad.location.rotation ); + for( auto &vertex : Lines.m_data.vertices ) { + vertex.position = glm::rotateZ( vertex.position, rotation.z ); + vertex.position = glm::rotateX( vertex.position, rotation.x ); + vertex.position = glm::rotateY( vertex.position, rotation.y ); + } + } + if( ( false == Scratchpad.location.offset.empty() ) + && ( Scratchpad.location.offset.top() != glm::dvec3( 0, 0, 0 ) ) ) { + // ...and move + auto const offset = Scratchpad.location.offset.top(); + for( auto &vertex : Lines.m_data.vertices ) { + vertex.position += offset; + } + } + // calculate bounding area + for( auto const &vertex : Lines.m_data.vertices ) { + Lines.m_data.area.center += vertex.position; + } + Lines.m_data.area.center /= Lines.m_data.vertices.size(); + Lines.compute_radius(); + // move the data into appropriate section + if( point_inside( Lines.m_data.area.center ) ) { + // NOTE: nodes placed outside of region boundaries are discarded + section( Lines.m_data.area.center ).insert( Lines ); + } + else { + ErrorLog( + "Bad scenario: lines node" + ( + Lines.m_name.empty() ? + "" : + " \"" + Lines.m_name + "\"" ) + + " placed in location outside region bounds (" + to_string( Lines.m_data.area.center ) + ")" ); + } +} + +// inserts provided track in the region +void +basic_region::insert_path( TTrack *Path, const scratch_data &Scratchpad ) { + + // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done + auto location = Path->location(); + + if( point_inside( location ) ) { + // NOTE: nodes placed outside of region boundaries are discarded + section( location ).insert( Path ); + } + else { + // tracks are guaranteed to hava a name so we can skip the check + ErrorLog( "Bad scenario: track node \"" + Path->name() + "\" placed in location outside region bounds (" + to_string( location ) + ")" ); + } + // also register path ends in appropriate sections, for path merging lookups + // TODO: clean this up during track refactoring + for( auto &point : Path->endpoints() ) { + register_path( Path, point ); + } +} + +// inserts provided track in the region +void +basic_region::insert_traction( TTraction *Traction, scratch_data &Scratchpad ) { + + // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done + auto location = Traction->location(); + + if( point_inside( location ) ) { + // NOTE: nodes placed outside of region boundaries are discarded + section( location ).insert( Traction ); + } + else { + // tracks are guaranteed to hava a name so we can skip the check + ErrorLog( "Bad scenario: traction node \"" + Traction->name() + "\" placed in location outside region bounds (" + to_string( location ) + ")" ); + } + // also register traction ends in appropriate sections, for path merging lookups + // TODO: clean this up during track refactoring + for( auto &point : Traction->endpoints() ) { + register_traction( Traction, point ); + } +} + +// inserts provided instance of 3d model in the region +void +basic_region::insert_instance( TAnimModel *Instance, scratch_data &Scratchpad ) { + + // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done + auto location = Instance->location(); + + if( point_inside( location ) ) { + // NOTE: nodes placed outside of region boundaries are discarded + section( location ).insert( Instance ); + } + else { + // tracks are guaranteed to hava a name so we can skip the check + ErrorLog( "Bad scenario: model node \"" + Instance->name() + "\" placed in location outside region bounds (" + to_string( location ) + ")" ); + } +} + +// inserts provided sound in the region +void +basic_region::insert_sound( sound *Sound, scratch_data &Scratchpad ) { + + // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done + auto location = Sound->location(); + + if( point_inside( location ) ) { + // NOTE: nodes placed outside of region boundaries are discarded + section( location ).insert( Sound ); + } + else { + // tracks are guaranteed to hava a name so we can skip the check + ErrorLog( "Bad scenario: sound node placed in location outside region bounds (" + to_string( location ) + ")" ); + } +} + +// inserts provided event launcher in the region +void +basic_region::insert_launcher( TEventLauncher *Launcher, scratch_data &Scratchpad ) { + + // NOTE: bounding area isn't present/filled until track class and wrapper refactoring is done + auto location = Launcher->location(); + + if( point_inside( location ) ) { + // NOTE: nodes placed outside of region boundaries are discarded + section( location ).insert( Launcher ); + } + else { + // tracks are guaranteed to hava a name so we can skip the check + ErrorLog( "Bad scenario: event launcher \"" + Launcher->name() + "\" placed in location outside region bounds (" + to_string( location ) + ")" ); + } +} + +// find a vehicle located neares to specified location, within specified radius, optionally discarding vehicles without drivers +std::tuple +basic_region::find_vehicle( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) { + + auto const §ionlist = sections( Point, Radius ); + // go through sections within radius of interest, and pick the nearest candidate + TDynamicObject + *foundvehicle, + *nearestvehicle { nullptr }; + float + founddistance, + nearestdistance { std::numeric_limits::max() }; + + for( auto *section : sectionlist ) { + std::tie( foundvehicle, founddistance ) = section->find( Point, Radius, Onlycontrolled, Findbycoupler ); + if( ( foundvehicle != nullptr ) + && ( founddistance < nearestdistance ) ) { + + std::tie( nearestvehicle, nearestdistance ) = std::tie( foundvehicle, founddistance ); + } + } + return std::tie( nearestvehicle, nearestdistance ); +} + +// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint +std::tuple +basic_region::find_path( glm::dvec3 const &Point, TTrack const *Exclude ) { + + // TBD: throw out of bounds exception instead of checks all over the place..? + if( point_inside( Point ) ) { + + return section( Point ).find( Point, Exclude ); + } + + return std::make_tuple( nullptr, -1 ); +} + +// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint +std::tuple +basic_region::find_traction( glm::dvec3 const &Point, TTraction const *Exclude ) { + + // TBD: throw out of bounds exception instead of checks all over the place..? + if( point_inside( Point ) ) { + + return section( Point ).find( Point, Exclude ); + } + + return std::make_tuple( nullptr, -1 ); +} + +// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece +std::tuple +basic_region::find_traction( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ) { + + auto const §ionlist = sections( Point, 0.f ); + // go through sections within radius of interest, and pick the nearest candidate + TTraction + *tractionfound, + *tractionnearest { nullptr }; + float + distancefound, + distancenearest { std::numeric_limits::max() }; + int + endpointfound, + endpointnearest { -1 }; + + for( auto *section : sectionlist ) { + std::tie( tractionfound, endpointfound, distancefound ) = section->find( Point, Other, Currentdirection ); + if( ( tractionfound != nullptr ) + && ( distancefound < distancenearest ) ) { + + std::tie( tractionnearest, endpointnearest, distancenearest ) = std::tie( tractionfound, endpointfound, distancefound ); + } + } + return { tractionnearest, endpointnearest }; +} + +// finds sections inside specified sphere. returns: list of sections +std::vector const & +basic_region::sections( glm::dvec3 const &Point, float const Radius ) { + + m_scratchpad.sections.clear(); + + auto const centerx { static_cast( std::floor( Point.x / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) }; + auto const centerz { static_cast( std::floor( Point.z / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) }; + auto const sectioncount { 2 * static_cast( std::ceil( Radius / EU07_SECTIONSIZE ) ) }; + + int const originx = centerx - sectioncount / 2; + int const originz = centerz - sectioncount / 2; + + auto const padding { 0.0 }; // { EU07_SECTIONSIZE * 0.25 }; // TODO: check if we can get away with padding of 0 + + for( int row = originz; row <= originz + sectioncount; ++row ) { + if( row < 0 ) { continue; } + if( row >= EU07_REGIONSIDESECTIONCOUNT ) { break; } + for( int column = originx; column <= originx + sectioncount; ++column ) { + if( column < 0 ) { continue; } + if( column >= EU07_REGIONSIDESECTIONCOUNT ) { break; } + + auto *section { m_sections[ row * EU07_REGIONSIDESECTIONCOUNT + column ] }; + if( ( section != nullptr ) + && ( glm::length2( section->area().center - Point ) <= ( ( section->area().radius + padding + Radius ) * ( section->area().radius + padding + Radius ) ) ) ) { + + m_scratchpad.sections.emplace_back( section ); + } + } + } + return m_scratchpad.sections; +} + +// registers specified path in the lookup directory of a cell enclosing specified point +void +basic_region::register_path( TTrack *Path, glm::dvec3 const &Point ) { + + if( point_inside( Point ) ) { + section( Point ).register_node( Path, Point ); + } +} + +// registers specified end point of the provided traction piece in the lookup directory of the region +void +basic_region::register_traction( TTraction *Traction, glm::dvec3 const &Point ) { + + if( point_inside( Point ) ) { + section( Point ).register_node( Traction, Point ); + } +} + +// checks whether specified point is within boundaries of the region +bool +basic_region::point_inside( glm::dvec3 const &Location ) { + + double const regionboundary = EU07_REGIONSIDESECTIONCOUNT / 2 * EU07_SECTIONSIZE; + return ( ( Location.x > -regionboundary ) && ( Location.x < regionboundary ) + && ( Location.z > -regionboundary ) && ( Location.z < regionboundary ) ); +} + +// trims provided shape to fit into a section, adds trimmed part at the end of provided list +// NOTE: legacy function. TBD, TODO: clean it up? +bool +basic_region::RaTriangleDivider( shape_node &Shape, std::deque &Shapes ) { + + if( Shape.m_data.vertices.size() != 3 ) { + // tylko gdy jeden trójkąt + return false; + } + + auto const margin { 200.0 }; + auto x0 = EU07_SECTIONSIZE * std::floor( 0.001 * Shape.m_data.area.center.x ) - margin; + auto x1 = x0 + EU07_SECTIONSIZE + margin * 2; + auto z0 = EU07_SECTIONSIZE * std::floor( 0.001 * Shape.m_data.area.center.z ) - margin; + auto z1 = z0 + EU07_SECTIONSIZE + margin * 2; + + if( ( Shape.m_data.vertices[ 0 ].position.x >= x0 ) && ( Shape.m_data.vertices[ 0 ].position.x <= x1 ) + && ( Shape.m_data.vertices[ 0 ].position.z >= z0 ) && ( Shape.m_data.vertices[ 0 ].position.z <= z1 ) + && ( Shape.m_data.vertices[ 1 ].position.x >= x0 ) && ( Shape.m_data.vertices[ 1 ].position.x <= x1 ) + && ( Shape.m_data.vertices[ 1 ].position.z >= z0 ) && ( Shape.m_data.vertices[ 1 ].position.z <= z1 ) + && ( Shape.m_data.vertices[ 2 ].position.x >= x0 ) && ( Shape.m_data.vertices[ 2 ].position.x <= x1 ) + && ( Shape.m_data.vertices[ 2 ].position.z >= z0 ) && ( Shape.m_data.vertices[ 2 ].position.z <= z1 ) ) { + // trójkąt wystający mniej niż 200m z kw. kilometrowego jest do przyjęcia + return false; + } + // 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.0 }, + mul; // jeśli przechodzi przez oś, iloczyn będzie ujemny + x0 += margin; + x1 -= margin; // przestawienie na siatkę + z0 += margin; + z1 -= margin; + // AB na wschodzie + mul = ( Shape.m_data.vertices[ 0 ].position.x - x0 ) * ( Shape.m_data.vertices[ 1 ].position.x - x0 ); + if( mul < min ) { + min = mul; + divide = 0; + } + // BC na wschodzie + mul = ( Shape.m_data.vertices[ 1 ].position.x - x0 ) * ( Shape.m_data.vertices[ 2 ].position.x - x0 ); + if( mul < min ) { + min = mul; + divide = 1; + } + // CA na wschodzie + mul = ( Shape.m_data.vertices[ 2 ].position.x - x0 ) * ( Shape.m_data.vertices[ 0 ].position.x - x0 ); + if( mul < min ) { + min = mul; + divide = 2; + } + // AB na zachodzie + mul = ( Shape.m_data.vertices[ 0 ].position.x - x1 ) * ( Shape.m_data.vertices[ 1 ].position.x - x1 ); + if( mul < min ) { + min = mul; + divide = 8; + } + // BC na zachodzie + mul = ( Shape.m_data.vertices[ 1 ].position.x - x1 ) * ( Shape.m_data.vertices[ 2 ].position.x - x1 ); + if( mul < min ) { + min = mul; + divide = 9; + } + // CA na zachodzie + mul = ( Shape.m_data.vertices[ 2 ].position.x - x1 ) * ( Shape.m_data.vertices[ 0 ].position.x - x1 ); + if( mul < min ) { + min = mul; + divide = 10; + } + // AB na południu + mul = ( Shape.m_data.vertices[ 0 ].position.z - z0 ) * ( Shape.m_data.vertices[ 1 ].position.z - z0 ); + if( mul < min ) { + min = mul; + divide = 4; + } + // BC na południu + mul = ( Shape.m_data.vertices[ 1 ].position.z - z0 ) * ( Shape.m_data.vertices[ 2 ].position.z - z0 ); + if( mul < min ) { + min = mul; + divide = 5; + } + // CA na południu + mul = ( Shape.m_data.vertices[ 2 ].position.z - z0 ) * ( Shape.m_data.vertices[ 0 ].position.z - z0 ); + if( mul < min ) { + min = mul; + divide = 6; + } + // AB na północy + mul = ( Shape.m_data.vertices[ 0 ].position.z - z1 ) * ( Shape.m_data.vertices[ 1 ].position.z - z1 ); + if( mul < min ) { + min = mul; + divide = 12; + } + // BC na północy + mul = ( Shape.m_data.vertices[ 1 ].position.z - z1 ) * ( Shape.m_data.vertices[ 2 ].position.z - z1 ); + if( mul < min ) { + min = mul; + divide = 13; + } + // CA na północy + mul = (Shape.m_data.vertices[2].position.z - z1) * (Shape.m_data.vertices[0].position.z - z1); + if( mul < min ) { + divide = 14; + } + + // tworzymy jeden dodatkowy trójkąt, dzieląc jeden bok na przecięciu siatki kilometrowej + Shapes.emplace_back( Shape ); // copy current shape + auto &newshape = Shapes.back(); + + switch (divide & 3) { + // podzielenie jednego z boków, powstaje wierzchołek D + case 0: { + // podział AB (0-1) -> ADC i DBC + newshape.m_data.vertices[ 2 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek C jest wspólny + newshape.m_data.vertices[ 1 ] = Shape.m_data.vertices[ 1 ]; // wierzchołek B przechodzi do nowego + if( divide & 4 ) { + Shape.m_data.vertices[ 1 ].set_from_z( + Shape.m_data.vertices[ 0 ], + Shape.m_data.vertices[ 1 ], + ( ( divide & 8 ) ? + z1 : + z0 ) ); + } + else { + Shape.m_data.vertices[ 1 ].set_from_x( + Shape.m_data.vertices[ 0 ], + Shape.m_data.vertices[ 1 ], + ( ( divide & 8 ) ? + x1 : + x0 ) ); + } + newshape.m_data.vertices[ 0 ] = Shape.m_data.vertices[ 1 ]; // wierzchołek D jest wspólny + break; + } + case 1: { + // podział BC (1-2) -> ABD i ADC + newshape.m_data.vertices[ 0 ] = Shape.m_data.vertices[ 0 ]; // wierzchołek A jest wspólny + newshape.m_data.vertices[ 2 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek C przechodzi do nowego + if( divide & 4 ) { + Shape.m_data.vertices[ 2 ].set_from_z( + Shape.m_data.vertices[ 1 ], + Shape.m_data.vertices[ 2 ], + ( ( divide & 8 ) ? + z1 : + z0 ) ); + } + else { + Shape.m_data.vertices[ 2 ].set_from_x( + Shape.m_data.vertices[ 1 ], + Shape.m_data.vertices[ 2 ], + ( ( divide & 8 ) ? + x1 : + x0 ) ); + } + newshape.m_data.vertices[ 1 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek D jest wspólny + break; + } + case 2: { + // podział CA (2-0) -> ABD i DBC + newshape.m_data.vertices[ 1 ] = Shape.m_data.vertices[ 1 ]; // wierzchołek B jest wspólny + newshape.m_data.vertices[ 2 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek C przechodzi do nowego + if( divide & 4 ) { + Shape.m_data.vertices[ 2 ].set_from_z( + Shape.m_data.vertices[ 2 ], + Shape.m_data.vertices[ 0 ], + ( ( divide & 8 ) ? + z1 : + z0 ) ); + } + else { + Shape.m_data.vertices[ 2 ].set_from_x( + Shape.m_data.vertices[ 2 ], + Shape.m_data.vertices[ 0 ], + ( ( divide & 8 ) ? + x1 : + x0 ) ); + } + newshape.m_data.vertices[ 0 ] = Shape.m_data.vertices[ 2 ]; // wierzchołek D jest wspólny + break; + } + } + // przeliczenie środków ciężkości obu + Shape.m_data.area.center = ( Shape.m_data.vertices[ 0 ].position + Shape.m_data.vertices[ 1 ].position + Shape.m_data.vertices[ 2 ].position ) / 3.0; + newshape.m_data.area.center = ( newshape.m_data.vertices[ 0 ].position + newshape.m_data.vertices[ 1 ].position + newshape.m_data.vertices[ 2 ].position ) / 3.0; + + return true; +} + +// provides access to section enclosing specified point +basic_section & +basic_region::section( glm::dvec3 const &Location ) { + + auto const column { static_cast( std::floor( Location.x / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) }; + auto const row { static_cast( std::floor( Location.z / EU07_SECTIONSIZE + EU07_REGIONSIDESECTIONCOUNT / 2 ) ) }; + + auto §ion = + m_sections[ + clamp( row, 0, EU07_REGIONSIDESECTIONCOUNT - 1 ) * EU07_REGIONSIDESECTIONCOUNT + + clamp( column, 0, EU07_REGIONSIDESECTIONCOUNT - 1 ) ] ; + + if( section == nullptr ) { + // there's no guarantee the section exists at this point, so check and if needed, create it + section = new basic_section(); + // assign center of the section + auto const centeroffset = -( EU07_REGIONSIDESECTIONCOUNT / 2 * EU07_SECTIONSIZE ) + EU07_SECTIONSIZE / 2; + glm::dvec3 regioncornercenter { centeroffset, 0, centeroffset }; + section->center( regioncornercenter + glm::dvec3{ column * EU07_SECTIONSIZE, 0.0, row * EU07_SECTIONSIZE } ); + } + + return *section; +} + +} // scene + +//--------------------------------------------------------------------------- diff --git a/scene.h b/scene.h new file mode 100644 index 00000000..33c93377 --- /dev/null +++ b/scene.h @@ -0,0 +1,368 @@ +/* +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/. +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include "parser.h" +#include "openglgeometrybank.h" +#include "scenenode.h" +#include "Track.h" +#include "Traction.h" + +class opengl_renderer; +namespace scene { + +int const EU07_CELLSIZE = 250; +int const EU07_SECTIONSIZE = 1000; +int const EU07_REGIONSIDESECTIONCOUNT = 500; // number of sections along a side of square region + +struct scratch_data { + + struct binary_data { + + bool terrain{ false }; + } binary; + + struct location_data { + + std::stack offset; + glm::vec3 rotation; + } location; + + struct trainset_data { + + std::string name; + std::string track; + float offset { 0.f }; + float velocity { 0.f }; + std::vector vehicles; + std::vector couplings; + TDynamicObject * driver { nullptr }; + bool is_open { false }; + } trainset; + + bool initialized { false }; +}; + +// basic element of rudimentary partitioning scheme for the section. fixed size, no further subdivision +// TBD, TODO: replace with quadtree scheme? +class basic_cell { + + friend class ::opengl_renderer; + +public: +// methods + // legacy method, finds and assigns traction piece to specified pantograph of provided vehicle + void + update_traction( TDynamicObject *Vehicle, int const Pantographindex ); + // legacy method, polls event launchers within radius around specified point + void + update_events(); + // legacy method, updates sounds within radius around specified point + void + update_sounds(); + // legacy method, triggers radio-stop procedure for all vehicles located on paths in the cell + void + radio_stop(); + // legacy method, adds specified path to the list of pieces undergoing state change + bool + RaTrackAnimAdd( TTrack *Track ); + // legacy method, updates geometry for pieces in the animation list + void + RaAnimate( unsigned int const Framestamp ); + // sends content of the class to provided stream + void + serialize( std::ostream &Output ) const; + // restores content of the class from provided stream + void + deserialize( std::istream &Input ); + // adds provided shape to the cell + void + insert( shape_node Shape ); + // adds provided lines to the cell + void + insert( lines_node Lines ); + // adds provided path to the cell + void + insert( TTrack *Path ); + // adds provided path to the cell + void + insert( TTraction *Traction ); + // adds provided model instance to the cell + void + insert( TAnimModel *Instance ); + // adds provided sound instance to the cell + void + insert( sound *Sound ); + // adds provided event launcher to the cell + void + insert( TEventLauncher *Launcher ); + // registers provided path in the lookup directory of the cell + void + register_end( TTrack *Path ); + // registers provided traction piece in the lookup directory of the cell + void + register_end( TTraction *Traction ); + // find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance + std::tuple + find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ); + // finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint + std::tuple + find( glm::dvec3 const &Point, TTrack const *Exclude ); + // finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint + std::tuple + find( glm::dvec3 const &Point, TTraction const *Exclude ); + // finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece + std::tuple + find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ); + // sets center point of the cell + void + center( glm::dvec3 Center ); + // generates renderable version of held non-instanced geometry in specified geometry bank + void + create_geometry( geometrybank_handle const &Bank ); + // provides access to bounding area data + bounding_area const & + area() const { + return m_area; } +private: +// types + using path_sequence = std::vector; + using shapenode_sequence = std::vector; + using linesnode_sequence = std::vector; + using traction_sequence = std::vector; + using instance_sequence = std::vector; + using sound_sequence = std::vector; + using eventlauncher_sequence = std::vector; +// methods + void + enclose_area( editor::basic_node *Node ); +// members + scene::bounding_area m_area { glm::dvec3(), static_cast( 0.5 * M_SQRT2 * EU07_CELLSIZE ) }; + bool m_active { false }; // whether the cell holds any actual data + // content + shapenode_sequence m_shapesopaque; // opaque pieces of geometry + shapenode_sequence m_shapestranslucent; // translucent pieces of geometry + linesnode_sequence m_lines; + path_sequence m_paths; // path pieces + instance_sequence m_instancesopaque; + instance_sequence m_instancetranslucent; + traction_sequence m_traction; + sound_sequence m_sounds; + eventlauncher_sequence m_eventlaunchers; + // search helpers + struct lookup_data { + path_sequence paths; + traction_sequence traction; + } m_directories; + // animation of owned items (legacy code, clean up along with track refactoring) + bool m_geometrycreated { false }; + unsigned int m_framestamp { 0 }; // id of last rendered gfx frame + TTrack *tTrackAnim = nullptr; // obiekty do przeliczenia animacji +}; + +// basic scene partitioning structure, holds terrain geometry and collection of cells +class basic_section { + + friend class ::opengl_renderer; + +public: +// methods +// legacy method, finds and assigns traction piece to specified pantograph of provided vehicle + void + update_traction( TDynamicObject *Vehicle, int const Pantographindex ); + // legacy method, updates sounds and polls event launchers within radius around specified point + void + update_events( glm::dvec3 const &Location, float const Radius ); + // legacy method, updates sounds and polls event launchers within radius around specified point + void + update_sounds( glm::dvec3 const &Location, float const Radius ); + // legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location + void + radio_stop( glm::dvec3 const &Location, float const Radius ); + // sends content of the class to provided stream + void + serialize( std::ostream &Output ) const; + // restores content of the class from provided stream + void + deserialize( std::istream &Input ); + // adds provided shape to the section + void + insert( shape_node Shape ); + // adds provided lines to the section + void + insert( lines_node Lines ); + // adds provided node to the section + template + void + insert( Type_ *Node ) { + auto &targetcell { cell( Node->location() ) }; + targetcell.insert( Node ); + // some node types can extend bounding area of the target cell + m_area.radius = std::max( + m_area.radius, + static_cast( glm::length( m_area.center - targetcell.area().center ) + targetcell.area().radius ) ); } + // registers provided node in the lookup directory of the section enclosing specified point + template + void + register_node( Type_ *Node, glm::dvec3 const &Point ) { + cell( Point ).register_end( Node ); } + // find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance + std::tuple + find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ); + // finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint + std::tuple + find( glm::dvec3 const &Point, TTrack const *Exclude ); + // finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint + std::tuple + find( glm::dvec3 const &Point, TTraction const *Exclude ); + // finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece + std::tuple + find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ); + // sets center point of the section + void + center( glm::dvec3 Center ); + // generates renderable version of held non-instanced geometry + void + create_geometry(); + // provides access to bounding area data + bounding_area const & + area() const { + return m_area; } + +private: +// types + using cell_array = std::array; + using shapenode_sequence = std::vector; +// methods + // provides access to section enclosing specified point + basic_cell & + cell( glm::dvec3 const &Location ); +// members + // placement and visibility + scene::bounding_area m_area { glm::dvec3(), static_cast( 0.5 * M_SQRT2 * EU07_SECTIONSIZE ) }; + // content + cell_array m_cells; // partitioning scheme + shapenode_sequence m_shapes; // large pieces of opaque geometry and (legacy) terrain + // TODO: implement dedicated, higher fidelity, fixed resolution terrain mesh item + // gfx renderer data + geometrybank_handle m_geometrybank; + bool m_geometrycreated { false }; +}; + +// top-level of scene spatial structure, holds collection of sections +class basic_region { + + friend class ::opengl_renderer; + +public: +// constructors + basic_region(); +// destructor + ~basic_region(); +// methods +// legacy method, finds and assigns traction piece to specified pantograph of provided vehicle + void + update_traction( TDynamicObject *Vehicle, int const Pantographindex ); + // legacy method, polls event launchers around camera + void + update_events(); + // legacy method, updates sounds around camera + void + update_sounds(); + // stores content of the class in file with specified name + void + serialize( std::string const &Scenariofile ) const; + // restores content of the class from file with specified name. returns: true on success, false otherwise + bool + deserialize( std::string const &Scenariofile ); + // legacy method, links specified path piece with potential neighbours + void + TrackJoin( TTrack *Track ); + // legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location + void + RadioStop( glm::dvec3 const &Location ); + // inserts provided shape in the region + void + insert_shape( shape_node Shape, scratch_data &Scratchpad, bool const Transform ); + // inserts provided lines in the region + void + insert_lines( lines_node Lines, scratch_data &Scratchpad ); + // inserts provided track in the region + void + insert_path( TTrack *Path, const scratch_data &Scratchpad ); + // inserts provided track in the region + void + insert_traction( TTraction *Traction, scratch_data &Scratchpad ); + // inserts provided instance of 3d model in the region + void + insert_instance( TAnimModel *Instance, scratch_data &Scratchpad ); + // inserts provided sound in the region + void + insert_sound( sound *Sound, scratch_data &Scratchpad ); + // inserts provided event launcher in the region + void + insert_launcher( TEventLauncher *Launcher, scratch_data &Scratchpad ); + // find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance + std::tuple + find_vehicle( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ); + // finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint + std::tuple + find_path( glm::dvec3 const &Point, TTrack const *Exclude ); + // finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint + std::tuple + find_traction( glm::dvec3 const &Point, TTraction const *Exclude ); + // finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece + std::tuple + find_traction( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ); + // finds sections inside specified sphere. returns: list of sections + std::vector const & + sections( glm::dvec3 const &Point, float const Radius ); + +private: +// types + using section_array = std::array; + + struct region_scratchpad { + + std::vector sections; + }; + +// methods + // registers specified end point of the provided path in the lookup directory of the region + void + register_path( TTrack *Path, glm::dvec3 const &Point ); + // registers specified end point of the provided traction piece in the lookup directory of the region + void + register_traction( TTraction *Traction, glm::dvec3 const &Point ); + // checks whether specified point is within boundaries of the region + bool + point_inside( glm::dvec3 const &Location ); + // legacy method, trims provided shape to fit into a section. adds trimmed part at the end of provided list, returns true if changes were made + bool + RaTriangleDivider( shape_node &Shape, std::deque &Shapes ); + // provides access to section enclosing specified point + basic_section & + section( glm::dvec3 const &Location ); + +// members + section_array m_sections; + region_scratchpad m_scratchpad; + +}; + +} // scene + +//--------------------------------------------------------------------------- diff --git a/scenenode.cpp b/scenenode.cpp new file mode 100644 index 00000000..ece55c66 --- /dev/null +++ b/scenenode.cpp @@ -0,0 +1,718 @@ +/* +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/. +*/ + +#include "stdafx.h" +#include "scenenode.h" + +#include "renderer.h" +#include "Logs.h" +#include "sn_utils.h" + +// stores content of the struct in provided output stream +void +lighting_data::serialize( std::ostream &Output ) const { + + sn_utils::s_vec4( Output, diffuse ); + sn_utils::s_vec4( Output, ambient ); + sn_utils::s_vec4( Output, specular ); +} + +// restores content of the struct from provided input stream +void +lighting_data::deserialize( std::istream &Input ) { + + diffuse = sn_utils::d_vec4( Input ); + ambient = sn_utils::d_vec4( Input ); + specular = sn_utils::d_vec4( Input ); +} + +namespace scene { + +// stores content of the struct in provided output stream +void +bounding_area::serialize( std::ostream &Output ) const { + // center + sn_utils::s_dvec3( Output, center ); + // radius + sn_utils::ls_float32( Output, radius ); +} + +// restores content of the struct from provided input stream +void +bounding_area::deserialize( std::istream &Input ) { + + center = sn_utils::d_dvec3( Input ); + radius = sn_utils::ld_float32( Input ); +} + + + +// sends content of the struct to provided stream +void +shape_node::shapenode_data::serialize( std::ostream &Output ) const { + // bounding area + area.serialize( Output ); + // visibility + sn_utils::ls_float64( Output, rangesquared_min ); + sn_utils::ls_float64( Output, rangesquared_max ); + sn_utils::s_bool( Output, visible ); + // material + sn_utils::s_bool( Output, translucent ); + // NOTE: material handle is created dynamically on load + sn_utils::s_str( + Output, + ( material != null_handle ? + GfxRenderer.Material( material ).name : + "" ) ); + lighting.serialize( Output ); + // geometry + sn_utils::s_dvec3( Output, origin ); + // NOTE: geometry handle is created dynamically on load + // vertex count, followed by vertex data + sn_utils::ls_uint32( Output, vertices.size() ); + for( auto const &vertex : vertices ) { + basic_vertex( + glm::vec3{ vertex.position - origin }, + vertex.normal, + vertex.texture ) + .serialize( Output ); + } +} + +// restores content of the struct from provided input stream +void +shape_node::shapenode_data::deserialize( std::istream &Input ) { + // bounding area + area.deserialize( Input ); + // visibility + rangesquared_min = sn_utils::ld_float64( Input ); + rangesquared_max = sn_utils::ld_float64( Input ); + visible = sn_utils::d_bool( Input ); + // material + translucent = sn_utils::d_bool( Input ); + auto const materialname { sn_utils::d_str( Input ) }; + if( false == materialname.empty() ) { + material = GfxRenderer.Fetch_Material( materialname ); + } + lighting.deserialize( Input ); + // geometry + origin = sn_utils::d_dvec3( Input ); + // NOTE: geometry handle is acquired during geometry creation + // vertex data + vertices.resize( sn_utils::ld_uint32( Input ) ); + basic_vertex localvertex; + for( auto &vertex : vertices ) { + localvertex.deserialize( Input ); + vertex.position = origin + glm::dvec3{ localvertex.position }; + vertex.normal = localvertex.normal; + vertex.texture = localvertex.texture; + } +} + + +// sends content of the class to provided stream +void +shape_node::serialize( std::ostream &Output ) const { + // name + sn_utils::s_str( Output, m_name ); + // node data + m_data.serialize( Output ); +} + +// restores content of the node from provided input stream +shape_node & +shape_node::deserialize( std::istream &Input ) { + // name + m_name = sn_utils::d_str( Input ); + // node data + m_data.deserialize( Input ); + + return *this; +} + +// restores content of the node from provided input stream +shape_node & +shape_node::deserialize( cParser &Input, scene::node_data const &Nodedata ) { + + // import common data + m_name = Nodedata.name; + m_data.rangesquared_min = Nodedata.range_min * Nodedata.range_min; + m_data.rangesquared_max = ( + Nodedata.range_max >= 0.0 ? + Nodedata.range_max * Nodedata.range_max : + std::numeric_limits::max() ); + + std::string token = Input.getToken(); + if( token == "material" ) { + // lighting settings + token = Input.getToken(); + while( token != "endmaterial" ) { + + if( token == "ambient:" ) { + Input.getTokens( 3 ); + Input + >> m_data.lighting.ambient.r + >> m_data.lighting.ambient.g + >> m_data.lighting.ambient.b; + m_data.lighting.ambient /= 255.f; + m_data.lighting.ambient.a = 1.f; + } + else if( token == "diffuse:" ) { + Input.getTokens( 3 ); + Input + >> m_data.lighting.diffuse.r + >> m_data.lighting.diffuse.g + >> m_data.lighting.diffuse.b; + m_data.lighting.diffuse /= 255.f; + m_data.lighting.diffuse.a = 1.f; + } + else if( token == "specular:" ) { + Input.getTokens( 3 ); + Input + >> m_data.lighting.specular.r + >> m_data.lighting.specular.g + >> m_data.lighting.specular.b; + m_data.lighting.specular /= 255.f; + m_data.lighting.specular.a = 1.f; + } + token = Input.getToken(); + } + token = Input.getToken(); + } + + // assigned material + m_data.material = GfxRenderer.Fetch_Material( token ); + + // determine way to proceed from the assigned diffuse texture + // TBT, TODO: add methods to material manager to access these simpler + auto const texturehandle = ( + m_data.material != null_handle ? + GfxRenderer.Material( m_data.material ).texture1 : + null_handle ); + auto const &texture = ( + texturehandle ? + GfxRenderer.Texture( texturehandle ) : + opengl_texture() ); // dirty workaround for lack of better api + bool const clamps = ( + texturehandle ? + texture.traits.find( 's' ) != std::string::npos : + false ); + bool const clampt = ( + texturehandle ? + texture.traits.find( 't' ) != std::string::npos : + false ); + + // remainder of legacy 'problend' system -- geometry assigned a texture with '@' in its name is treated as translucent, opaque otherwise + if( texturehandle != null_handle ) { + m_data.translucent = ( + ( ( texture.name.find( '@' ) != std::string::npos ) + && ( true == texture.has_alpha ) ) ? + true : + false ); + } + else { + m_data.translucent = false; + } + + // geometry + enum subtype { + triangles, + triangle_strip, + triangle_fan + }; + + subtype const nodetype = ( + Nodedata.type == "triangles" ? triangles : + Nodedata.type == "triangle_strip" ? triangle_strip : + triangle_fan ); + std::size_t vertexcount{ 0 }; + world_vertex vertex, vertex1, vertex2; + do { + Input.getTokens( 8, false ); + Input + >> vertex.position.x + >> vertex.position.y + >> vertex.position.z + >> vertex.normal.x + >> vertex.normal.y + >> vertex.normal.z + >> vertex.texture.s + >> vertex.texture.t; + // clamp texture coordinates if texture wrapping is off + if( true == clamps ) { vertex.texture.s = clamp( vertex.texture.s, 0.001f, 0.999f ); } + if( true == clampt ) { vertex.texture.t = clamp( vertex.texture.t, 0.001f, 0.999f ); } + // convert all data to gl_triangles to allow data merge for matching nodes + switch( nodetype ) { + case triangles: { + + if( vertexcount == 0 ) { vertex1 = vertex; } + else if( vertexcount == 1 ) { vertex2 = vertex; } + else if( vertexcount >= 2 ) { + if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { + m_data.vertices.emplace_back( vertex1 ); + m_data.vertices.emplace_back( vertex2 ); + m_data.vertices.emplace_back( vertex ); + } + else { + ErrorLog( + "Bad geometry: degenerate triangle encountered" + + ( m_name != "" ? " in node \"" + m_name + "\"" : "" ) + + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); + } + } + ++vertexcount; + if( vertexcount > 2 ) { vertexcount = 0; } // start new triangle if needed + break; + } + case triangle_fan: { + + if( vertexcount == 0 ) { vertex1 = vertex; } + else if( vertexcount == 1 ) { vertex2 = vertex; } + else if( vertexcount >= 2 ) { + if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { + m_data.vertices.emplace_back( vertex1 ); + m_data.vertices.emplace_back( vertex2 ); + m_data.vertices.emplace_back( vertex ); + vertex2 = vertex; + } + else { + ErrorLog( + "Bad geometry: degenerate triangle encountered" + + ( m_name != "" ? " in node \"" + m_name + "\"" : "" ) + + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); + } + } + ++vertexcount; + break; + } + case triangle_strip: { + + if( vertexcount == 0 ) { vertex1 = vertex; } + else if( vertexcount == 1 ) { vertex2 = vertex; } + else if( vertexcount >= 2 ) { + if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { + // swap order every other triangle, to maintain consistent winding + if( vertexcount % 2 == 0 ) { + m_data.vertices.emplace_back( vertex1 ); + m_data.vertices.emplace_back( vertex2 ); + } + else { + m_data.vertices.emplace_back( vertex2 ); + m_data.vertices.emplace_back( vertex1 ); + } + m_data.vertices.emplace_back( vertex ); + + vertex1 = vertex2; + vertex2 = vertex; + } + else { + ErrorLog( + "Bad geometry: degenerate triangle encountered" + + ( m_name != "" ? " in node \"" + m_name + "\"" : "" ) + + " (vertices: " + to_string( vertex1.position ) + " + " + to_string( vertex2.position ) + " + " + to_string( vertex.position ) + ")" ); + } + } + ++vertexcount; + break; + } + default: { break; } + } + token = Input.getToken(); + + } while( token != "endtri" ); + + return *this; +} + +// imports data from provided submodel +shape_node & +shape_node::convert( TSubModel const *Submodel ) { + + m_name = Submodel->pName; + m_data.lighting.ambient = Submodel->f4Ambient; + m_data.lighting.diffuse = Submodel->f4Diffuse; + m_data.lighting.specular = Submodel->f4Specular; + m_data.material = Submodel->m_material; + m_data.translucent = ( true == GfxRenderer.Material( m_data.material ).has_alpha ); + // NOTE: we set unlimited view range typical for terrain, because we don't expect to convert any other 3d models + m_data.rangesquared_max = std::numeric_limits::max(); + + if( Submodel->m_geometry == null_handle ) { return *this; } + + int vertexcount { 0 }; + std::vector importedvertices; + world_vertex vertex, vertex1, vertex2; + for( auto const &sourcevertex : GfxRenderer.Vertices( Submodel->m_geometry ) ) { + vertex.position = sourcevertex.position; + vertex.normal = sourcevertex.normal; + vertex.texture = sourcevertex.texture; + if( vertexcount == 0 ) { vertex1 = vertex; } + else if( vertexcount == 1 ) { vertex2 = vertex; } + else if( vertexcount >= 2 ) { + if( false == degenerate( vertex1.position, vertex2.position, vertex.position ) ) { + importedvertices.emplace_back( vertex1 ); + importedvertices.emplace_back( vertex2 ); + importedvertices.emplace_back( vertex ); + } + // start a new triangle + vertexcount = -1; + } + ++vertexcount; + } + + if( true == importedvertices.empty() ) { return *this; } + + // assign imported geometry to the node... + m_data.vertices.swap( importedvertices ); + // ...and calculate center... + for( auto const &vertex : m_data.vertices ) { + m_data.area.center += vertex.position; + } + m_data.area.center /= m_data.vertices.size(); + // ...and bounding area + double squareradius { 0.0 }; + for( auto const &vertex : m_data.vertices ) { + squareradius = std::max( + squareradius, + glm::length2( vertex.position - m_data.area.center ) ); + } + m_data.area.radius = std::max( + m_data.area.radius, + static_cast( std::sqrt( squareradius ) ) ); + + return *this; +} + +// adds content of provided node to already enclosed geometry. returns: true if merge could be performed +bool +shape_node::merge( shape_node &Shape ) { + + if( ( m_data.material != Shape.m_data.material ) + || ( m_data.lighting != Shape.m_data.lighting ) ) { + // can't merge nodes with different appearance + return false; + } + // add geometry from provided node + m_data.area.center = + interpolate( + m_data.area.center, Shape.m_data.area.center, + static_cast( Shape.m_data.vertices.size() ) / ( Shape.m_data.vertices.size() + m_data.vertices.size() ) ); + m_data.vertices.insert( + std::end( m_data.vertices ), + std::begin( Shape.m_data.vertices ), std::end( Shape.m_data.vertices ) ); + // NOTE: we could recalculate radius with something other than brute force, but it'll do + compute_radius(); + + return true; +} + +// generates renderable version of held non-instanced geometry in specified geometry bank +void +shape_node::create_geometry( geometrybank_handle const &Bank ) { + + vertex_array vertices; vertices.reserve( m_data.vertices.size() ); + + for( auto const &vertex : m_data.vertices ) { + vertices.emplace_back( + vertex.position - m_data.origin, + vertex.normal, + vertex.texture ); + } + m_data.geometry = GfxRenderer.Insert( vertices, Bank, GL_TRIANGLES ); + std::vector().swap( m_data.vertices ); // hipster shrink_to_fit +} + +// calculates shape's bounding radius +void +shape_node::compute_radius() { + + auto squaredradius { 0.0 }; + for( auto const &vertex : m_data.vertices ) { + squaredradius = std::max( + squaredradius, + glm::length2( vertex.position - m_data.area.center ) ); + } + m_data.area.radius = static_cast( std::sqrt( squaredradius ) ); +} + + + +// sends content of the struct to provided stream +void +lines_node::linesnode_data::serialize( std::ostream &Output ) const { + // bounding area + area.serialize( Output ); + // visibility + sn_utils::ls_float64( Output, rangesquared_min ); + sn_utils::ls_float64( Output, rangesquared_max ); + sn_utils::s_bool( Output, visible ); + // material + sn_utils::ls_float32( Output, line_width ); + lighting.serialize( Output ); + // geometry + sn_utils::s_dvec3( Output, origin ); + // NOTE: geometry handle is created dynamically on load + // vertex count, followed by vertex data + sn_utils::ls_uint32( Output, vertices.size() ); + for( auto const &vertex : vertices ) { + basic_vertex( + glm::vec3{ vertex.position - origin }, + vertex.normal, + vertex.texture ) + .serialize( Output ); + } +} + +// restores content of the struct from provided input stream +void +lines_node::linesnode_data::deserialize( std::istream &Input ) { + // bounding area + area.deserialize( Input ); + // visibility + rangesquared_min = sn_utils::ld_float64( Input ); + rangesquared_max = sn_utils::ld_float64( Input ); + visible = sn_utils::d_bool( Input ); + // material + line_width = sn_utils::ld_float32( Input ); + lighting.deserialize( Input ); + // geometry + origin = sn_utils::d_dvec3( Input ); + // NOTE: geometry handle is acquired during geometry creation + // vertex data + vertices.resize( sn_utils::ld_uint32( Input ) ); + basic_vertex localvertex; + for( auto &vertex : vertices ) { + localvertex.deserialize( Input ); + vertex.position = origin + glm::dvec3{ localvertex.position }; + vertex.normal = localvertex.normal; + vertex.texture = localvertex.texture; + } +} + + + +// sends content of the class to provided stream +void +lines_node::serialize( std::ostream &Output ) const { + // name + sn_utils::s_str( Output, m_name ); + // node data + m_data.serialize( Output ); +} + +// restores content of the node from provided input stream +lines_node & +lines_node::deserialize( std::istream &Input ) { + // name + m_name = sn_utils::d_str( Input ); + // node data + m_data.deserialize( Input ); + + return *this; +} + +// restores content of the node from provded input stream +lines_node & +lines_node::deserialize( cParser &Input, scene::node_data const &Nodedata ) { + + // import common data + m_name = Nodedata.name; + m_data.rangesquared_min = Nodedata.range_min * Nodedata.range_min; + m_data.rangesquared_max = ( + Nodedata.range_max >= 0.0 ? + Nodedata.range_max * Nodedata.range_max : + std::numeric_limits::max() ); + + // material + Input.getTokens( 3, false ); + Input + >> m_data.lighting.diffuse.r + >> m_data.lighting.diffuse.g + >> m_data.lighting.diffuse.b; + m_data.lighting.diffuse /= 255.f; + m_data.lighting.diffuse.a = 1.f; + Input.getTokens( 1, false ); + Input + >> m_data.line_width; + m_data.line_width = std::min( 30.f, m_data.line_width ); // 30 pix equals rougly width of a signal pole viewed from ~1m away + + // geometry + enum subtype { + lines, + line_strip, + line_loop + }; + + subtype const nodetype = ( + Nodedata.type == "lines" ? lines : + Nodedata.type == "line_strip" ? line_strip : + line_loop ); + std::size_t vertexcount { 0 }; + world_vertex vertex, vertex0, vertex1; + std::string token = Input.getToken(); + do { + vertex.position.x = std::atof( token.c_str() ); + Input.getTokens( 2, false ); + Input + >> vertex.position.y + >> vertex.position.z; + // convert all data to gl_lines to allow data merge for matching nodes + switch( nodetype ) { + case lines: { + m_data.vertices.emplace_back( vertex ); + break; + } + case line_strip: { + if( vertexcount > 0 ) { + m_data.vertices.emplace_back( vertex1 ); + m_data.vertices.emplace_back( vertex ); + } + vertex1 = vertex; + ++vertexcount; + break; + } + case line_loop: { + if( vertexcount == 0 ) { + vertex0 = vertex; + vertex1 = vertex; + } + else { + m_data.vertices.emplace_back( vertex1 ); + m_data.vertices.emplace_back( vertex ); + } + vertex1 = vertex; + ++vertexcount; + break; + } + default: { break; } + } + token = Input.getToken(); + + } while( token != "endline" ); + // add closing line for the loop + if( ( nodetype == line_loop ) + && ( vertexcount > 2 ) ) { + m_data.vertices.emplace_back( vertex1 ); + m_data.vertices.emplace_back( vertex0 ); + } + if( m_data.vertices.size() % 2 != 0 ) { + ErrorLog( "Lines node specified odd number of vertices, encountered in file \"" + Input.Name() + "\" (line " + std::to_string( Input.Line() - 1 ) + ")" ); + m_data.vertices.pop_back(); + } + + return *this; +} + +// adds content of provided node to already enclosed geometry. returns: true if merge could be performed +bool +lines_node::merge( lines_node &Lines ) { + + if( ( m_data.line_width != Lines.m_data.line_width ) + || ( m_data.lighting != Lines.m_data.lighting ) ) { + // can't merge nodes with different appearance + return false; + } + // add geometry from provided node + m_data.area.center = + interpolate( + m_data.area.center, Lines.m_data.area.center, + static_cast( Lines.m_data.vertices.size() ) / ( Lines.m_data.vertices.size() + m_data.vertices.size() ) ); + m_data.vertices.insert( + std::end( m_data.vertices ), + std::begin( Lines.m_data.vertices ), std::end( Lines.m_data.vertices ) ); + // NOTE: we could recalculate radius with something other than brute force, but it'll do + compute_radius(); + + return true; +} + +// generates renderable version of held non-instanced geometry in specified geometry bank +void +lines_node::create_geometry( geometrybank_handle const &Bank ) { + + vertex_array vertices; vertices.reserve( m_data.vertices.size() ); + + for( auto const &vertex : m_data.vertices ) { + vertices.emplace_back( + vertex.position - m_data.origin, + vertex.normal, + vertex.texture ); + } + m_data.geometry = GfxRenderer.Insert( vertices, Bank, GL_LINES ); + std::vector().swap( m_data.vertices ); // hipster shrink_to_fit +} + +// calculates node's bounding radius +void +lines_node::compute_radius() { + + auto squaredradius { 0.0 }; + for( auto const &vertex : m_data.vertices ) { + squaredradius = std::max( + squaredradius, + glm::length2( vertex.position - m_data.area.center ) ); + } + m_data.area.radius = static_cast( std::sqrt( squaredradius ) ); +} + + + +/* +memory_node & +memory_node::deserialize( cParser &Input, node_data const &Nodedata ) { + + // import common data + m_name = Nodedata.name; + + Input.getTokens( 3 ); + Input + >> m_data.area.center.x + >> m_data.area.center.y + >> m_data.area.center.z; + + TMemCell memorycell( Nodedata.name ); + memorycell.Load( &Input ); +} +*/ +} // scene + + + +namespace editor { + +basic_node::basic_node( scene::node_data const &Nodedata ) : + m_name( Nodedata.name ) +{ + m_rangesquaredmin = Nodedata.range_min * Nodedata.range_min; + m_rangesquaredmax = ( + Nodedata.range_max >= 0.0 ? + Nodedata.range_max * Nodedata.range_max : + std::numeric_limits::max() ); +} + +float const & +basic_node::radius() { + + if( m_area.radius == -1.0 ) { + // calculate if needed + radius_(); + } + return m_area.radius; +} + +// radius() subclass details, calculates node's bounding radius +// by default nodes are 'virtual don't extend from their center point +void +basic_node::radius_() { + + m_area.radius = 0.f; +} + +} // editor +//--------------------------------------------------------------------------- diff --git a/scenenode.h b/scenenode.h new file mode 100644 index 00000000..4e1797b8 --- /dev/null +++ b/scenenode.h @@ -0,0 +1,371 @@ +/* +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/. +*/ + +#pragma once + +#include + +#include "material.h" +#include "vertex.h" +#include "openglgeometrybank.h" +#include "parser.h" +#include "Model3d.h" + +struct lighting_data { + + glm::vec4 diffuse { 0.8f, 0.8f, 0.8f, 1.0f }; + glm::vec4 ambient { 0.2f, 0.2f, 0.2f, 1.0f }; + glm::vec4 specular { 0.0f, 0.0f, 0.0f, 1.0f }; + + // stores content of the struct in provided output stream + void + serialize( std::ostream &Output ) const; + // restores content of the struct from provided input stream + void + deserialize( std::istream &Input ); +}; + +inline +bool +operator==( lighting_data const &Left, lighting_data const &Right ) { + return ( ( Left.diffuse == Right.diffuse ) + && ( Left.ambient == Right.ambient ) + && ( Left.specular == Right.specular ) ); +} + +inline +bool +operator!=( lighting_data const &Left, lighting_data const &Right ) { + return !( Left == Right ); +} + +namespace scene { + +struct bounding_area { + + glm::dvec3 center; // mid point of the rectangle + float radius { -1.0f }; // radius of the bounding sphere + + bounding_area() = default; + bounding_area( glm::dvec3 Center, float Radius ) : + center( Center ), + radius( Radius ) + {} + // stores content of the struct in provided output stream + void + serialize( std::ostream &Output ) const; + // restores content of the struct from provided input stream + void + deserialize( std::istream &Input ); +}; + +struct node_data { + + double range_min { 0.0 }; + double range_max { std::numeric_limits::max() }; + std::string name; + std::string type; +}; + +// holds unique piece of geometry, covered with single material +class shape_node { + + friend class basic_region; // region might want to modify node content when it's being inserted + +public: +// types + struct shapenode_data { + // members: + // placement and visibility + scene::bounding_area area; // bounding area, in world coordinates + double rangesquared_min { 0.0 }; // visibility range, min + double rangesquared_max { 0.0 }; // visibility range, max + bool visible { true }; // visibility flag + // material data + bool translucent { false }; // whether opaque or translucent + material_handle material { null_handle }; + lighting_data lighting; + // geometry data + glm::dvec3 origin; // world position of the relative coordinate system origin + geometry_handle geometry { 0, 0 }; // relative origin-centered chunk of geometry held by gfx renderer + std::vector vertices; // world space source data of the geometry + // methods: + // sends content of the struct to provided stream + void + serialize( std::ostream &Output ) const; + // restores content of the struct from provided input stream + void + deserialize( std::istream &Input ); + }; + +// methods + // sends content of the class to provided stream + void + serialize( std::ostream &Output ) const; + // restores content of the node from provided input stream + shape_node & + deserialize( std::istream &Input ); + // restores content of the node from provided input stream + shape_node & + deserialize( cParser &Input, scene::node_data const &Nodedata ); + // imports data from provided submodel + shape_node & + convert( TSubModel const *Submodel ); + // adds content of provided node to already enclosed geometry. returns: true if merge could be performed + bool + merge( shape_node &Shape ); + // generates renderable version of held non-instanced geometry in specified geometry bank + void + create_geometry( geometrybank_handle const &Bank ); + // calculates shape's bounding radius + void + compute_radius(); + // set visibility + void + visible( bool State ); + // set origin point + void + origin( glm::dvec3 Origin ); + // data access + shapenode_data const & + data() const; + +private: +// members + std::string m_name; + shapenode_data m_data; +}; + +// set visibility +inline +void +shape_node::visible( bool State ) { + m_data.visible = State; +} +// set origin point +inline +void +shape_node::origin( glm::dvec3 Origin ) { + m_data.origin = Origin; +} +// data access +inline +shape_node::shapenode_data const & +shape_node::data() const { + return m_data; +} + + + +// holds a group of untextured lines +class lines_node { + + friend class basic_region; // region might want to modify node content when it's being inserted + +public: +// types + struct linesnode_data { + // members: + // placement and visibility + scene::bounding_area area; // bounding area, in world coordinates + double rangesquared_min { 0.0 }; // visibility range, min + double rangesquared_max { 0.0 }; // visibility range, max + bool visible { true }; // visibility flag + // material data + float line_width { 1.f }; // thickness of stored lines + lighting_data lighting; + // geometry data + glm::dvec3 origin; // world position of the relative coordinate system origin + geometry_handle geometry { 0, 0 }; // relative origin-centered chunk of geometry held by gfx renderer + std::vector vertices; // world space source data of the geometry + // methods: + // sends content of the struct to provided stream + void + serialize( std::ostream &Output ) const; + // restores content of the struct from provided input stream + void + deserialize( std::istream &Input ); + }; + +// methods + // sends content of the class to provided stream + void + serialize( std::ostream &Output ) const; + // restores content of the node from provided input stream + lines_node & + deserialize( std::istream &Input ); + // restores content of the node from provided input stream + lines_node & + deserialize( cParser &Input, scene::node_data const &Nodedata ); + // adds content of provided node to already enclosed geometry. returns: true if merge could be performed + bool + merge( lines_node &Lines ); + // generates renderable version of held non-instanced geometry in specified geometry bank + void + create_geometry( geometrybank_handle const &Bank ); + // calculates shape's bounding radius + void + compute_radius(); + // set visibility + void + visible( bool State ); + // set origin point + void + origin( glm::dvec3 Origin ); + // data access + linesnode_data const & + data() const; + +private: +// members + std::string m_name; + linesnode_data m_data; +}; + +// set visibility +inline +void +lines_node::visible( bool State ) { + m_data.visible = State; +} +// set origin point +inline +void +lines_node::origin( glm::dvec3 Origin ) { + m_data.origin = Origin; +} +// data access +inline +lines_node::linesnode_data const & +lines_node::data() const { + return m_data; +} + + +/* +// holds geometry for specific piece of track/road/waterway +class path_node { + + friend class basic_region; // region might want to modify node content when it's being inserted + +public: +// types + // TODO: enable after track class refactoring + struct pathnode_data { + // placement and visibility + bounding_area area; // bounding area, in world coordinates + bool visible { true }; // visibility flag + // material data + material_handle material_1 { 0 }; + material_handle material_2 { 0 }; + lighting_data lighting; + TEnvironmentType environment { e_flat }; + // geometry data + std::vector vertices; // world space source data of the geometry + glm::dvec3 origin; // world position of the relative coordinate system origin + using geometryhandle_sequence = std::vector; + geometryhandle_sequence geometry_1; // geometry chunks textured with texture 1 + geometryhandle_sequence geometry_2; // geometry chunks textured with texture 2 + }; +// methods + // restores content of the node from provded input stream + // TODO: implement + path_node & + deserialize( cParser &Input, node_data const &Nodedata ); + // binds specified track to the node + // TODO: remove after track class refactoring + void + path( TTrack *Path ) { + m_path = Path; } + TTrack * + path() { + return m_path; } + +private: +// members + +// // TODO: enable after track class refactoring +// pathnode_data m_data; + + TTrack * m_path; +}; +*/ +} // scene + + + +namespace editor { + +// base interface for nodes which can be actvated in scenario editor +struct basic_node { + +public: +// constructor + basic_node( scene::node_data const &Nodedata ); +// destructor + virtual ~basic_node() = default; +// methods + std::string const & + name() const; + void + location( glm::dvec3 const Location ); + glm::dvec3 const & + location() const; + float const & + radius(); + void + visible( bool const Visible ); + bool + visible() const; + +protected: +// methods + // radius() subclass details, calculates node's bounding radius + virtual void radius_(); +// members + scene::bounding_area m_area; + bool m_visible { true }; + double m_rangesquaredmin { 0.0 }; // visibility range, min + double m_rangesquaredmax { 0.0 }; // visibility range, max + std::string m_name; +}; + +inline +std::string const & +basic_node::name() const { + return m_name; +} + +inline +void +basic_node::location( glm::dvec3 const Location ) { + m_area.center = Location; +} + +inline +glm::dvec3 const & +basic_node::location() const { + return m_area.center; +} + +inline +void +basic_node::visible( bool const Visible ) { + m_visible = Visible; +} + +inline +bool +basic_node::visible() const { + return m_visible; +} + +} // editor + +//--------------------------------------------------------------------------- diff --git a/simulation.cpp b/simulation.cpp new file mode 100644 index 00000000..16adfc9f --- /dev/null +++ b/simulation.cpp @@ -0,0 +1,895 @@ +/* +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/. +*/ + +#include "stdafx.h" +#include "simulation.h" + +#include "Globals.h" +#include "Logs.h" +#include "uilayer.h" + +namespace simulation { + +state_manager State; +event_manager Events; +memory_table Memory; +path_table Paths; +traction_table Traction; +powergridsource_table Powergrid; +instance_table Instances; +vehicle_table Vehicles; +light_array Lights; +lua Lua; + +scene::basic_region *Region { nullptr }; + +bool +state_manager::deserialize( std::string const &Scenariofile ) { + + // TODO: move initialization to separate routine so we can reuse it + SafeDelete( Region ); + Region = new scene::basic_region(); + + // TODO: check first for presence of serialized binary files + // if this fails, fall back on the legacy text format + scene::scratch_data importscratchpad; + if( Scenariofile != "$.scn" ) { + // compilation to binary file isn't supported for rainsted-created overrides + importscratchpad.binary.terrain = Region->deserialize( Scenariofile ); + } + // NOTE: for the time being import from text format is a given, since we don't have full binary serialization + cParser scenarioparser( Scenariofile, cParser::buffer_FILE, Global::asCurrentSceneryPath, Global::bLoadTraction ); + + if( false == scenarioparser.ok() ) { return false; } + + deserialize( scenarioparser, importscratchpad ); + if( ( false == importscratchpad.binary.terrain ) + && ( Scenariofile != "$.scn" ) ) { + // if we didn't find usable binary version of the scenario files, create them now for future use + // as long as the scenario file wasn't rainsted-created base file override + Region->serialize( Scenariofile ); + } + + Global::iPause &= ~0x10; // koniec pauzy wczytywania + return true; +} + +// legacy method, calculates changes in simulation state over specified time +void +state_manager::update( double const Deltatime, int Iterationcount ) { + // aktualizacja animacji krokiem FPS: dt=krok czasu [s], dt*iter=czas od ostatnich przeliczeń + if (Deltatime == 0.0) { + // jeśli załączona jest pauza, to tylko obsłużyć ruch w kabinie trzeba + return; + } + + auto const totaltime { Deltatime * Iterationcount }; + // NOTE: we perform animations first, as they can determine factors like contact with powergrid + TAnimModel::AnimUpdate( totaltime ); // wykonanie zakolejkowanych animacji + + simulation::Powergrid.update( totaltime ); + simulation::Vehicles.update( Deltatime, Iterationcount ); +} + +// restores class data from provided stream +void +state_manager::deserialize( cParser &Input, scene::scratch_data &Scratchpad ) { + + // prepare deserialization function table + // since all methods use the same objects, we can have simple, hard-coded binds or lambdas for the task + using deserializefunction = void(state_manager::*)(cParser &, scene::scratch_data &); + std::vector< + std::pair< + std::string, + deserializefunction> > functionlist = { + { "atmo", &state_manager::deserialize_atmo }, + { "camera", &state_manager::deserialize_camera }, + { "config", &state_manager::deserialize_config }, + { "description", &state_manager::deserialize_description }, + { "event", &state_manager::deserialize_event }, + { "lua", &state_manager::deserialize_lua }, + { "firstinit", &state_manager::deserialize_firstinit }, + { "light", &state_manager::deserialize_light }, + { "node", &state_manager::deserialize_node }, + { "origin", &state_manager::deserialize_origin }, + { "endorigin", &state_manager::deserialize_endorigin }, + { "rotate", &state_manager::deserialize_rotate }, + { "sky", &state_manager::deserialize_sky }, + { "test", &state_manager::deserialize_test }, + { "time", &state_manager::deserialize_time }, + { "trainset", &state_manager::deserialize_trainset }, + { "endtrainset", &state_manager::deserialize_endtrainset } }; + using deserializefunctionbind = std::function; + std::unordered_map< + std::string, + deserializefunctionbind> functionmap; + for( auto &function : functionlist ) { + functionmap.emplace( function.first, std::bind( function.second, this, std::ref( Input ), std::ref( Scratchpad ) ) ); + } + + // deserialize content from the provided input + auto + timelast { std::chrono::steady_clock::now() }, + timenow { timelast }; + std::string token { Input.getToken() }; + while( false == token.empty() ) { + + auto lookup = functionmap.find( token ); + if( lookup != functionmap.end() ) { + lookup->second(); + } + else { + ErrorLog( "Bad scenario: unexpected token \"" + token + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( Input.Line() - 1 ) + ")" ); + } + + timenow = std::chrono::steady_clock::now(); + if( std::chrono::duration_cast( timenow - timelast ).count() >= 200 ) { + timelast = timenow; + glfwPollEvents(); + UILayer.set_progress( Input.getProgress(), Input.getFullProgress() ); + GfxRenderer.Render(); + } + + token = Input.getToken(); + } + + if( false == Scratchpad.initialized ) { + // manually perform scenario initialization + deserialize_firstinit( Input, Scratchpad ); + } +} + +void +state_manager::deserialize_atmo( cParser &Input, scene::scratch_data &Scratchpad ) { + + // NOTE: parameter system needs some decent replacement, but not worth the effort if we're moving to built-in editor + // atmosphere color; legacy parameter, no longer used + Input.getTokens( 3 ); + // fog range + Input.getTokens( 2 ); + Input + >> Global::fFogStart + >> Global::fFogEnd; + + if( Global::fFogEnd > 0.0 ) { + // fog colour; optional legacy parameter, no longer used + Input.getTokens( 3 ); + } + + std::string token { Input.getToken() }; + if( token != "endatmo" ) { + // optional overcast parameter + Global::Overcast = clamp( std::stof( token ), 0.f, 1.f ); + } + while( ( false == token.empty() ) + && ( token != "endatmo" ) ) { + // anything else left in the section has no defined meaning + token = Input.getToken(); + } +} + +void +state_manager::deserialize_camera( cParser &Input, scene::scratch_data &Scratchpad ) { + + glm::dvec3 xyz, abc; + int i = -1, into = -1; // do której definicji kamery wstawić + std::string token; + do { // opcjonalna siódma liczba określa numer kamery, a kiedyś były tylko 3 + Input.getTokens(); + Input >> 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() ); break; } // takie sobie, bo można wpisać -1 + default: { break; } + } + } while( token.compare( "endcamera" ) != 0 ); + if( into < 0 ) + into = ++Global::iCameraLast; + if( into < 10 ) { // przepisanie do odpowiedniego miejsca w tabelce + Global::FreeCameraInit[ into ] = xyz; + Global::FreeCameraInitAngle[ into ] = + Math3D::vector3( + glm::radians( abc.x ), + glm::radians( abc.y ), + glm::radians( abc.z ) ); + Global::iCameraLast = into; // numer ostatniej + } +/* + // cleaned up version of the above. + // NOTE: no longer supports legacy mode where some parameters were optional + Input.getTokens( 7 ); + glm::vec3 + position, + rotation; + int index; + Input + >> position.x + >> position.y + >> position.z + >> rotation.x + >> rotation.y + >> rotation.z + >> index; + + skip_until( Input, "endcamera" ); + + // TODO: finish this +*/ +} + +void +state_manager::deserialize_config( cParser &Input, scene::scratch_data &Scratchpad ) { + + // config parameters (re)definition + Global::ConfigParse( Input ); +} + +void +state_manager::deserialize_description( cParser &Input, scene::scratch_data &Scratchpad ) { + + // legacy section, never really used; + skip_until( Input, "enddescription" ); +} + +void +state_manager::deserialize_event( cParser &Input, scene::scratch_data &Scratchpad ) { + + // TODO: refactor event class and its de/serialization. do offset and rotation after deserialization is done + auto *event = new TEvent(); + Math3D::vector3 offset = ( + Scratchpad.location.offset.empty() ? + Math3D::vector3() : + Math3D::vector3( + Scratchpad.location.offset.top().x, + Scratchpad.location.offset.top().y, + Scratchpad.location.offset.top().z ) ); + event->Load( &Input, offset ); + + if( false == simulation::Events.insert( event ) ) { + delete event; + } +} + +void state_manager::deserialize_lua( cParser &Input, scene::scratch_data &Scratchpad ) +{ + Input.getTokens(1, false); + std::string file; + Input >> file; + simulation::Lua.interpret(Global::asCurrentSceneryPath + file); +} + +void +state_manager::deserialize_firstinit( cParser &Input, scene::scratch_data &Scratchpad ) { + + if( true == Scratchpad.initialized ) { return; } + + simulation::Paths.InitTracks(); + simulation::Traction.InitTraction(); + simulation::Events.InitEvents(); + simulation::Events.InitLaunchers(); + simulation::Memory.InitCells(); + + Scratchpad.initialized = true; +} + +void +state_manager::deserialize_light( cParser &Input, scene::scratch_data &Scratchpad ) { + + // legacy section, no longer used nor supported; + skip_until( Input, "endlight" ); +} + +void +state_manager::deserialize_node( cParser &Input, scene::scratch_data &Scratchpad ) { + + auto const inputline = Input.Line(); // cache in case we need to report error + + scene::node_data nodedata; + // common data and node type indicator + Input.getTokens( 4 ); + Input + >> nodedata.range_max + >> nodedata.range_min + >> nodedata.name + >> nodedata.type; + // type-based deserialization. not elegant but it'll do + if( nodedata.type == "dynamic" ) { + + auto *vehicle { deserialize_dynamic( Input, Scratchpad, nodedata ) }; + // vehicle import can potentially fail + if( vehicle == nullptr ) { return; } + + if( false == simulation::Vehicles.insert( vehicle ) ) { + + ErrorLog( "Bad scenario: vehicle with duplicate name \"" + vehicle->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + } + + if( ( vehicle->MoverParameters->CategoryFlag == 1 ) // trains only + && ( ( vehicle->MoverParameters->SecuritySystem.SystemType != 0 ) + || ( vehicle->MoverParameters->SandCapacity > 0.0 ) ) ) { + // we check for presence of security system or sand load, as a way to determine whether the vehicle is a controllable engine + // NOTE: this isn't 100% precise, e.g. middle EZT module comes with security system, while it has no lights, and some engines + // don't have security systems fitted + simulation::Lights.insert( vehicle ); + } + } + else if( nodedata.type == "track" ) { + + auto *path { deserialize_path( Input, Scratchpad, nodedata ) }; + // duplicates of named tracks are currently experimentally allowed + if( false == simulation::Paths.insert( path ) ) { + ErrorLog( "Bad scenario: track with duplicate name \"" + path->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); +/* + delete path; + delete pathnode; +*/ + } + simulation::Region->insert_path( path, Scratchpad ); + } + else if( nodedata.type == "traction" ) { + + auto *traction { deserialize_traction( Input, Scratchpad, nodedata ) }; + // traction loading is optional + if( traction == nullptr ) { return; } + + if( false == simulation::Traction.insert( traction ) ) { + ErrorLog( "Bad scenario: traction piece with duplicate name \"" + traction->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + } + simulation::Region->insert_traction( traction, Scratchpad ); + } + else if( nodedata.type == "tractionpowersource" ) { + + auto *powersource { deserialize_tractionpowersource( Input, Scratchpad, nodedata ) }; + // traction loading is optional + if( powersource == nullptr ) { return; } + + if( false == simulation::Powergrid.insert( powersource ) ) { + ErrorLog( "Bad scenario: power grid source with duplicate name \"" + powersource->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + } +/* + // TODO: implement this + simulation::Region.insert_powersource( powersource, Scratchpad ); +*/ + } + else if( nodedata.type == "model" ) { + + if( nodedata.range_min < 0.0 ) { + // 3d terrain + if( false == Scratchpad.binary.terrain ) { + // if we're loading data from text .scn file convert and import + auto *instance { deserialize_model( Input, Scratchpad, nodedata ) }; + // model import can potentially fail + if( instance == nullptr ) { return; } + // go through submodels, and import them as shapes + auto const cellcount = instance->TerrainCount() + 1; // zliczenie submodeli + for( auto i = 1; i < cellcount; ++i ) { + auto *submodel = instance->TerrainSquare( i - 1 ); + simulation::Region->insert_shape( + scene::shape_node().convert( submodel ), + Scratchpad, + false ); + // if there's more than one group of triangles in the cell they're held as children of the primary submodel + submodel = submodel->ChildGet(); + while( submodel != nullptr ) { + simulation::Region->insert_shape( + scene::shape_node().convert( submodel ), + Scratchpad, + false ); + submodel = submodel->NextGet(); + } + } + // with the import done we can get rid of the source model + delete instance; + } + else { + // if binary terrain file was present, we already have this data + skip_until( Input, "endmodel" ); + } + } + else { + // regular instance of 3d mesh + auto *instance { deserialize_model( Input, Scratchpad, nodedata ) }; + // model import can potentially fail + if( instance == nullptr ) { return; } + + if( false == simulation::Instances.insert( instance ) ) { + ErrorLog( "Bad scenario: 3d model instance with duplicate name \"" + instance->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + } + simulation::Region->insert_instance( instance, Scratchpad ); + } + } + else if( ( nodedata.type == "triangles" ) + || ( nodedata.type == "triangle_strip" ) + || ( nodedata.type == "triangle_fan" ) ) { + + if( false == Scratchpad.binary.terrain ) { + + simulation::Region->insert_shape( + scene::shape_node().deserialize( + Input, nodedata ), + Scratchpad, + true ); + } + else { + // all shapes were already loaded from the binary version of the file + skip_until( Input, "endtri" ); + } + } + else if( ( nodedata.type == "lines" ) + || ( nodedata.type == "line_strip" ) + || ( nodedata.type == "line_loop" ) ) { + + if( false == Scratchpad.binary.terrain ) { + + simulation::Region->insert_lines( + scene::lines_node().deserialize( + Input, nodedata ), + Scratchpad ); + } + else { + // all lines were already loaded from the binary version of the file + skip_until( Input, "endline" ); + } + } + else if( nodedata.type == "memcell" ) { + + auto *memorycell { deserialize_memorycell( Input, Scratchpad, nodedata ) }; + if( false == simulation::Memory.insert( memorycell ) ) { + ErrorLog( "Bad scenario: memory cell with duplicate name \"" + memorycell->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + } +/* + // TODO: implement this + simulation::Region.insert_memorycell( memorycell, Scratchpad ); +*/ + } + else if( nodedata.type == "eventlauncher" ) { + + auto *eventlauncher { deserialize_eventlauncher( Input, Scratchpad, nodedata ) }; + if( false == simulation::Events.insert( eventlauncher ) ) { + ErrorLog( "Bad scenario: event launcher with duplicate name \"" + eventlauncher->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + } + // event launchers can be either global, or local with limited range of activation + // each gets assigned different caretaker + if( true == eventlauncher->IsGlobal() ) { + simulation::Events.queue( eventlauncher ); + } + else { + simulation::Region->insert_launcher( eventlauncher, Scratchpad ); + } + } + else if( nodedata.type == "sound" ) { + + auto *sound { deserialize_sound( Input, Scratchpad, nodedata ) }; + simulation::Region->insert_sound( sound, Scratchpad ); + } + +} + +void +state_manager::deserialize_origin( cParser &Input, scene::scratch_data &Scratchpad ) { + + glm::dvec3 offset; + Input.getTokens( 3 ); + Input + >> offset.x + >> offset.y + >> offset.z; + // sumowanie całkowitego przesunięcia + Scratchpad.location.offset.emplace( + offset + ( + Scratchpad.location.offset.empty() ? + glm::dvec3() : + Scratchpad.location.offset.top() ) ); +} + +void +state_manager::deserialize_endorigin( cParser &Input, scene::scratch_data &Scratchpad ) { + + if( false == Scratchpad.location.offset.empty() ) { + Scratchpad.location.offset.pop(); + } + else { + ErrorLog( "Bad origin: endorigin instruction with empty origin stack in file \"" + Input.Name() + "\" (line " + std::to_string( Input.Line() - 1 ) + ")" ); + } +} + +void +state_manager::deserialize_rotate( cParser &Input, scene::scratch_data &Scratchpad ) { + + Input.getTokens( 3 ); + Input + >> Scratchpad.location.rotation.x + >> Scratchpad.location.rotation.y + >> Scratchpad.location.rotation.z; +} + +void +state_manager::deserialize_sky( cParser &Input, scene::scratch_data &Scratchpad ) { + + // sky model + Input.getTokens( 1 ); + Input + >> Global::asSky; + // anything else left in the section has no defined meaning + skip_until( Input, "endsky" ); +} + +void +state_manager::deserialize_test( cParser &Input, scene::scratch_data &Scratchpad ) { + + // legacy section, no longer supported; + skip_until( Input, "endtest" ); +} + +void +state_manager::deserialize_time( cParser &Input, scene::scratch_data &Scratchpad ) { + + // current scenario time + cParser timeparser( Input.getToken() ); + timeparser.getTokens( 2, false, ":" ); + auto &time = simulation::Time.data(); + timeparser + >> time.wHour + >> time.wMinute; + + // remaining sunrise and sunset parameters are no longer used, as they're now calculated dynamically + // anything else left in the section has no defined meaning + skip_until( Input, "endtime" ); +} + +void +state_manager::deserialize_trainset( cParser &Input, scene::scratch_data &Scratchpad ) { + + if( true == Scratchpad.trainset.is_open ) { + // shouldn't happen but if it does wrap up currently open trainset and report an error + deserialize_endtrainset( Input, Scratchpad ); + ErrorLog( "Bad scenario: encountered nested trainset definitions in file \"" + Input.Name() + "\" (line " + std::to_string( Input.Line() ) + ")" ); + } + + Scratchpad.trainset = scene::scratch_data::trainset_data(); + Scratchpad.trainset.is_open = true; + + Input.getTokens( 4 ); + Input + >> Scratchpad.trainset.name + >> Scratchpad.trainset.track + >> Scratchpad.trainset.offset + >> Scratchpad.trainset.velocity; +} + +void +state_manager::deserialize_endtrainset( cParser &Input, scene::scratch_data &Scratchpad ) { + + if( ( false == Scratchpad.trainset.is_open ) + || ( true == Scratchpad.trainset.vehicles.empty() ) ) { + // not bloody likely but we better check for it just the same + ErrorLog( "Bad trainset: empty trainset defined in file \"" + Input.Name() + "\" (line " + std::to_string( Input.Line() - 1 ) + ")" ); + Scratchpad.trainset.is_open = false; + return; + } + + std::size_t vehicleindex { 0 }; + for( auto *vehicle : Scratchpad.trainset.vehicles ) { + // go through list of vehicles in the trainset, coupling them together and checking for potential driver + if( ( vehicle->Mechanik != nullptr ) + && ( vehicle->Mechanik->Primary() ) ) { + // primary driver will receive the timetable for this trainset + Scratchpad.trainset.driver = vehicle; + } + if( vehicleindex > 0 ) { + // from second vehicle on couple it with the previous one + Scratchpad.trainset.vehicles[ vehicleindex - 1 ]->AttachPrev( + vehicle, + Scratchpad.trainset.couplings[ vehicleindex - 1 ] ); + } + ++vehicleindex; + } + + if( Scratchpad.trainset.driver != nullptr ) { + // if present, send timetable to the driver + // wysłanie komendy "Timetable" ustawia odpowiedni tryb jazdy + auto *controller = Scratchpad.trainset.driver->Mechanik; + controller->DirectionInitial(); + controller->PutCommand( + "Timetable:" + Scratchpad.trainset.name, + Scratchpad.trainset.velocity, + 0, + nullptr ); + } + if( Scratchpad.trainset.couplings.back() == coupling::faux ) { + // jeśli ostatni pojazd ma sprzęg 0 to założymy mu końcówki blaszane (jak AI się odpali, to sobie poprawi) + Scratchpad.trainset.vehicles.back()->RaLightsSet( -1, TMoverParameters::light::rearendsignals ); + } + // all done + Scratchpad.trainset.is_open = false; +} + +// creates path and its wrapper, restoring class data from provided stream +TTrack * +state_manager::deserialize_path( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + // TODO: refactor track and wrapper classes and their de/serialization. do offset and rotation after deserialization is done + auto *track = new TTrack( Nodedata ); + Math3D::vector3 offset = ( + Scratchpad.location.offset.empty() ? + Math3D::vector3() : + Math3D::vector3( + Scratchpad.location.offset.top().x, + Scratchpad.location.offset.top().y, + Scratchpad.location.offset.top().z ) ); + track->Load( &Input, offset ); + + return track; +} + +TTraction * +state_manager::deserialize_traction( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + if( false == Global::bLoadTraction ) { + skip_until( Input, "endtraction" ); + return nullptr; + } + // TODO: refactor track and wrapper classes and their de/serialization. do offset and rotation after deserialization is done + auto *traction = new TTraction( Nodedata ); + auto offset = ( + Scratchpad.location.offset.empty() ? + glm::dvec3() : + Scratchpad.location.offset.top() ); + traction->Load( &Input, offset ); + + return traction; +} + +TTractionPowerSource * +state_manager::deserialize_tractionpowersource( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + if( false == Global::bLoadTraction ) { + skip_until( Input, "end" ); + return nullptr; + } + + auto *powersource = new TTractionPowerSource( Nodedata ); + powersource->Load( &Input ); + // adjust location + powersource->location( transform( powersource->location(), Scratchpad ) ); + + return powersource; +} + +TMemCell * +state_manager::deserialize_memorycell( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + auto *memorycell = new TMemCell( Nodedata ); + memorycell->Load( &Input ); + // adjust location + memorycell->location( transform( memorycell->location(), Scratchpad ) ); + + return memorycell; +} + +TEventLauncher * +state_manager::deserialize_eventlauncher( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + glm::dvec3 location; + Input.getTokens( 3 ); + Input + >> location.x + >> location.y + >> location.z; + + auto *eventlauncher = new TEventLauncher( Nodedata ); + eventlauncher->Load( &Input ); + eventlauncher->location( transform( location, Scratchpad ) ); + + return eventlauncher; +} + +TAnimModel * +state_manager::deserialize_model( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + glm::dvec3 location; + glm::vec3 rotation; + Input.getTokens( 4 ); + Input + >> location.x + >> location.y + >> location.z + >> rotation.y; + + auto *instance = new TAnimModel( Nodedata ); + instance->RaAnglesSet( Scratchpad.location.rotation + rotation ); // dostosowanie do pochylania linii + + if( false == instance->Load( &Input, false ) ) { + // model nie wczytał się - ignorowanie node + SafeDelete( instance ); + } + instance->location( transform( location, Scratchpad ) ); + + return instance; +} + +TDynamicObject * +state_manager::deserialize_dynamic( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + if( false == Scratchpad.trainset.is_open ) { + // part of trainset data is used when loading standalone vehicles, so clear it just in case + Scratchpad.trainset = scene::scratch_data::trainset_data(); + } + auto const inputline { Input.Line() }; // cache in case of errors + // basic attributes + auto const datafolder { Input.getToken() }; + auto const skinfile { Input.getToken() }; + auto const mmdfile { Input.getToken() }; + auto const pathname = ( + Scratchpad.trainset.is_open ? + Scratchpad.trainset.track : + Input.getToken() ); + auto const offset { Input.getToken( false ) }; + auto const drivertype { Input.getToken() }; + auto const couplingdata = ( + Scratchpad.trainset.is_open ? + Input.getToken() : + "3" ); + auto const velocity = ( + Scratchpad.trainset.is_open ? + Scratchpad.trainset.velocity : + Input.getToken( false ) ); + // extract coupling type and optional parameters + auto const couplingdatawithparams = couplingdata.find( '.' ); + auto coupling = ( + couplingdatawithparams != std::string::npos ? + std::atoi( couplingdata.substr( 0, couplingdatawithparams ).c_str() ) : + std::atoi( couplingdata.c_str() ) ); + if( coupling < 0 ) { + // sprzęg zablokowany (pojazdy nierozłączalne przy manewrach) + coupling = ( -coupling ) | coupling::permanent; + } + if( ( offset != -1.0 ) + && ( std::abs( offset ) > 0.5 ) ) { // maksymalna odległość między sprzęgami - do przemyślenia + // likwidacja sprzęgu, jeśli odległość zbyt duża - to powinno być uwzględniane w fizyce sprzęgów... + coupling = coupling::faux; + } + auto const params = ( + couplingdatawithparams != std::string::npos ? + couplingdata.substr( couplingdatawithparams + 1 ) : + "" ); + // load amount and type + auto loadcount { Input.getToken( false ) }; + auto loadtype = ( + loadcount ? + Input.getToken() : + "" ); + if( loadtype == "enddynamic" ) { + // idiotoodporność: ładunek bez podanego typu nie liczy się jako ładunek + loadcount = 0; + loadtype = ""; + } + + auto *path = simulation::Paths.find( pathname ); + if( path == nullptr ) { + + ErrorLog( "Bad scenario: vehicle \"" + Nodedata.name + "\" placed on nonexistent path \"" + pathname + "\" in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + skip_until( Input, "enddynamic" ); + return nullptr; + } + + if( ( true == Scratchpad.trainset.vehicles.empty() ) // jeśli pierwszy pojazd, + && ( false == path->asEvent0Name.empty() ) // tor ma Event0 + && ( std::abs( velocity ) <= 1.f ) // a skład stoi + && ( Scratchpad.trainset.offset >= 0.0 ) // ale może nie sięgać na owy tor + && ( Scratchpad.trainset.offset < 8.0 ) ) { // i raczej nie sięga + // przesuwamy około pół EU07 dla wstecznej zgodności + Scratchpad.trainset.offset = 8.0; + } + + auto *vehicle = new TDynamicObject(); + + auto const length = + vehicle->Init( + Nodedata.name, + datafolder, skinfile, mmdfile, + path, + ( offset == -1.0 ? + Scratchpad.trainset.offset : + Scratchpad.trainset.offset - offset ), + drivertype, + velocity, + Scratchpad.trainset.name, + loadcount, loadtype, + ( offset == -1.0 ), + params ); + + if( length != 0.0 ) { // zero oznacza błąd + // przesunięcie dla kolejnego, minus bo idziemy w stronę punktu 1 + Scratchpad.trainset.offset -= length; + // automatically establish permanent connections for couplers which specify them in their definitions + if( ( coupling != 0 ) + && ( vehicle->MoverParameters->Couplers[ ( offset == -1.0 ? 0 : 1 ) ].AllowedFlag & coupling::permanent ) ) { + coupling |= coupling::permanent; + } + if( true == Scratchpad.trainset.is_open ) { + Scratchpad.trainset.vehicles.emplace_back( vehicle ); + Scratchpad.trainset.couplings.emplace_back( coupling ); + } + } + else { + delete vehicle; + skip_until( Input, "enddynamic" ); + return nullptr; + } + + auto const destination { Input.getToken() }; + if( destination != "enddynamic" ) { + // optional vehicle destination parameter + vehicle->asDestination = Input.getToken(); + skip_until( Input, "enddynamic" ); + } + + return vehicle; +} + +sound * +state_manager::deserialize_sound( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ) { + + glm::dvec3 location; + Input.getTokens( 3 ); + Input + >> location.x + >> location.y + >> location.z; + // adjust location + location = transform( location, Scratchpad ); + + auto const soundname { Input.getToken() }; + auto *sound = sound_man->create_text_sound(soundname); + sound->position((glm::vec3)location); + if (Nodedata.range_max != -1.0) + sound->dist(Nodedata.range_max); + else + sound->set_mode(sound::global); + + skip_until( Input, "endsound" ); + + return sound; +} + +// skips content of stream until specified token +void +state_manager::skip_until( cParser &Input, std::string const &Token ) { + + std::string token { Input.getToken() }; + while( ( false == token.empty() ) + && ( token != Token ) ) { + + token = Input.getToken(); + } +} + +// transforms provided location by specifed rotation and offset +glm::dvec3 +state_manager::transform( glm::dvec3 Location, scene::scratch_data const &Scratchpad ) { + + if( Scratchpad.location.rotation != glm::vec3( 0, 0, 0 ) ) { + auto const rotation = glm::radians( Scratchpad.location.rotation ); + Location = glm::rotateY( Location, rotation.y ); // Ra 2014-11: uwzględnienie rotacji + } + if( false == Scratchpad.location.offset.empty() ) { + Location += Scratchpad.location.offset.top(); + } + return Location; +} + +} // simulation + +//--------------------------------------------------------------------------- diff --git a/simulation.h b/simulation.h new file mode 100644 index 00000000..8b27b571 --- /dev/null +++ b/simulation.h @@ -0,0 +1,92 @@ +/* +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/. +*/ + +#pragma once + +#include "parser.h" +#include "scene.h" +#include "event.h" +#include "MemCell.h" +#include "EvLaunch.h" +#include "Track.h" +#include "Traction.h" +#include "TractionPower.h" +#include "sound.h" +#include "AnimModel.h" +#include "DynObj.h" +#include "Driver.h" +#include "lightarray.h" +#include "Event.h" +#include "lua.h" + +namespace simulation { + +class state_manager { + +public: +// types + +// methods + // legacy method, calculates changes in simulation state over specified time + void + update( double dt, int iter ); + bool + deserialize( std::string const &Scenariofile ); + +private: +// methods + // restores class data from provided stream + void deserialize( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_atmo( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_camera( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_config( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_description( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_event( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_lua( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_firstinit( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_light( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_node( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_origin( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_endorigin( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_rotate( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_sky( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_test( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_time( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_trainset( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_endtrainset( cParser &Input, scene::scratch_data &Scratchpad ); + TTrack * deserialize_path( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + TTraction * deserialize_traction( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + TTractionPowerSource * deserialize_tractionpowersource( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + TMemCell * deserialize_memorycell( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + TEventLauncher * deserialize_eventlauncher( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + TAnimModel * deserialize_model( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + TDynamicObject * deserialize_dynamic( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + sound * deserialize_sound( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + // skips content of stream until specified token + void skip_until( cParser &Input, std::string const &Token ); + // transforms provided location by specifed rotation and offset + glm::dvec3 transform( glm::dvec3 Location, scene::scratch_data const &Scratchpad ); +}; + +extern state_manager State; +extern event_manager Events; +extern memory_table Memory; +extern path_table Paths; +extern traction_table Traction; +extern powergridsource_table Powergrid; +extern instance_table Instances; +extern vehicle_table Vehicles; +extern light_array Lights; +extern lua Lua; + +extern scene::basic_region *Region; + +} // simulation + +//--------------------------------------------------------------------------- diff --git a/sn_utils.cpp b/sn_utils.cpp index f454fc3c..9906d9c1 100644 --- a/sn_utils.cpp +++ b/sn_utils.cpp @@ -75,6 +75,28 @@ std::string sn_utils::d_str(std::istream &s) return r; } +bool sn_utils::d_bool(std::istream& s) +{ + return ( ld_uint16( s ) == 1 ); +} + +glm::dvec3 sn_utils::d_dvec3(std::istream& s) +{ + return { + ld_float64(s), + ld_float64(s), + ld_float64(s) }; +} + +glm::vec4 sn_utils::d_vec4( std::istream& s) +{ + return { + ld_float32(s), + ld_float32(s), + ld_float32(s), + ld_float32(s) }; +} + void sn_utils::ls_uint16(std::ostream &s, uint16_t v) { uint8_t buf[2]; @@ -133,4 +155,28 @@ void sn_utils::s_str(std::ostream &s, std::string v) { const char* buf = v.c_str(); s.write(buf, v.size() + 1); -} \ No newline at end of file +} + +void sn_utils::s_bool(std::ostream &s, bool v) +{ + ls_uint16( + s, + ( true == v ? + 1 : + 0 ) ); +} + +void sn_utils::s_dvec3(std::ostream &s, glm::dvec3 const &v) +{ + ls_float64(s, v.x); + ls_float64(s, v.y); + ls_float64(s, v.z); +} + +void sn_utils::s_vec4(std::ostream &s, glm::vec4 const &v) +{ + ls_float32(s, v.x); + ls_float32(s, v.y); + ls_float32(s, v.z); + ls_float32(s, v.w); +} diff --git a/sn_utils.h b/sn_utils.h index dd269121..e11d1b31 100644 --- a/sn_utils.h +++ b/sn_utils.h @@ -13,6 +13,9 @@ public: static float ld_float32(std::istream&); static double ld_float64(std::istream&); static std::string d_str(std::istream&); + static bool d_bool(std::istream&); + static glm::dvec3 d_dvec3(std::istream&); + static glm::vec4 d_vec4(std::istream&); static void ls_uint16(std::ostream&, uint16_t); static void ls_uint32(std::ostream&, uint32_t); @@ -20,4 +23,7 @@ public: static void ls_float32(std::ostream&, float); static void ls_float64(std::ostream&, double); static void s_str(std::ostream&, std::string); + static void s_bool(std::ostream&, bool); + static void s_dvec3(std::ostream&, glm::dvec3 const &); + static void s_vec4(std::ostream&, glm::vec4 const &); }; \ No newline at end of file diff --git a/sound.cpp b/sound.cpp index be956d70..5baed1a3 100644 --- a/sound.cpp +++ b/sound.cpp @@ -348,6 +348,11 @@ sound& sound::position(glm::vec3 p) return *this; } +glm::vec3 sound::location() +{ + return pos; +} + sound& sound::position(Math3D::vector3 const &pos) { position((glm::vec3)glm::make_vec3(&pos.x)); diff --git a/sound.h b/sound.h index 82f82556..84adbd02 100644 --- a/sound.h +++ b/sound.h @@ -13,6 +13,7 @@ #include #include "dumb3d.h" #include "parser.h" +#include "Names.h" class load_error : public std::runtime_error { @@ -70,6 +71,7 @@ public: virtual ~sound(); + glm::vec3 location(); //get position virtual bool is_playing() = 0; virtual void play() = 0; diff --git a/stdafx.h b/stdafx.h index 23a8d555..dadd3764 100644 --- a/stdafx.h +++ b/stdafx.h @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include diff --git a/translation.h b/translation.h index 297f35cf..a9831f81 100644 --- a/translation.h +++ b/translation.h @@ -70,6 +70,8 @@ static std::unordered_map m_cabcontrols = { { "pantalloff_sw:", "all pantographs" }, { "pantselected_sw:", "selected pantograph" }, { "pantselectedoff_sw:", "selected pantograph" }, + { "pantcompressor_sw:", "pantograph compressor" }, + { "pantcompressorvalve_sw:", "pantograph 3 way valve" }, { "trainheating_sw:", "heating" }, { "signalling_sw:", "braking indicator" }, { "door_signalling_sw:", "door locking" }, diff --git a/version.h b/version.h index c063cca4..e7a0964f 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 17 -#define VERSION_MINOR 914 +#define VERSION_MINOR 1031 #define VERSION_REVISION 0 diff --git a/vertex.cpp b/vertex.cpp new file mode 100644 index 00000000..badcab8e --- /dev/null +++ b/vertex.cpp @@ -0,0 +1,64 @@ +/* +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/. +*/ + +#include "stdafx.h" +#include "vertex.h" +#include "sn_utils.h" + +void +world_vertex::serialize( std::ostream &s ) const { + + sn_utils::ls_float64( s, position.x ); + sn_utils::ls_float64( s, position.y ); + sn_utils::ls_float64( s, position.z ); + + sn_utils::ls_float32( s, normal.x ); + sn_utils::ls_float32( s, normal.y ); + sn_utils::ls_float32( s, normal.z ); + + sn_utils::ls_float32( s, texture.x ); + sn_utils::ls_float32( s, texture.y ); +} + +void +world_vertex::deserialize( std::istream &s ) { + + position.x = sn_utils::ld_float64( s ); + position.y = sn_utils::ld_float64( s ); + position.z = sn_utils::ld_float64( s ); + + normal.x = sn_utils::ld_float32( s ); + normal.y = sn_utils::ld_float32( s ); + normal.z = sn_utils::ld_float32( s ); + + texture.x = sn_utils::ld_float32( s ); + texture.y = sn_utils::ld_float32( s ); +} + +template <> +world_vertex & +world_vertex::operator+=( world_vertex const &Right ) { + + position += Right.position; + normal += Right.normal; + texture += Right.texture; + return *this; +} + +template <> +world_vertex & +world_vertex::operator*=( world_vertex const &Right ) { + + position *= Right.position; + normal *= Right.normal; + texture *= Right.texture; + return *this; +} + +//--------------------------------------------------------------------------- diff --git a/vertex.h b/vertex.h new file mode 100644 index 00000000..0dcfbbb9 --- /dev/null +++ b/vertex.h @@ -0,0 +1,89 @@ +/* +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/. +*/ + +#pragma once + +#include +#include "usefull.h" + +// geometry vertex with double precision position +struct world_vertex { + +// members + glm::dvec3 position; + glm::vec3 normal; + glm::vec2 texture; + +// overloads + // operator+ + template + world_vertex & + operator+=( Scalar_ const &Right ) { + position += Right; + normal += Right; + texture += Right; + return *this; } + template + friend + world_vertex + operator+( world_vertex Left, Scalar_ const &Right ) { + Left += Right; + return Left; } + // operator* + template + world_vertex & + operator*=( Scalar_ const &Right ) { + position *= Right; + normal *= Right; + texture *= Right; + return *this; } + template + friend + world_vertex + operator*( world_vertex Left, Type_ const &Right ) { + Left *= Right; + return Left; } +// methods + void serialize( std::ostream& ) const; + void deserialize( std::istream& ); + // wyliczenie współrzędnych i mapowania punktu na środku odcinka v1<->v2 + void + set_half( world_vertex const &Vertex1, world_vertex const &Vertex2 ) { + *this = + interpolate( + Vertex1, + Vertex2, + 0.5 ); } + // wyliczenie współrzędnych i mapowania punktu na odcinku v1<->v2 + void + set_from_x( world_vertex const &Vertex1, world_vertex const &Vertex2, double const X ) { + *this = + interpolate( + Vertex1, + Vertex2, + ( X - Vertex1.position.x ) / ( Vertex2.position.x - Vertex1.position.x ) ); } + // wyliczenie współrzędnych i mapowania punktu na odcinku v1<->v2 + void + set_from_z( world_vertex const &Vertex1, world_vertex const &Vertex2, double const Z ) { + *this = + interpolate( + Vertex1, + Vertex2, + ( Z - Vertex1.position.z ) / ( Vertex2.position.z - Vertex1.position.z ) ); } +}; + +template <> +world_vertex & +world_vertex::operator+=( world_vertex const &Right ); + +template <> +world_vertex & +world_vertex::operator*=( world_vertex const &Right ); + +//--------------------------------------------------------------------------- diff --git a/windows.cpp b/windows.cpp index 30d08032..fe96b241 100644 --- a/windows.cpp +++ b/windows.cpp @@ -63,7 +63,7 @@ LRESULT APIENTRY WndProc( HWND hWnd, // handle for this window // obsługa danych przesłanych przez program sterujący pDane = (PCOPYDATASTRUCT)lParam; if( pDane->dwData == MAKE_ID4('E', 'U', '0', '7')) // sygnatura danych - World.OnCommandGet( (DaneRozkaz *)( pDane->lpData ) ); + World.OnCommandGet( ( multiplayer::DaneRozkaz *)( pDane->lpData ) ); break; } case WM_KEYDOWN: diff --git a/winheaders.h b/winheaders.h index ac75ec0c..484963de 100644 --- a/winheaders.h +++ b/winheaders.h @@ -6,6 +6,9 @@ #else #include #define WORD uint16_t +#define UINT uint32_t +#define WPARAM uint64_t +#define LPARAM uint64_t #define DWORD uint32_t #define LONG int32_t #define BYTE uint8_t