/* 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, Maciej Czapkiewicz and others */ #include "stdafx.h" #include "DynObj.h" #include "simulation.h" #include "lightarray.h" #include "Camera.h" #include "Train.h" #include "Driver.h" #include "Globals.h" #include "Timer.h" #include "Logs.h" #include "Console.h" #include "MdlMngr.h" #include "Model3d.h" #include "renderer.h" #include "uitranscripts.h" #include "messaging.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) #define M_2PI 6.283185307179586476925286766559; const float maxrot = (float)(M_PI / 3.0); // 60° std::string const TDynamicObject::MED_labels[] = { "masa: ", "amax: ", "Fzad: ", "FmPN: ", "FmED: ", "FrED: ", "FzPN: ", "nPrF: " }; bool TDynamicObject::bDynamicRemove { false }; // helper, locates submodel with specified name in specified 3d model; returns: pointer to the submodel, or null TSubModel * GetSubmodelFromName( TModel3d * const Model, std::string const Name ) { return ( Model ? Model->GetFromName( Name ) : nullptr ); } // Ra 2015-01: sprawdzenie dostępności tekstury o podanej nazwie std::string TextureTest( std::string const &Name ) { auto const lookup { FileExists( { Global.asCurrentTexturePath + Name, Name, szTexturePath + Name }, { ".mat", ".dds", ".tga", ".bmp" } ) }; return ( lookup.first + lookup.second ); } //--------------------------------------------------------------------------- void TAnimPant::AKP_4E() { // ustawienie wymiarów dla pantografu AKP-4E vPos = Math3D::vector3(0, 0, 0); // przypisanie domyśnych współczynników do pantografów fLenL1 = 1.22; // 1.176289 w modelach fLenU1 = 1.755; // 1.724482197 w modelach fHoriz = 0.535; // 0.54555075 przesunięcie ślizgu w długości pojazdu względem // osi obrotu dolnego // ramienia fHeight = 0.07; // wysokość ślizgu ponad oś obrotu fWidth = 0.635; // połowa szerokości ślizgu, 0.635 dla AKP-1 i AKP-4E fAngleL0 = DegToRad(2.8547285515689267247882521833308); fAngleL = fAngleL0; // początkowy kąt dolnego ramienia // fAngleU0=acos((1.22*cos(fAngleL)+0.535)/1.755); //górne ramię fAngleU0 = acos((fLenL1 * cos(fAngleL) + fHoriz) / fLenU1); // górne ramię fAngleU = fAngleU0; // początkowy kąt // PantWys=1.22*sin(fAngleL)+1.755*sin(fAngleU); //wysokość początkowa PantWys = fLenL1 * sin(fAngleL) + fLenU1 * sin(fAngleU) + fHeight; // wysokość początkowa PantTraction = PantWys; hvPowerWire = NULL; fWidthExtra = 0.381f; //(2.032m-1.027)/2 // poza obszarem roboczym jest aproksymacja łamaną o 5 odcinkach fHeightExtra[0] = 0.0f; //+0.0762 fHeightExtra[1] = -0.01f; //+0.1524 fHeightExtra[2] = -0.03f; //+0.2286 fHeightExtra[3] = -0.07f; //+0.3048 fHeightExtra[4] = -0.15f; //+0.3810 }; //--------------------------------------------------------------------------- int TAnim::TypeSet(int i, int fl) { // ustawienie typu animacji i zależnej od niego ilości animowanych submodeli fMaxDist = -1.0; // normalnie nie pokazywać switch (i) { // maska 0x000F: ile używa wskaźników na submodele (0 gdy jeden, // wtedy bez tablicy) // maska 0x00F0: // 0-osie,1-drzwi,2-obracane,3-zderzaki,4-wózki,5-pantografy,6-tłoki // maska 0xFF00: ile używa liczb float dla współczynników i stanu case 0: iFlags = 0x000; break; // 0-oś case 1: iFlags = 0x010; break; // 1-drzwi case 2: iFlags = 0x020; fParam = fl ? new float[fl] : NULL; iFlags += fl << 8; break; // 2-wahacz, dźwignia itp. case 3: iFlags = 0x030; break; // 3-zderzak case 4: iFlags = 0x040; break; // 4-wózek case 5: // 5-pantograf - 5 submodeli iFlags = 0x055; fParamPants = new TAnimPant(); fParamPants->AKP_4E(); break; case 6: iFlags = 0x068; break; // 6-tłok i rozrząd - 8 submodeli case 7: iFlags = 0x070; break; // doorstep case 8: iFlags = 0x080; break; // mirror default: iFlags = 0; } yUpdate = nullptr; return iFlags & 15; // ile wskaźników rezerwować dla danego typu animacji }; TAnim::~TAnim() { // usuwanie animacji switch (iFlags & 0xF0) { // usuwanie struktur, zależnie ile zostało stworzonych case 0x20: // 2-wahacz, dźwignia itp. delete fParam; break; case 0x50: // 5-pantograf delete fParamPants; break; default: break; } }; /* void TAnim::Parovoz(){ // animowanie tłoka i rozrządu parowozu }; */ // assigns specified texture or a group of textures to replacable texture slots void material_data::assign( std::string const &Replacableskin ) { // check for the pipe method first if( Replacableskin.find( '|' ) != std::string::npos ) { cParser nameparser( Replacableskin ); nameparser.getTokens( 4, true, "|" ); int skinindex = 0; std::string texturename; nameparser >> texturename; while( ( texturename != "" ) && ( skinindex < 4 ) ) { replacable_skins[ skinindex + 1 ] = GfxRenderer->Fetch_Material( texturename ); ++skinindex; texturename = ""; nameparser >> texturename; } multi_textures = skinindex; } else { // otherwise try the basic approach int skinindex = 0; do { // test quietly for file existence so we don't generate tons of false errors in the log // NOTE: this means actual missing files won't get reported which is hardly ideal, but still somewhat better auto const material { TextureTest( ToLower( Replacableskin + "," + std::to_string( skinindex + 1 ) ) ) }; if( true == material.empty() ) { break; } replacable_skins[ skinindex + 1 ] = GfxRenderer->Fetch_Material( material ); ++skinindex; } while( skinindex < 4 ); multi_textures = skinindex; if( multi_textures == 0 ) { // zestaw nie zadziałał, próbujemy normanie replacable_skins[ 1 ] = GfxRenderer->Fetch_Material( Replacableskin ); } } if( replacable_skins[ 1 ] == null_handle ) { // last ditch attempt, check for single replacable skin texture replacable_skins[ 1 ] = GfxRenderer->Fetch_Material( Replacableskin ); } textures_alpha = ( GfxRenderer->Material( replacable_skins[ 1 ] ).is_translucent() ? 0x31310031 : // tekstura -1 z kanałem alfa - nie renderować w cyklu nieprzezroczystych 0x30300030 ); // wszystkie tekstury nieprzezroczyste - nie renderować w cyklu przezroczystych if( GfxRenderer->Material( replacable_skins[ 2 ] ).is_translucent() ) { // tekstura -2 z kanałem alfa - nie renderować w cyklu nieprzezroczystych textures_alpha |= 0x02020002; } if( GfxRenderer->Material( replacable_skins[ 3 ] ).is_translucent() ) { // tekstura -3 z kanałem alfa - nie renderować w cyklu nieprzezroczystych textures_alpha |= 0x04040004; } if( GfxRenderer->Material( replacable_skins[ 4 ] ).is_translucent() ) { // tekstura -4 z kanałem alfa - nie renderować w cyklu nieprzezroczystych textures_alpha |= 0x08080008; } } void TDynamicObject::destination_data::deserialize( cParser &Input ) { while( true == deserialize_mapping( Input ) ) { ; // all work done by while() } } bool TDynamicObject::destination_data::deserialize_mapping( cParser &Input ) { // token can be a key or block end auto const key { Input.getToken( true, "\n\r\t ,;[]" ) }; if( ( true == key.empty() ) || ( key == "}" ) ) { return false; } if( key == "{" ) { script = Input.getToken(); } else if( key == "update:" ) { auto const value { Input.getToken() }; // TODO: implement } else if( key == "instance:" ) { instancing = Input.getToken(); } else if( key == "parameters:" ) { parameters = Input.getToken(); } return true; } //--------------------------------------------------------------------------- TDynamicObject * TDynamicObject::FirstFind(int &coupler_nr, int cf) { // szukanie skrajnego połączonego pojazdu w pociagu // od strony sprzegu (coupler_nr) obiektu (start) TDynamicObject *temp = this; for (int i = 0; i < 300; i++) // ograniczenie do 300 na wypadek zapętlenia składu { if (!temp) return NULL; // Ra: zabezpieczenie przed ewentaulnymi błędami sprzęgów if ((temp->MoverParameters->Couplers[coupler_nr].CouplingFlag & cf) != cf) return temp; // nic nie ma już dalej podłączone sprzęgiem cf if (coupler_nr == end::front) { // jeżeli szukamy od sprzęgu 0 if (temp->PrevConnected()) // jeśli mamy coś z przodu { if (temp->PrevConnectedNo() == end::front) // jeśli pojazd od strony sprzęgu 0 jest odwrócony coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu temp = temp->PrevConnected(); // ten jest od strony 0 } else return temp; // jeśli jednak z przodu nic nie ma } else { if (temp->NextConnected()) { if (temp->NextConnectedNo() == end::rear) // jeśli pojazd od strony sprzęgu 1 jest odwrócony coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu temp = temp->NextConnected(); // ten pojazd jest od strony 1 } else return temp; // jeśli jednak z tyłu nic nie ma } } return NULL; // to tylko po wyczerpaniu pętli }; //--------------------------------------------------------------------------- float TDynamicObject::GetEPP() { // szukanie skrajnego połączonego pojazdu w pociagu // od strony sprzegu (coupler_nr) obiektu (start) TDynamicObject *temp = this; int coupler_nr = 0; double eq = 0.0; double am = 0.0; for (int i = 0; i < 300; ++i) // ograniczenie do 300 na wypadek zapętlenia składu { if (!temp) break; // Ra: zabezpieczenie przed ewentaulnymi błędami sprzęgów eq += temp->MoverParameters->PipePress * temp->MoverParameters->Dim.L; am += temp->MoverParameters->Dim.L; if ((temp->MoverParameters->Couplers[coupler_nr].CouplingFlag & coupling::brakehose) != coupling::brakehose) break; // nic nie ma już dalej podłączone if (coupler_nr == 0) { // jeżeli szukamy od sprzęgu 0 if (temp->PrevConnected()) // jeśli mamy coś z przodu { if (temp->PrevConnectedNo() == end::front) // jeśli pojazd od strony sprzęgu 0 jest odwrócony coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu temp = temp->PrevConnected(); // ten jest od strony 0 } else break; // jeśli jednak z przodu nic nie ma } else { if (temp->NextConnected()) { if (temp->NextConnectedNo() == end::rear) // jeśli pojazd od strony sprzęgu 1 jest odwrócony coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu temp = temp->NextConnected(); // ten pojazd jest od strony 1 } else break; // jeśli jednak z tyłu nic nie ma } } temp = this; coupler_nr = 1; for (int i = 0; i < 300; i++) // ograniczenie do 300 na wypadek zapętlenia składu { if (!temp) break; // Ra: zabezpieczenie przed ewentaulnymi błędami sprzęgów eq += temp->MoverParameters->PipePress * temp->MoverParameters->Dim.L; am += temp->MoverParameters->Dim.L; if ((temp->MoverParameters->Couplers[coupler_nr].CouplingFlag & coupling::brakehose) != coupling::brakehose) break; // nic nie ma już dalej podłączone if (coupler_nr == 0) { // jeżeli szukamy od sprzęgu 0 if (temp->PrevConnected()) // jeśli mamy coś z przodu { if (temp->PrevConnectedNo() == end::front) // jeśli pojazd od strony sprzęgu 0 jest odwrócony coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu temp = temp->PrevConnected(); // ten jest od strony 0 } else break; // jeśli jednak z przodu nic nie ma } else { if (temp->NextConnected()) { if (temp->NextConnectedNo() == end::rear) // jeśli pojazd od strony sprzęgu 1 jest odwrócony coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu temp = temp->NextConnected(); // ten pojazd jest od strony 1 } else break; // jeśli jednak z tyłu nic nie ma } } eq -= MoverParameters->PipePress * MoverParameters->Dim.L; am -= MoverParameters->Dim.L; return eq / am; }; //--------------------------------------------------------------------------- TDynamicObject * TDynamicObject::GetFirstDynamic(int cpl_type, int cf) { // Szukanie skrajnego połączonego pojazdu w pociagu // od strony sprzegu (cpl_type) obiektu szukajacego // Ra: wystarczy jedna funkcja do szukania w obu kierunkach return FirstFind(cpl_type, cf); // używa referencji }; void TDynamicObject::ABuSetModelShake( Math3D::vector3 mShake ) { modelShake = mShake; }; int TDynamicObject::GetPneumatic(bool front, bool red) { int x, y, z; // 1=prosty, 2=skośny if (red) { if (front) { x = btCPneumatic1.GetStatus(); y = btCPneumatic1r.GetStatus(); } else { x = btCPneumatic2.GetStatus(); y = btCPneumatic2r.GetStatus(); } } else if (front) { x = btPneumatic1.GetStatus(); y = btPneumatic1r.GetStatus(); } else { x = btPneumatic2.GetStatus(); y = btPneumatic2r.GetStatus(); } z = 0; // brak węży? if ((x == 1) && (y == 1)) z = 3; // dwa proste if ((x == 2) && (y == 0)) z = 1; // lewy skośny, brak prawego if ((x == 0) && (y == 2)) z = 2; // brak lewego, prawy skośny return z; } void TDynamicObject::SetPneumatic(bool front, bool red) { int x = 0, ten = 0, tamten = 0; ten = GetPneumatic(front, red); // 1=lewy skos,2=prawy skos,3=dwa proste if (front) if (PrevConnected()) // pojazd od strony sprzęgu 0 tamten = PrevConnected()->GetPneumatic((PrevConnectedNo() == end::front ? true : false), red); if (!front) if (NextConnected()) // pojazd od strony sprzęgu 1 tamten = NextConnected()->GetPneumatic((NextConnectedNo() == end::front ? true : false), red); if (ten == tamten) // jeśli układ jest symetryczny switch (ten) { case 1: x = 2; break; // mamy lewy skos, dać lewe skosy case 2: x = 3; break; // mamy prawy skos, dać prawe skosy case 3: // wszystkie cztery na prosto if (MoverParameters->Couplers[front ? end::front : end::rear].Render) x = 1; else x = 4; break; } else { if (ten == 2) x = 4; if (ten == 1) x = 1; if (ten == 3) if (tamten == 1) x = 4; else x = 1; } if (front) { if (red) cp1 = x; else sp1 = x; } // który pokazywać z przodu else { if (red) cp2 = x; else sp2 = x; } // który pokazywać z tyłu } void TDynamicObject::UpdateAxle(TAnim *pAnim) { // animacja osi size_t wheel_id = pAnim->dWheelAngle; pAnim->smAnimated->SetRotate(float3(1, 0, 0), dWheelAngle[wheel_id]); pAnim->smAnimated->future_transform = glm::rotate((float)glm::radians(m_future_wheels_angle[wheel_id]), glm::vec3(1.0f, 0.0f, 0.0f)); }; // animacja drzwi - przesuw void TDynamicObject::UpdateDoorTranslate(TAnim *pAnim) { if( pAnim->smAnimated == nullptr ) { return; } auto const &door { MoverParameters->Doors.instances[ ( ( pAnim->iNumber & 1 ) == 0 ? side::right : side::left ) ] }; pAnim->smAnimated->SetTranslate( Math3D::vector3{ 0.0, 0.0, door.position } ); }; // animacja drzwi - obrót void TDynamicObject::UpdateDoorRotate(TAnim *pAnim) { if( pAnim->smAnimated == nullptr ) { return; } auto const &door { MoverParameters->Doors.instances[ ( ( pAnim->iNumber & 1 ) == 0 ? side::right : side::left ) ] }; pAnim->smAnimated->SetRotate( float3(1, 0, 0), door.position ); }; // animacja drzwi - obrót void TDynamicObject::UpdateDoorFold(TAnim *pAnim) { if( pAnim->smAnimated == nullptr ) { return; } auto const &door { MoverParameters->Doors.instances[ ( ( pAnim->iNumber & 1 ) == 0 ? side::right : side::left ) ] }; // skrzydło mniejsze pAnim->smAnimated->SetRotate( float3(0, 0, 1), door.position); // skrzydło większe auto *sm = pAnim->smAnimated->ChildGet(); if( sm == nullptr ) { return; } sm->SetRotate( float3(0, 0, 1), -door.position - door.position); // podnóżek? sm = sm->ChildGet(); if( sm == nullptr ) { return; } sm->SetRotate( float3(0, 1, 0), door.position); }; // animacja drzwi - odskokprzesuw void TDynamicObject::UpdateDoorPlug(TAnim *pAnim) { if( pAnim->smAnimated == nullptr ) { return; } auto const &door { MoverParameters->Doors.instances[ ( ( pAnim->iNumber & 1 ) == 0 ? side::right : side::left ) ] }; pAnim->smAnimated->SetTranslate( Math3D::vector3 { std::min( door.position * 2.f, MoverParameters->Doors.range_out ), 0.0, std::max( 0.f, door.position - MoverParameters->Doors.range_out * 0.5f ) } ); } void TDynamicObject::UpdatePant(TAnim *pAnim) { // animacja pantografu - 4 obracane ramiona, ślizg piąty float a, b, c; a = RadToDeg(pAnim->fParamPants->fAngleL - pAnim->fParamPants->fAngleL0); b = RadToDeg(pAnim->fParamPants->fAngleU - pAnim->fParamPants->fAngleU0); c = a + b; if (pAnim->smElement[0]) pAnim->smElement[0]->SetRotate(float3(-1, 0, 0), a); // dolne ramię if (pAnim->smElement[1]) pAnim->smElement[1]->SetRotate(float3(1, 0, 0), a); if (pAnim->smElement[2]) pAnim->smElement[2]->SetRotate(float3(1, 0, 0), c); // górne ramię if (pAnim->smElement[3]) pAnim->smElement[3]->SetRotate(float3(-1, 0, 0), c); if (pAnim->smElement[4]) pAnim->smElement[4]->SetRotate(float3(-1, 0, 0), b); //ślizg } // doorstep animation, shift void TDynamicObject::UpdatePlatformTranslate( TAnim *pAnim ) { if( pAnim->smAnimated == nullptr ) { return; } auto const &door { MoverParameters->Doors.instances[ ( ( pAnim->iNumber & 1 ) == 0 ? side::right : side::left ) ] }; pAnim->smAnimated->SetTranslate( Math3D::vector3{ interpolate( 0.f, MoverParameters->Doors.step_range, door.step_position ), 0.0, 0.0 } ); } // doorstep animation, rotate void TDynamicObject::UpdatePlatformRotate( TAnim *pAnim ) { if( pAnim->smAnimated == nullptr ) { return; } auto const &door { MoverParameters->Doors.instances[ ( ( pAnim->iNumber & 1 ) == 0 ? side::right : side::left ) ] }; pAnim->smAnimated->SetRotate( float3( 0, 1, 0 ), interpolate( 0.f, MoverParameters->Doors.step_range, door.step_position ) ); } // mirror animation, rotate void TDynamicObject::UpdateMirror( TAnim *pAnim ) { if( pAnim->smAnimated == nullptr ) { return; } // only animate the mirror if it's located on the same end of the vehicle as the active cab auto const isactive { ( MoverParameters->CabOccupied > 0 ? ( ( pAnim->iNumber >> 4 ) == end::front ? 1.0 : 0.0 ) : MoverParameters->CabOccupied < 0 ? ( ( pAnim->iNumber >> 4 ) == end::rear ? 1.0 : 0.0 ) : 0.0 ) }; if( pAnim->iNumber & 1 ) pAnim->smAnimated->SetRotate( float3( 0, 1, 0 ), interpolate( 0.0, MoverParameters->MirrorMaxShift, dMirrorMoveR * isactive ) ); else pAnim->smAnimated->SetRotate( float3( 0, 1, 0 ), interpolate( 0.0, MoverParameters->MirrorMaxShift, dMirrorMoveL * isactive ) ); } /* void TDynamicObject::UpdateLeverDouble(TAnim *pAnim) { // animacja gałki zależna od double pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fSpeed * *pAnim->fDoubleBase); }; void TDynamicObject::UpdateLeverFloat(TAnim *pAnim) { // animacja gałki zależna od float pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fSpeed * *pAnim->fFloatBase); }; void TDynamicObject::UpdateLeverInt(TAnim *pAnim) { // animacja gałki zależna od int pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fSpeed * *pAnim->iIntBase); }; void TDynamicObject::UpdateLeverEnum(TAnim *pAnim) { // ustawienie kąta na // wartość wskazaną przez // int z tablicy fParam // pAnim->fParam[0]; - dodać lepkość pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fParam[*pAnim->iIntBase]); }; */ // sets light levels for registered interior sections void TDynamicObject::toggle_lights() { if( true == SectionLightsActive ) { // switch all lights off... for( auto §ion : Sections ) { auto const sectionname { section.compartment->pName }; if( sectionname.rfind( "cab", 0 ) != 0 ) { section.light_level = 0.0; } } SectionLightsActive = false; } else { // set lights with probability depending on the compartment type. TODO: expose this in .mmd file for( auto §ion : Sections ) { auto const sectionname { section.compartment->pName }; if( ( sectionname.find( "corridor" ) == 0 ) || ( sectionname.find( "korytarz" ) == 0 ) ) { // corridors are lit 100% of time section.light_level = 0.75f; } else if( ( sectionname.find( "compartment" ) == 0 ) || ( sectionname.find( "przedzial" ) == 0 ) ) { // compartments are lit with 75% probability section.light_level = ( Random() < 0.75 ? 0.75f : 0.10f ); } } SectionLightsActive = true; } } void TDynamicObject::set_cab_lights( int const Cab, float const Level ) { for( auto §ion : Sections ) { // cab compartments are placed at the beginning of the list, so we can bail out as soon as we find different compartment type auto const sectionname { section.compartment->pName }; if( sectionname.size() < 4 ) { return; } if( sectionname.find( "cab" ) != 0 ) { return; } if( sectionname[ 3 ] != Cab + '0' ) { continue; } // match the cab with correct index section.light_level = Level; } } // ABu 29.01.05 przeklejone z render i renderalpha: ********************* void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) { // ABu290105: pozbierane i uporzadkowane powtarzajace // sie rzeczy z Render i RenderAlpha // dodatkowy warunek, if (ObjSqrDist<...) zeby niepotrzebnie nie zmianiec w // obiektach, // ktorych i tak nie widac // NBMX wrzesien, MC listopad: zuniwersalnione btnOn = false; // czy przywrócić stan domyślny po renderowaniu if (mdLoad) // tymczasowo ładunek na poziom podłogi if (vFloor.z > 0.0) mdLoad->GetSMRoot()->SetTranslate(modelShake + vFloor); if (ObjSqrDist < ( 400 * 400 ) ) // gdy bliżej niż 400m { for( auto &animation : pAnimations ) { // wykonanie kolejnych animacji if( ( ObjSqrDist < animation.fMaxDist ) && ( animation.yUpdate ) ) { // jeśli zdefiniowana funkcja aktualizacja animacji (położenia submodeli animation.yUpdate( &animation ); } } if( ( mdModel != nullptr ) && ( ObjSqrDist < ( 50 * 50 ) ) ) { // gdy bliżej niż 50m // ABu290105: rzucanie pudlem // te animacje wymagają bananów w modelach! mdModel->GetSMRoot()->SetTranslate(modelShake); if (mdKabina) mdKabina->GetSMRoot()->SetTranslate(modelShake); if (mdLoad) mdLoad->GetSMRoot()->SetTranslate(modelShake + vFloor); if (mdLowPolyInt) mdLowPolyInt->GetSMRoot()->SetTranslate(modelShake); // ABu: koniec rzucania // ABu011104: liczenie obrotow wozkow ABuBogies(); // Mczapkie-100402: rysowanie lub nie - sprzegow // ABu-240105: Dodatkowy warunek: if (...).Render, zeby rysowal tylko jeden z polaczonych sprzegow // display _on if connected with another vehicle and the coupling owner (render flag) // display _xon if connected with another vehicle and not the coupling owner // display _xon if not connected, but equipped with coupling adapter // display _off if not connected, not equipped with coupling adapter or if _xon model is missing if( TestFlag( MoverParameters->Couplers[ end::front ].CouplingFlag, coupling::coupler ) ) { if( MoverParameters->Couplers[ end::front ].Render ) { btCoupler1.TurnOn(); } else { btCoupler1.TurnxOnWithOffAsFallback(); } btnOn = true; } else { if( true == MoverParameters->Couplers[ end::front ].has_adapter() ) { btCoupler1.TurnxOnWithOffAsFallback(); btnOn = true; } } if( TestFlag( MoverParameters->Couplers[ end::rear ].CouplingFlag, coupling::coupler ) ) { if( MoverParameters->Couplers[ end::rear ].Render ) { btCoupler2.TurnOn(); } else { btCoupler2.TurnxOnWithOffAsFallback(); } btnOn = true; } else { if( true == MoverParameters->Couplers[ end::rear ].has_adapter() ) { btCoupler2.TurnxOnWithOffAsFallback(); btnOn = true; } } //******************************************************************************** // przewody powietrzne j.w., ABu: decyzja czy rysowac tylko na podstawie // 'render' - juz // nie // przewody powietrzne, yB: decyzja na podstawie polaczen w t3d if (Global.bnewAirCouplers) { SetPneumatic(false, false); // wczytywanie z t3d ulozenia wezykow SetPneumatic(true, false); // i zapisywanie do zmiennej SetPneumatic(true, true); // ktore z nich nalezy SetPneumatic(false, true); // wyswietlic w tej klatce if (TestFlag(MoverParameters->Couplers[end::front].CouplingFlag, ctrain_pneumatic)) { switch (cp1) { case 1: btCPneumatic1.TurnOn(); break; case 2: btCPneumatic1.TurnxOn(); break; case 3: btCPneumatic1r.TurnxOn(); break; case 4: btCPneumatic1r.TurnOn(); break; } btnOn = true; } if (TestFlag(MoverParameters->Couplers[end::rear].CouplingFlag, ctrain_pneumatic)) { switch (cp2) { case 1: btCPneumatic2.TurnOn(); break; case 2: btCPneumatic2.TurnxOn(); break; case 3: btCPneumatic2r.TurnxOn(); break; case 4: btCPneumatic2r.TurnOn(); break; } btnOn = true; } // przewody zasilajace, j.w. (yB) if (TestFlag(MoverParameters->Couplers[end::front].CouplingFlag, ctrain_scndpneumatic)) { switch (sp1) { case 1: btPneumatic1.TurnOn(); break; case 2: btPneumatic1.TurnxOn(); break; case 3: btPneumatic1r.TurnxOn(); break; case 4: btPneumatic1r.TurnOn(); break; } btnOn = true; } if (TestFlag(MoverParameters->Couplers[end::rear].CouplingFlag, ctrain_scndpneumatic)) { switch (sp2) { case 1: btPneumatic2.TurnOn(); break; case 2: btPneumatic2.TurnxOn(); break; case 3: btPneumatic2r.TurnxOn(); break; case 4: btPneumatic2r.TurnOn(); break; } btnOn = true; } } //*********************************************************************************/ else // po staremu ABu'oewmu { // przewody powietrzne j.w., ABu: decyzja czy rysowac tylko na podstawie // 'render' if (TestFlag(MoverParameters->Couplers[end::front].CouplingFlag, ctrain_pneumatic)) { if (MoverParameters->Couplers[end::front].Render) btCPneumatic1.TurnOn(); else btCPneumatic1r.TurnOn(); btnOn = true; } if (TestFlag(MoverParameters->Couplers[end::rear].CouplingFlag, ctrain_pneumatic)) { if (MoverParameters->Couplers[end::rear].Render) btCPneumatic2.TurnOn(); else btCPneumatic2r.TurnOn(); btnOn = true; } // przewody powietrzne j.w., ABu: decyzja czy rysowac tylko na podstawie // 'render' // //yB - zasilajace if (TestFlag(MoverParameters->Couplers[0].CouplingFlag, ctrain_scndpneumatic)) { if (MoverParameters->Couplers[0].Render) btPneumatic1.TurnOn(); else btPneumatic1r.TurnOn(); btnOn = true; } if (TestFlag(MoverParameters->Couplers[1].CouplingFlag, ctrain_scndpneumatic)) { if (MoverParameters->Couplers[1].Render) btPneumatic2.TurnOn(); else btPneumatic2r.TurnOn(); btnOn = true; } } //*************************************************************/// koniec // wezykow // uginanie zderzakow for (int i = 0; i < 2; ++i) { if( MoverParameters->Couplers[ i ].has_adapter() ) { // HACK: if there's coupler adapter on this side, we presume there's additional distance put between vehicles // which prevents buffers from clashing against each other (or the other vehicle doesn't have buffers to begin with) continue; } auto const dist { clamp( MoverParameters->Couplers[ i ].Dist / 2.0, -MoverParameters->Couplers[ i ].DmaxB, 0.0 ) }; if( dist >= 0.0 ) { continue; } if( smBuforLewy[ i ] ) { smBuforLewy[ i ]->SetTranslate( Math3D::vector3( dist, 0, 0 ) ); } if( smBuforPrawy[ i ] ) { smBuforPrawy[ i ]->SetTranslate( Math3D::vector3( dist, 0, 0 ) ); } } } // vehicle within 50m // Winger 160204 - podnoszenie pantografow // przewody sterowania ukrotnionego if (TestFlag(MoverParameters->Couplers[0].CouplingFlag, coupling::control)) { btCCtrl1.Turn( true ); btnOn = true; } // else btCCtrl1.TurnOff(); if (TestFlag(MoverParameters->Couplers[1].CouplingFlag, coupling::control)) { btCCtrl2.Turn( true ); btnOn = true; } // else btCCtrl2.TurnOff(); // McZapkie-181103: mostki przejsciowe if (TestFlag(MoverParameters->Couplers[0].CouplingFlag, ctrain_passenger)) { btCPass1.Turn( true ); btnOn = true; } // else btCPass1.TurnOff(); if (TestFlag(MoverParameters->Couplers[1].CouplingFlag, ctrain_passenger)) { btCPass2.Turn( true ); btnOn = true; } // else btCPass2.TurnOff(); if (MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable) { // sygnaly konca pociagu if (btEndSignals1.Active()) { if (TestFlag(MoverParameters->iLights[0], 2) || TestFlag(MoverParameters->iLights[0], 32)) { btEndSignals1.Turn( true ); btnOn = true; } // else btEndSignals1.TurnOff(); } else { if (TestFlag(MoverParameters->iLights[0], 2)) { btEndSignals11.Turn( true ); btnOn = true; } // else btEndSignals11.TurnOff(); if (TestFlag(MoverParameters->iLights[0], 32)) { btEndSignals13.Turn( true ); btnOn = true; } // else btEndSignals13.TurnOff(); } if (btEndSignals2.Active()) { if (TestFlag(MoverParameters->iLights[1], 2) || TestFlag(MoverParameters->iLights[1], 32)) { btEndSignals2.Turn( true ); btnOn = true; } // else btEndSignals2.TurnOff(); } else { if (TestFlag(MoverParameters->iLights[1], 2)) { btEndSignals21.Turn( true ); btnOn = true; } // else btEndSignals21.TurnOff(); if (TestFlag(MoverParameters->iLights[1], 32)) { btEndSignals23.Turn( true ); btnOn = true; } // else btEndSignals23.TurnOff(); } } // tablice blaszane: if (TestFlag(MoverParameters->iLights[end::front], light::rearendsignals)) { btEndSignalsTab1.Turn( true ); btnOn = true; } // else btEndSignalsTab1.TurnOff(); if (TestFlag(MoverParameters->iLights[end::rear], light::rearendsignals)) { btEndSignalsTab2.Turn( true ); btnOn = true; } // destination signs update_destinations(); // else btEndSignalsTab2.TurnOff(); // McZapkie-181002: krecenie wahaczem (korzysta z kata obrotu silnika) if (iAnimType[ANIM_LEVERS]) for (int i = 0; i < 4; ++i) if (smWahacze[i]) smWahacze[i]->SetRotate(float3(1, 0, 0), fWahaczeAmp * cos(MoverParameters->eAngle)); // cooling shutters // NOTE: shutters display _on state when they're closed, _off otherwise if( ( true == MoverParameters->dizel_heat.water.config.shutters ) && ( false == MoverParameters->dizel_heat.zaluzje1 ) ) { btShutters1.Turn( true ); btnOn = true; } if( ( true == MoverParameters->dizel_heat.water_aux.config.shutters ) && ( false == MoverParameters->dizel_heat.zaluzje2 ) ) { btShutters2.Turn( true ); btnOn = true; } if( ( false == bDisplayCab ) // edge case, lowpoly may act as a stand-in for the hi-fi cab, so make sure not to show the driver when inside && ( Mechanik != nullptr ) && ( ( Mechanik->action() != TAction::actSleep ) /* || ( MoverParameters->Battery ) */ ) ) { // rysowanie figurki mechanika btMechanik1.Turn( MoverParameters->CabOccupied > 0 ); btMechanik2.Turn( MoverParameters->CabOccupied < 0 ); if( MoverParameters->CabOccupied != 0 ) { btnOn = true; } } } // vehicle within 400m if( MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable ) { // sygnały czoła pociagu //Ra: wyświetlamy bez // ograniczeń odległości, by były widoczne z // daleka if (TestFlag(MoverParameters->iLights[end::front], light::headlight_left)) { if( DimHeadlights ) { btHeadSignals11.TurnxOnWithOnAsFallback(); } else { btHeadSignals11.TurnOn(); } btnOn = true; } if (TestFlag(MoverParameters->iLights[end::front], light::headlight_upper)) { if( DimHeadlights ) { btHeadSignals12.TurnxOnWithOnAsFallback(); } else { btHeadSignals12.TurnOn(); } btnOn = true; } if (TestFlag(MoverParameters->iLights[end::front], light::headlight_right)) { if( DimHeadlights ) { btHeadSignals13.TurnxOnWithOnAsFallback(); } else { btHeadSignals13.TurnOn(); } btnOn = true; } // else btHeadSignals13.TurnOff(); if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_left)) { if( DimHeadlights ) { btHeadSignals21.TurnxOnWithOnAsFallback(); } else { btHeadSignals21.TurnOn(); } btnOn = true; } if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_upper)) { if( DimHeadlights ) { btHeadSignals22.TurnxOnWithOnAsFallback(); } else { btHeadSignals22.TurnOn(); } btnOn = true; } if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_right)) { if( DimHeadlights ) { btHeadSignals23.TurnxOnWithOnAsFallback(); } else { btHeadSignals23.TurnOn(); } btnOn = true; } } // interior light levels auto sectionlightcolor { glm::vec4( 1.f ) }; bool cabsection{ true }; for( auto const §ion : Sections ) { if( cabsection ) { // check whether we're still processing cab sections auto const §ionname { section.compartment->pName }; cabsection &= ( ( sectionname.size() >= 4 ) && ( sectionname.substr( 0, 3 ) == "cab" ) ); } // TODO: add cablight devices auto const sectionlightlevel { section.light_level * ( cabsection ? 1.0f : MoverParameters->CompartmentLights.intensity ) }; sectionlightcolor = glm::vec4( ( ( ( sectionlightlevel == 0.f ) || ( Global.fLuminance > section.compartment->fLight ) ) ? glm::vec3( 240.f / 255.f ) : // TBD: save and restore initial submodel diffuse instead of enforcing one? InteriorLight ), // TODO: per-compartment (type) light color sectionlightlevel ); section.compartment->SetLightLevel( sectionlightcolor, true ); if( section.load != nullptr ) { section.load->SetLightLevel( sectionlightcolor, true ); } } // load chunks visibility for( auto const §ion : Sections ) { // section isn't guaranteed to have load model, so check that first if( section.load == nullptr ) { continue; } section.load->iVisible = ( section.load_chunks_visible > 0 ); // if the section root isn't visible we can skip meddling with its children if( false == section.load->iVisible ) { continue; } // if the section root is visible set the state of section chunks auto *sectionchunk { section.load->ChildGet() }; auto visiblechunkcount { section.load_chunks_visible }; while( sectionchunk != nullptr ) { sectionchunk->iVisible = ( visiblechunkcount > 0 ); --visiblechunkcount; sectionchunk = sectionchunk->NextGet(); } } // driver cabs visibility for( int cabidx = 0; cabidx < LowPolyIntCabs.size(); ++cabidx ) { if( LowPolyIntCabs[ cabidx ] == nullptr ) { continue; } LowPolyIntCabs[ cabidx ]->iVisible = ( mdKabina == nullptr ? true : // there's no hi-fi cab bDisplayCab == false ? true : // we're in external view simulation::Train == nullptr ? true : // not a player-driven vehicle, implies external view simulation::Train->Dynamic() != this ? true : // not a player-driven vehicle, implies external view JointCabs ? false : // internal view, all cabs share the model so hide them 'all' ( simulation::Train->iCabn != cabidx ) ); // internal view, hide occupied cab and show others } } // ABu 29.01.05 koniec przeklejenia ************************************* TDynamicObject * TDynamicObject::ABuFindNearestObject(TTrack *Track, TDynamicObject *MyPointer, int &CouplNr) { // zwraca wskaznik do obiektu znajdujacego sie na torze (Track), którego sprzęg jest najblizszy kamerze // służy np. do łączenia i rozpinania sprzęgów // WE: Track - tor, na ktorym odbywa sie poszukiwanie // MyPointer - wskaznik do obiektu szukajacego // WY: CouplNr - który sprzęg znalezionego obiektu jest bliższy kamerze // Uwaga! Jesli CouplNr==-2 to szukamy njblizszego obiektu, a nie sprzegu!!! for( auto dynamic : Track->Dynamics ) { if( CouplNr == -2 ) { // wektor [kamera-obiekt] - poszukiwanie obiektu if( Math3D::LengthSquared3( Global.pCamera.Pos - dynamic->vPosition ) < 100.0 ) { // 10 metrów return dynamic; } } else { // jeśli (CouplNr) inne niz -2, szukamy sprzęgu if( Math3D::LengthSquared3( Global.pCamera.Pos - dynamic->vCoulpler[ 0 ] ) < 25.0 ) { // 5 metrów CouplNr = 0; return dynamic; } if( Math3D::LengthSquared3( Global.pCamera.Pos - dynamic->vCoulpler[ 1 ] ) < 25.0 ) { // 5 metrów CouplNr = 1; return dynamic; } } } // empty track or nothing found return nullptr; } TDynamicObject * TDynamicObject::ABuScanNearestObject(TTrack *Track, double ScanDir, double ScanDist, int &CouplNr) { // skanowanie toru w poszukiwaniu obiektu najblizszego kamerze if (ABuGetDirection() < 0) ScanDir = -ScanDir; TDynamicObject *FoundedObj; FoundedObj = ABuFindNearestObject(Track, this, CouplNr); // zwraca numer sprzęgu znalezionego pojazdu if (FoundedObj == NULL) { double ActDist; // Przeskanowana odleglosc. double CurrDist = 0; // Aktualna dlugosc toru. if (ScanDir >= 0) ActDist = Track->Length() - RaTranslationGet(); //???-przesunięcie wózka względem Point1 toru else ActDist = RaTranslationGet(); // przesunięcie wózka względem Point1 toru while (ActDist < ScanDist) { ActDist += CurrDist; if (ScanDir > 0) // do przodu { if (Track->iNextDirection) { Track = Track->CurrentNext(); ScanDir = -ScanDir; } else Track = Track->CurrentNext(); } else // do tyłu { if (Track->iPrevDirection) Track = Track->CurrentPrev(); else { Track = Track->CurrentPrev(); ScanDir = -ScanDir; } } if (Track != NULL) { // jesli jest kolejny odcinek toru CurrDist = Track->Length(); FoundedObj = ABuFindNearestObject(Track, this, CouplNr); if (FoundedObj != NULL) ActDist = ScanDist; } else // Jesli nie ma, to wychodzimy. ActDist = ScanDist; } } // Koniec szukania najblizszego toru z jakims obiektem. return FoundedObj; } // ABu 01.11.04 poczatek wyliczania przechylow pudla ********************** void TDynamicObject::ABuModelRoll() { // ustawienie przechyłki pojazdu i jego // zawartości // Ra: przechyłkę załatwiamy na etapie przesuwania modelu } // ABu 06.05.04 poczatek wyliczania obrotow wozkow ********************** void TDynamicObject::ABuBogies() { // Obracanie wozkow na zakretach. Na razie // uwzględnia tylko zakręty, // bez zadnych gorek i innych przeszkod. if ((smBogie[0] != NULL) && (smBogie[1] != NULL)) { // modelRot.z=ABuAcos(Axle0.pPosition-Axle1.pPosition); //kąt obrotu pojazdu // [rad] // bogieRot[0].z=ABuAcos(Axle0.pPosition-Axle3.pPosition); bogieRot[0].z = Axle0.vAngles.z; bogieRot[0] = RadToDeg(modelRot - bogieRot[0]); // mnożenie wektora przez stałą smBogie[0]->SetRotateXYZ(bogieRot[0]); // bogieRot[1].z=ABuAcos(Axle2.pPosition-Axle1.pPosition); bogieRot[1].z = Axle1.vAngles.z; bogieRot[1] = RadToDeg(modelRot - bogieRot[1]); smBogie[1]->SetRotateXYZ(bogieRot[1]); } }; // ABu 06.05.04 koniec wyliczania obrotow wozkow ************************ // ABu 16.03.03 sledzenie toru przed obiektem: ************************** void TDynamicObject::ABuCheckMyTrack() { // Funkcja przypisujaca obiekt // prawidlowej tablicy Dynamics, // bo gdzies jest jakis blad i wszystkie obiekty z danego // pociagu na poczatku stawiane sa na jednym torze i wpisywane // do jednej tablicy. Wykonuje sie tylko raz - po to 'ABuChecked' TTrack *OldTrack = MyTrack; TTrack *NewTrack = Axle0.GetTrack(); if( NewTrack != OldTrack ) { if( OldTrack ) { OldTrack->RemoveDynamicObject( this ); } NewTrack->AddDynamicObject(this); } iAxleFirst = 0; // pojazd powiązany z przednią osią - Axle0 } // Ra: w poniższej funkcji jest problem ze sprzęgami TDynamicObject * TDynamicObject::ABuFindObject( int &Foundcoupler, double &Distance, TTrack const *Track, int const Direction, int const Mycoupler ) const { // Zwraca wskaźnik najbliższego obiektu znajdującego się // na torze w określonym kierunku, ale tylko wtedy, kiedy // obiekty mogą się zderzyć, tzn. nie mijają się. // WE: // Track - tor, na ktorym odbywa sie poszukiwanie, // Direction - kierunek szukania na torze (+1:w stronę Point2, -1:w stronę Point1) // Mycoupler - nr sprzegu obiektu szukajacego; // WY: // wskaznik do znalezionego obiektu. // Foundcoupler - nr sprzegu znalezionego obiektu // Distance - distance to found object if( true == Track->Dynamics.empty() ) { // sens szukania na tym torze jest tylko, gdy są na nim pojazdy Distance += Track->Length(); // doliczenie długości odcinka do przeskanowanej odległości return nullptr; // nie ma pojazdów na torze, to jest NULL } double distance = Track->Length(); // najmniejsza znaleziona odleglość (zaczynamy od długości toru) double myposition; // pozycja szukającego na torze TDynamicObject *foundobject = nullptr; if( MyTrack == Track ) { // gdy szukanie na tym samym torze myposition = RaTranslationGet(); // położenie wózka względem Point1 toru } else { // gdy szukanie na innym torze if( Direction > 0 ) { // szukanie w kierunku Point2 (od zera) - jesteśmy w Point1 myposition = 0; } else { // szukanie w kierunku Point1 (do zera) - jesteśmy w Point2 myposition = distance; } } double objectposition; // robocza odległość od kolejnych pojazdów na danym odcinku for( auto dynamic : Track->Dynamics ) { if( dynamic == this ) { continue; } // szukający się nie liczy /* if( ( ( dynamic == PrevConnected ) && ( MoverParameters->Couplers[ TMoverParameters::side::front ].CouplingFlag != coupling::faux ) ) || ( ( dynamic == NextConnected ) && ( MoverParameters->Couplers[ TMoverParameters::side::rear ].CouplingFlag != coupling::faux ) ) ){ // stop-gap check to prevent 'detection' of attached vehicles // which seems to be a side-effect of inaccurate location calculation in 'simple' mode? // TODO: investigate actual cause continue; } */ if( Direction > 0 ) { // jeśli szukanie w kierunku Point2 objectposition = ( dynamic->RaTranslationGet() ) - myposition; // odległogłość tamtego od szukającego if( ( objectposition > 0 ) && ( objectposition < distance ) ) { // gdy jest po właściwej stronie i bliżej niż jakiś wcześniejszy Foundcoupler = ( dynamic->RaDirectionGet() > 0 ) ? 1 : 0; // to, bo (ScanDir>=0) } else { continue; } } else { objectposition = myposition - ( dynamic->RaTranslationGet() ); //???-przesunięcie wózka względem Point1 toru if( ( objectposition > 0 ) && ( objectposition < distance ) ) { Foundcoupler = ( dynamic->RaDirectionGet() > 0 ) ? 0 : 1; // odwrotnie, bo (ScanDir<0) } else { continue; } } if( Track->iCategoryFlag & 254 ) { // trajektoria innego typu niż tor kolejowy // dla torów nie ma sensu tego sprawdzać, rzadko co jedzie po jednej szynie i się mija // Ra: mijanie samochodów wcale nie jest proste // Przesuniecie wzgledne pojazdow. Wyznaczane, zeby sprawdzic, // czy pojazdy faktycznie sie zderzaja (moga byc przesuniete // w/m siebie tak, ze nie zachodza na siebie i wtedy sie mijaja). double relativeoffset; // wzajemna odległość poprzeczna if( Foundcoupler != Mycoupler ) { // facing the same direction relativeoffset = std::abs( MoverParameters->OffsetTrackH - dynamic->MoverParameters->OffsetTrackH ); } else { relativeoffset = std::abs( MoverParameters->OffsetTrackH + dynamic->MoverParameters->OffsetTrackH ); } if( relativeoffset + relativeoffset > MoverParameters->Dim.W + dynamic->MoverParameters->Dim.W ) { // odległość większa od połowy sumy szerokości - kolizji nie będzie continue; } // jeśli zahaczenie jest niewielkie, a jest miejsce na poboczu, to zjechać na pobocze } foundobject = dynamic; // potencjalna kolizja distance = objectposition; // odleglość pomiędzy aktywnymi osiami pojazdów } Distance += distance; // doliczenie odległości przeszkody albo długości odcinka do przeskanowanej odległości return foundobject; } int TDynamicObject::DettachStatus(int dir) { // sprawdzenie odległości sprzęgów // rzeczywistych od strony (dir): // 0=przód,1=tył // Ra: dziwne, że ta funkcja nie jest używana if( MoverParameters->Couplers[ dir ].CouplingFlag == coupling::faux ) { return 0; // jeśli nic nie podłączone, to jest OK } return (MoverParameters->DettachStatus(dir)); // czy jest w odpowiedniej odległości? } int TDynamicObject::Dettach(int dir) { // rozłączenie sprzęgów rzeczywistych od // strony (dir): 0=przód,1=tył // zwraca maskę bitową aktualnych sprzegów (0 jeśli rozłączony) if (ctOwner) { // jeśli pojazd ma przypisany obiekt nadzorujący skład, to póki // są wskaźniki TDynamicObject *d = this; while (d) { d->ctOwner = NULL; // usuwanie właściciela d = d->Prev(); } d = Next(); while (d) { d->ctOwner = NULL; // usuwanie właściciela d = d->Next(); // i w drugą stronę } } if( MoverParameters->Couplers[ dir ].CouplingFlag ) { // odczepianie, o ile coś podłączone MoverParameters->Dettach( dir ); /* if( true == MoverParameters->Dettach( dir ) ) { auto *othervehicle { dir ? NextConnected : PrevConnected }; auto const othercoupler { dir ? NextConnectedNo() : PrevConnectedNo() }; ( othercoupler ? othervehicle->NextConnected : othervehicle->PrevConnected ) = nullptr; ( dir ? NextConnected : PrevConnected ) = nullptr; }; */ } // sprzęg po rozłączaniu (czego się nie da odpiąć return MoverParameters->Couplers[dir].CouplingFlag; } void TDynamicObject::couple( int const Side ) { auto const &neighbour { MoverParameters->Neighbours[ Side ] }; if( neighbour.vehicle == nullptr ) { return; } auto const &coupler { MoverParameters->Couplers[ Side ] }; auto *othervehicle { neighbour.vehicle }; auto *othervehicleparams{ othervehicle->MoverParameters }; auto const &othercoupler { othervehicleparams->Couplers[ neighbour.vehicle_end ] }; if( coupler.CouplingFlag == coupling::faux ) { // najpierw hak if( ( coupler.AllowedFlag & othercoupler.AllowedFlag & coupling::coupler ) == coupling::coupler ) { if( MoverParameters->Attach( Side, neighbour.vehicle_end, othervehicleparams, coupling::coupler ) ) { // one coupling type per key press return; } else { WriteLog( "Mechanical coupling failed." ); } } } if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::brakehose ) ) { // pneumatyka if( ( coupler.AllowedFlag & othercoupler.AllowedFlag & coupling::brakehose ) == coupling::brakehose ) { if( MoverParameters->Attach( Side, neighbour.vehicle_end, othervehicleparams, ( coupler.CouplingFlag | coupling::brakehose ) ) ) { SetPneumatic( Side != 0, true ); othervehicle->SetPneumatic( Side != 0, true ); // one coupling type per key press return; } } } if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::mainhose ) ) { // zasilajacy if( ( coupler.AllowedFlag & othercoupler.AllowedFlag & coupling::mainhose ) == coupling::mainhose ) { if( MoverParameters->Attach( Side, neighbour.vehicle_end, othervehicleparams, ( coupler.CouplingFlag | coupling::mainhose ) ) ) { SetPneumatic( Side != 0, false ); othervehicle->SetPneumatic( Side != 0, false ); // one coupling type per key press return; } } } if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::control ) ) { // ukrotnionko if( ( ( coupler.AllowedFlag & othercoupler.AllowedFlag & coupling::control ) == coupling::control ) && ( coupler.control_type == othercoupler.control_type ) ) { if( MoverParameters->Attach( Side, neighbour.vehicle_end, othervehicleparams, ( coupler.CouplingFlag | coupling::control ) ) ) { // one coupling type per key press return; } } } if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::gangway ) ) { // mostek if( ( coupler.AllowedFlag & othercoupler.AllowedFlag & coupling::gangway ) == coupling::gangway ) { if( MoverParameters->Attach( Side, neighbour.vehicle_end, othervehicleparams, ( coupler.CouplingFlag | coupling::gangway ) ) ) { // one coupling type per key press return; } } } if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::heating ) ) { // heating if( ( coupler.AllowedFlag & othercoupler.AllowedFlag & coupling::heating ) == coupling::heating ) { if( MoverParameters->Attach( Side, neighbour.vehicle_end, othervehicleparams, ( coupler.CouplingFlag | coupling::heating ) ) ) { // one coupling type per key press return; } } } } int TDynamicObject::uncouple( int const Side ) { if( ( DettachStatus( Side ) >= 0 ) || ( true == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::permanent ) ) ) { // can't uncouple, return existing coupling state return MoverParameters->Couplers[ Side ].CouplingFlag; } // jeżeli sprzęg niezablokowany, jest co odczepić i się da auto const couplingflag { Dettach( Side ) }; return couplingflag; } bool TDynamicObject::attach_coupler_adapter( int const Side, bool const Enforce ) { auto &coupler { MoverParameters->Couplers[ Side ] }; // sanity check(s) if( coupler.type() == TCouplerType::Automatic ) { return false; } auto const *neighbour { MoverParameters->Neighbours[ Side ].vehicle }; if( ( neighbour == nullptr ) || ( MoverParameters->Neighbours[ Side ].distance > 25.0 ) ) { // can only acquire the adapter from a nearby enough vehicle return false; } // TBD: empty struct instead of fallback defaults, to allow vehicles without adapter? auto adapterdata { coupleradapter_data { { 0.085f, 0.95f }, "tabor/polsprzeg" } }; if( false == neighbour->m_coupleradapter.model.empty() ) { // explicit coupler adapter definition overrides default parameters adapterdata = neighbour->m_coupleradapter; } if( ( MoverParameters->Neighbours[ Side ].distance - adapterdata.position.x < 0.5 ) && ( false == Enforce ) ) { // arbitrary amount of free room required to install the adapter // NOTE: this also covers cases with established physical connection return false; } coupler.adapter_type = TCouplerType::Automatic; coupler.adapter_length = adapterdata.position.x; coupler.adapter_height = adapterdata.position.y; // audio flag, visuals update coupler.sounds |= sound::attachadapter; m_coupleradapters[ Side ] = TModelsManager::GetModel( adapterdata.model ); return true; } bool TDynamicObject::remove_coupler_adapter( int const Side ) { auto &coupler{ MoverParameters->Couplers[ Side ] }; if( coupler.adapter_type == TCouplerType::NoCoupler ) { return false; } // TODO: sanity check(s) if( coupler.Connected != nullptr ) { // TBD: disallow instead adapter removal if it's coupled with another vehicle? uncouple( Side ); } coupler.adapter_type = TCouplerType::NoCoupler; coupler.adapter_length = 0.0; coupler.adapter_height = 0.0; // audio flag, visuals update coupler.sounds |= sound::removeadapter; m_coupleradapters[ Side ] = nullptr; return true; } TDynamicObject::TDynamicObject() { modelShake = Math3D::vector3(0, 0, 0); // fTrackBlock = 10000.0; // brak przeszkody na drodze btnOn = false; vUp = vWorldUp; vFront = vWorldFront; vLeft = vWorldLeft; iNumAxles = 0; MoverParameters = NULL; Mechanik = NULL; MechInside = false; // McZapkie-270202 Controller = AIdriver; bDisplayCab = false; // 030303 // NextConnected = PrevConnected = NULL; // NextConnectedNo = PrevConnectedNo = 2; // ABu: Numery sprzegow. 2=nie podłączony bEnabled = true; MyTrack = NULL; // McZapkie-260202 dWheelAngle[0] = 0.0; dWheelAngle[1] = 0.0; dWheelAngle[2] = 0.0; // Winger 160204 - pantografy // PantVolume = 3.5; NoVoltTime = 0; mdModel = NULL; mdKabina = NULL; // smWiazary[0]=smWiazary[1]=NULL; smWahacze[0] = smWahacze[1] = smWahacze[2] = smWahacze[3] = NULL; fWahaczeAmp = 0; smBrakeMode = NULL; smLoadMode = NULL; mdLoad = NULL; mdLowPolyInt = NULL; //smMechanik0 = smMechanik1 = NULL; smBuforLewy[0] = smBuforLewy[1] = NULL; smBuforPrawy[0] = smBuforPrawy[1] = NULL; smBogie[0] = smBogie[1] = NULL; bogieRot[0] = bogieRot[1] = Math3D::vector3(0, 0, 0); modelRot = Math3D::vector3(0, 0, 0); cp1 = cp2 = sp1 = sp2 = 0; iDirection = 1; // stoi w kierunku tradycyjnym (0, gdy jest odwrócony) iAxleFirst = 0; // numer pierwszej osi w kierunku ruchu (przełączenie // następuje, gdy osie sa na // tym samym torze) RaLightsSet(0, 0); // początkowe zerowanie stanu świateł // Ra: domyślne ilości animacji dla zgodności wstecz (gdy brak ilości podanych // w MMD) // ustawienie liczby modeli animowanych podczas konstruowania obiektu a nie na 0 // prowadzi prosto do wysypów jeśli źle zdefiniowane mmd iAnimations = 0; // na razie nie ma żadnego pAnimated = NULL; fShade = 0.0; // standardowe oświetlenie na starcie iHornWarning = 1; // numer syreny do użycia po otrzymaniu sygnału do jazdy asDestination = "none"; // stojący nigdzie nie jedzie pValveGear = NULL; // Ra: tymczasowo iCabs = 0; // maski bitowe modeli kabin smBrakeSet = NULL; // nastawa hamulca (wajcha) smLoadSet = NULL; // nastawa ładunku (wajcha) smWiper = NULL; // wycieraczka (poniekąd też wajcha) ctOwner = NULL; // na początek niczyj iOverheadMask = 0; // maska przydzielana przez AI pojazdom posiadającym // pantograf, aby wymuszały // jazdę bezprądową tmpTraction.TractionVoltage = 0; // Ra 2F1H: prowizorka, trzeba przechować // napięcie, żeby nie wywalało WS pod // izolatorem fAdjustment = 0.0; // korekcja odległości pomiędzy wózkami (np. na łukach) } TDynamicObject::~TDynamicObject() { SafeDelete( Mechanik ); SafeDelete( MoverParameters ); SafeDeleteArray( pAnimated ); // lista animowanych submodeli } void TDynamicObject::place_on_track(TTrack *Track, double fDist, bool Reversed) { for( auto &axle : m_axlesounds ) { // wyszukiwanie osi (0 jest na końcu, dlatego dodajemy długość?) axle.distance = ( Reversed ? -axle.offset : ( axle.offset + MoverParameters->Dim.L ) ) + fDist; } double fAxleDistHalf = fAxleDist * 0.5; // przesuwanie pojazdu tak, aby jego początek był we wskazanym miejcu fDist -= 0.5 * MoverParameters->Dim.L; // dodajemy pół długości pojazdu, bo ustawiamy jego środek (zliczanie na minus) switch (iNumAxles) { // Ra: pojazdy wstawiane są na tor początkowy, a potem przesuwane case 2: // ustawianie osi na torze Axle0.Init(Track, this, iDirection ? 1 : -1); Axle0.Reset(); Axle0.Move((iDirection ? fDist : -fDist) + fAxleDistHalf, false); Axle1.Init(Track, this, iDirection ? 1 : -1); Axle1.Reset(); Axle1.Move((iDirection ? fDist : -fDist) - fAxleDistHalf, false); // false, żeby nie generować eventów break; } // potrzebne do wyliczenia aktualnej pozycji; nie może być zero, bo nie przeliczy pozycji // teraz jeszcze trzeba przypisać pojazdy do nowego toru, bo przesuwanie początkowe osi nie // zrobiło tego Move( 0.0001 ); ABuCheckMyTrack(); // zmiana toru na ten, co oś Axle0 (oś z przodu) } double TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" std::string BaseDir, // z którego katalogu wczytany, np. "PKP/EU07" std::string asReplacableSkin, // nazwa wymiennej tekstury std::string Type_Name, // nazwa CHK/MMD, np. "303E" TTrack *Track, // tor początkowy wstwawienia (początek składu) double fDist, // dystans względem punktu 1 std::string DriverType, // typ obsady double fVel, // prędkość początkowa std::string TrainName, // nazwa składu, np. "PE2307" albo Vmax, jeśli pliku nie ma a są cyfry float Load, // ilość ładunku std::string LoadType, // nazwa ładunku bool Reversed, // true, jeśli ma stać odwrotnie w składzie std::string MoreParams // dodatkowe parametry wczytywane w postaci tekstowej ) { // Ustawienie początkowe pojazdu iDirection = (Reversed ? 0 : 1); // Ra: 0, jeśli ma być wstawiony jako obrócony tyłem asBaseDir = szDynamicPath + BaseDir + "/"; // McZapkie-310302 replace_slashes( asBaseDir ); asName = Name; std::string asAnimName; // zmienna robocza do wyszukiwania osi i wózków // Ra: zmieniamy znaczenie obsady na jednoliterowe, żeby dosadzić kierownika if (DriverType == "headdriver") DriverType = "1"; // sterujący kabiną +1 else if (DriverType == "reardriver") DriverType = "2"; // sterujący kabiną -1 else if (DriverType == "passenger") DriverType = ""; // legacy type, no longer needed else if (DriverType == "nobody") DriverType = ""; // nikt nie siedzi int Cab = 0; // numer kabiny z obsadą (nie można zająć obu) if (DriverType == "1") // od przodu składu Cab = 1; // iDirection?1:-1; //iDirection=1 gdy normalnie, =0 odwrotnie else if (DriverType == "2") // od tyłu składu Cab = -1; // iDirection?-1:1; /* // NOTE: leave passenger in the middle section, this is most likely to be 'passenger' section in MU trains else if (DriverType == "p") { if (Random(6) < 3) Cab = 1; else Cab = -1; // losowy przydział kabiny } */ // utworzenie parametrów fizyki MoverParameters = new TMoverParameters(iDirection ? fVel : -fVel, Type_Name, asName, Cab); // McZapkie: TypeName musi byc nazwą CHK/MMD pojazdu if (!MoverParameters->LoadFIZ(asBaseDir)) { // jak wczytanie CHK się nie uda, to błąd if (ConversionError == 666) ErrorLog( "Bad vehicle: failed do locate definition file \"" + BaseDir + "/" + Type_Name + ".fiz" + "\"" ); else { ErrorLog( "Bad vehicle: failed to load definition from file \"" + BaseDir + "/" + Type_Name + ".fiz\" (error " + to_string( ConversionError ) + ")" ); } return 0.0; // zerowa długość to brak pojazdu } // load the cargo now that we know whether the vehicle will allow it MoverParameters->AssignLoad( LoadType, Load ); MoverParameters->ComputeMass(); bool driveractive = (fVel != 0.0); // jeśli prędkość niezerowa, to aktywujemy ruch if (!MoverParameters->CheckLocomotiveParameters( driveractive, (fVel > 0 ? 1 : -1) * Cab * (iDirection ? 1 : -1))) // jak jedzie lub obsadzony to gotowy do drogi { Error("Parameters mismatch: dynamic object " + asName + " from \"" + BaseDir + "/" + Type_Name + "\"" ); return 0.0; // zerowa długość to brak pojazdu } // controller position MoverParameters->MainCtrlPos = MoverParameters->MainCtrlNoPowerPos(); // ustawienie pozycji hamulca MoverParameters->LocalBrakePosA = 0.0; if (driveractive) { if (Cab == 0) MoverParameters->BrakeCtrlPos = static_cast( std::floor(MoverParameters->Handle->GetPos(bh_NP)) ); else MoverParameters->BrakeCtrlPos = static_cast( std::floor(MoverParameters->Handle->GetPos(bh_RP)) ); // engage independent brake if applicable, if the vehicle is to be on standby // NOTE: with more than one driver in the consist this is likely to go awery, since all but one will be sent to sleep // TBD, TODO: have the virtual helper release independent brakes during consist check? if( DriverType != "" ) { if( MoverParameters->LocalBrake != TLocalBrake::ManualBrake ) { if( fVel < 1.0 ) { MoverParameters->IncLocalBrakeLevel( LocalBrakePosNo ); } } } } else MoverParameters->BrakeCtrlPos = static_cast( std::floor(MoverParameters->Handle->GetPos(bh_NP)) ); // poprawienie hamulca po ewentualnym przestawieniu przez Pascal // TODO: check if needed, we're not in Pascal anymore, Toto MoverParameters->BrakeLevelSet(MoverParameters->BrakeCtrlPos); // dodatkowe parametry yB MoreParams += "."; // wykonuje o jedną iterację za mało, więc trzeba mu dodać // kropkę na koniec size_t kropka = MoreParams.find("."); // znajdź kropke std::string ActPar; // na parametry while (kropka != std::string::npos) // jesli sa kropki jeszcze { size_t dlugosc = MoreParams.length(); ActPar = ToUpper(MoreParams.substr(0, kropka)); // pierwszy parametr; MoreParams = MoreParams.substr(kropka + 1, dlugosc - kropka); // reszta do dalszej obrobki kropka = MoreParams.find("."); if (ActPar.substr(0, 1) == "B") // jesli hamulce { // sprawdzanie kolejno nastaw WriteLog("Wpis hamulca: " + ActPar); if (ActPar.find('G') != std::string::npos) { MoverParameters->BrakeDelaySwitch(bdelay_G); } if( ActPar.find( 'P' ) != std::string::npos ) { MoverParameters->BrakeDelaySwitch(bdelay_P); } if( ActPar.find( 'R' ) != std::string::npos ) { MoverParameters->BrakeDelaySwitch(bdelay_R); } if( ActPar.find( 'M' ) != std::string::npos ) { MoverParameters->BrakeDelaySwitch(bdelay_R); MoverParameters->BrakeDelaySwitch(bdelay_R + bdelay_M); } // wylaczanie hamulca if (ActPar.find("<>") != std::string::npos) // wylaczanie na probe hamowania naglego { MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } if (ActPar.find('0') != std::string::npos) // wylaczanie na sztywno { MoverParameters->Hamulec->ForceEmptiness(); MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } if (ActPar.find('E') != std::string::npos) // oprozniony { MoverParameters->Hamulec->ForceEmptiness(); MoverParameters->Pipe->CreatePress(0); MoverParameters->Pipe2->CreatePress(0); } if (ActPar.find('Q') != std::string::npos) // oprozniony { MoverParameters->Hamulec->ForceEmptiness(); MoverParameters->Pipe->CreatePress(0.0); MoverParameters->PipePress = 0.0; MoverParameters->Pipe2->CreatePress(0.0); MoverParameters->ScndPipePress = 0.0; MoverParameters->PantVolume = 0.1; MoverParameters->PantPress = 0.0; MoverParameters->CompressedVolume = 0.0; } if (ActPar.find('1') != std::string::npos) // wylaczanie 10% { if (Random(10) < 1) // losowanie 1/10 { 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->ForceEmptiness(); MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz } if (MoverParameters->BrakeCylMult[2] * MoverParameters->BrakeCylMult[1] > 0.01) // jesli jest nastawiacz mechaniczny PL { float rnd = Random(100); if (rnd < 20) // losowanie 20/100 usrednienie { MoverParameters->BrakeCylMult[2] = MoverParameters->BrakeCylMult[1] = (MoverParameters->BrakeCylMult[2] + MoverParameters->BrakeCylMult[1]) / 2; } else if (rnd < 70) // losowanie 70/100-20/100 oslabienie { MoverParameters->BrakeCylMult[1] = MoverParameters->BrakeCylMult[1] * 0.50; MoverParameters->BrakeCylMult[2] = MoverParameters->BrakeCylMult[2] * 0.75; } else if (rnd < 80) // losowanie 80/100-70/100 tylko prozny { MoverParameters->BrakeCylMult[2] = MoverParameters->BrakeCylMult[1]; } else // tylko ladowny { MoverParameters->BrakeCylMult[1] = MoverParameters->BrakeCylMult[2]; } } } // nastawianie ladunku if (ActPar.find('T') != std::string::npos) // prozny { MoverParameters->DecBrakeMult(); MoverParameters->DecBrakeMult(); } // dwa razy w dol if (ActPar.find('H') != std::string::npos) // ladowny I (dla P-Ł dalej prozny) { MoverParameters->IncBrakeMult(); MoverParameters->IncBrakeMult(); MoverParameters->DecBrakeMult(); } // dwa razy w gore i obniz if (ActPar.find('F') != std::string::npos) // ladowny II { MoverParameters->IncBrakeMult(); MoverParameters->IncBrakeMult(); } // dwa razy w gore if (ActPar.find('N') != std::string::npos) // parametr neutralny { } } // koniec hamulce else if( ( ActPar.size() >= 3 ) && ( ActPar[ 0 ] == 'W' ) ) { // wheel ActPar.erase( 0, 1 ); auto fixedflatsize { 0 }; auto randomflatsize { 0 }; auto flatchance { 100 }; while( false == ActPar.empty() ) { // TODO: convert this whole copy and paste mess to something more elegant one day switch( ActPar[ 0 ] ) { case 'F': { // fixed flat size auto const indexstart { 1 }; auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) }; fixedflatsize = std::atoi( ActPar.substr( indexstart, indexend ).c_str() ); ActPar.erase( 0, indexend ); break; } case 'R': { // random flat size auto const indexstart { 1 }; auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) }; randomflatsize = std::atoi( ActPar.substr( indexstart, indexend ).c_str() ); ActPar.erase( 0, indexend ); break; } case 'P': { // random flat probability auto const indexstart { 1 }; auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) }; flatchance = std::atoi( ActPar.substr( indexstart, indexend ).c_str() ); ActPar.erase( 0, indexend ); break; } case 'H': { // truck hunting auto const indexstart { 1 }; auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) }; auto const huntingchance { std::atoi( ActPar.substr( indexstart, indexend ).c_str() ) }; MoverParameters->TruckHunting = ( Random( 0, 100 ) < huntingchance ); ActPar.erase( 0, indexend ); break; } default: { // unrecognized key ActPar.erase( 0, 1 ); break; } } } if( Random( 0, 100 ) <= flatchance ) { MoverParameters->WheelFlat += fixedflatsize + Random( 0, randomflatsize ); } } // wheel else if( ( ActPar.size() >= 2 ) && ( ActPar[ 0 ] == 'T' ) ) { // temperature ActPar.erase( 0, 1 ); auto setambient { false }; while( false == ActPar.empty() ) { switch( ActPar[ 0 ] ) { case 'A': { // cold start, set all temperatures to ambient level setambient = true; ActPar.erase( 0, 1 ); break; } default: { // unrecognized key ActPar.erase( 0, 1 ); break; } } } if( true == setambient ) { // TODO: pull ambient temperature from environment data MoverParameters->dizel_HeatSet( Global.AirTemperature ); } } // temperature /* else if (ActPar.substr(0, 1) == "") // tu mozna wpisac inny prefiks i inne rzeczy { // jakies inne prefiksy } */ } // koniec while kropka if (MoverParameters->CategoryFlag & 2) // jeśli samochód { // ustawianie samochodow na poboczu albo na środku drogi if( Track->fTrackWidth < 3.5 ) // jeśli droga wąska MoverParameters->OffsetTrackH = 0.0; // to stawiamy na środku, niezależnie od stanu // ruchu else if( driveractive ) {// od 3.5m do 8.0m jedzie po środku pasa, dla // szerszych w odległości // 1.5m auto const lanefreespace = 0.5 * Track->fTrackWidth - MoverParameters->Dim.W; MoverParameters->OffsetTrackH = ( lanefreespace > 1.5 ? -0.5 * MoverParameters->Dim.W - 0.75 : // wide road, keep near centre with safe margin ( lanefreespace > 0.1 ? -0.25 * Track->fTrackWidth : // narrow fit, stay on the middle -0.5 * MoverParameters->Dim.W - 0.075 ) ); // too narrow, just keep some minimal clearance } else // jak stoi, to kołem na poboczu i pobieramy szerokość razem z // poboczem, ale nie z // chodnikiem MoverParameters->OffsetTrackH = -0.5 * (Track->WidthTotal() - MoverParameters->Dim.W) + 0.05; iHornWarning = 0; // nie będzie trąbienia po podaniu zezwolenia na jazdę if (fDist < 0.0) //-0.5*MoverParameters->Dim.L) //jeśli jest przesunięcie do tyłu if (!Track->CurrentPrev()) // a nie ma tam odcinka i trzeba by coś // wygenerować fDist = -fDist; // to traktujemy, jakby przesunięcie było w drugą stronę } create_controller( DriverType, !TrainName.empty() ); ShakeSpring.Init( 125.0 ); BaseShake = baseshake_config {}; ShakeState = shake_state {}; // McZapkie-250202 /* iAxles = std::min( MoverParameters->NAxles, MaxAxles ); // ilość osi */ // wczytywanie z pliku nazwatypu.mmd, w tym model erase_extension( asReplacableSkin ); LoadMMediaFile(Type_Name, asReplacableSkin); // McZapkie-100402: wyszukiwanie submodeli sprzegów btCoupler1.Init( "coupler1", mdModel ); // false - ma być wyłączony btCoupler2.Init( "coupler2", mdModel ); // brake hoses btCPneumatic1.Init("cpneumatic1", mdModel); btCPneumatic2.Init("cpneumatic2", mdModel); btCPneumatic1r.Init("cpneumatic1r", mdModel); btCPneumatic2r.Init("cpneumatic2r", mdModel); // main hoses btPneumatic1.Init("pneumatic1", mdModel); btPneumatic2.Init("pneumatic2", mdModel); btPneumatic1r.Init("pneumatic1r", mdModel); btPneumatic2r.Init("pneumatic2r", mdModel); // control cables btCCtrl1.Init( "cctrl1", mdModel, false); btCCtrl2.Init( "cctrl2", mdModel, false); // gangways btCPass1.Init( "cpass1", mdModel, false); btCPass2.Init( "cpass2", mdModel, false); // sygnaly // ABu 060205: Zmiany dla koncowek swiecacych: btEndSignals11.Init( "endsignal13", mdModel, false); btEndSignals21.Init( "endsignal23", mdModel, false); btEndSignals13.Init( "endsignal12", mdModel, false); btEndSignals23.Init( "endsignal22", mdModel, false); iInventory[ end::front ] |= btEndSignals11.Active() ? light::redmarker_left : 0; // informacja, czy ma poszczególne światła iInventory[ end::front ] |= btEndSignals13.Active() ? light::redmarker_right : 0; iInventory[ end::rear ] |= btEndSignals21.Active() ? light::redmarker_left : 0; iInventory[ end::rear ] |= btEndSignals23.Active() ? light::redmarker_right : 0; // ABu: to niestety zostawione dla kompatybilnosci modeli: btEndSignals1.Init( "endsignals1", mdModel, false); btEndSignals2.Init( "endsignals2", mdModel, false); btEndSignalsTab1.Init( "endtab1", mdModel, false); btEndSignalsTab2.Init( "endtab2", mdModel, false); iInventory[ end::front ] |= btEndSignals1.Active() ? ( light::redmarker_left | light::redmarker_right ) : 0; iInventory[ end::front ] |= btEndSignalsTab1.Active() ? light::rearendsignals : 0; // tabliczki blaszane iInventory[ end::rear ] |= btEndSignals2.Active() ? ( light::redmarker_left | light::redmarker_right ) : 0; iInventory[ end::rear ] |= btEndSignalsTab2.Active() ? light::rearendsignals : 0; // ABu Uwaga! tu zmienic w modelu! btHeadSignals11.Init( "headlamp13", mdModel); // lewe btHeadSignals12.Init( "headlamp11", mdModel); // górne btHeadSignals13.Init( "headlamp12", mdModel); // prawe btHeadSignals21.Init( "headlamp23", mdModel); btHeadSignals22.Init( "headlamp21", mdModel); btHeadSignals23.Init( "headlamp22", mdModel); iInventory[ end::front ] |= btHeadSignals11.Active() ? light::headlight_left : 0; iInventory[ end::front ] |= btHeadSignals12.Active() ? light::headlight_upper : 0; iInventory[ end::front ] |= btHeadSignals13.Active() ? light::headlight_right : 0; iInventory[ end::rear ] |= btHeadSignals21.Active() ? light::headlight_left : 0; iInventory[ end::rear ] |= btHeadSignals22.Active() ? light::headlight_upper : 0; iInventory[ end::rear ] |= btHeadSignals23.Active() ? light::headlight_right : 0; btMechanik1.Init( "mechanik1", mdLowPolyInt, false); btMechanik2.Init( "mechanik2", mdLowPolyInt, false); if( MoverParameters->dizel_heat.water.config.shutters ) { btShutters1.Init( "shutters1", mdModel, false ); } if( MoverParameters->dizel_heat.water_aux.config.shutters ) { btShutters2.Init( "shutters2", mdModel, false ); } TurnOff(); // resetowanie zmiennych submodeli if( mdLowPolyInt != nullptr ) { // check the low poly interior for potential compartments of interest, ie ones which can be individually lit // TODO: definition of relevant compartments in the .mmd file TSubModel *submodel { nullptr }; if( ( submodel = mdLowPolyInt->GetFromName( "cab0" ) ) != nullptr ) { Sections.push_back( { submodel, nullptr, 0, 0.0f } ); LowPolyIntCabs[ 0 ] = submodel; } if( ( submodel = mdLowPolyInt->GetFromName( "cab1" ) ) != nullptr ) { Sections.push_back( { submodel, nullptr, 0, 0.0f } ); LowPolyIntCabs[ 1 ] = submodel; } if( ( submodel = mdLowPolyInt->GetFromName( "cab2" ) ) != nullptr ) { Sections.push_back( { submodel, nullptr, 0, 0.0f } ); LowPolyIntCabs[ 2 ] = submodel; } // passenger car compartments std::vector nameprefixes = { "corridor", "korytarz", "compartment", "przedzial" }; for( auto const &nameprefix : nameprefixes ) { init_sections( mdLowPolyInt, nameprefix, MoverParameters->CompartmentLights.start_type == start_t::manual ); } } // destination sign if( mdModel ) { init_destination( mdModel ); } // 'external_load' is an optional special section in the main model, pointing to submodel of external load if( mdModel ) { init_sections( mdModel, "external_load", false ); } update_load_sections(); update_load_visibility(); // wyszukiwanie zderzakow if( mdModel ) { // jeśli ma w czym szukać for( int i = 0; i < 2; i++ ) { asAnimName = std::string( "buffer_left0" ) + to_string( i + 1 ); smBuforLewy[ i ] = mdModel->GetFromName( asAnimName ); if( smBuforLewy[ i ] ) smBuforLewy[ i ]->WillBeAnimated(); // ustawienie flagi animacji asAnimName = std::string( "buffer_right0" ) + to_string( i + 1 ); smBuforPrawy[ i ] = mdModel->GetFromName( asAnimName ); if( smBuforPrawy[ i ] ) smBuforPrawy[ i ]->WillBeAnimated(); } } if( Track->fSoundDistance > 0.f ) { for( auto &axle : m_axlesounds ) { // wyszukiwanie osi (0 jest na końcu, dlatego dodajemy długość?) axle.distance = clamp_circular( ( Reversed ? -axle.offset : axle.offset ) - 0.5 * MoverParameters->Dim.L + fDist, Track->fSoundDistance ); } } // McZapkie-250202 end. Track->AddDynamicObject(this); // wstawiamy do toru na pozycję 0, a potem przesuniemy // McZapkie: zmieniono na ilosc osi brane z chk // iNumAxles=(MoverParameters->NAxles>3 ? 4 : 2 ); iNumAxles = 2; // McZapkie-090402: odleglosc miedzy czopami skretu lub osiami fAxleDist = clamp( std::max( MoverParameters->BDist, MoverParameters->ADist ), 0.2, //żeby się dało wektory policzyć MoverParameters->Dim.L - 0.2 ); // nie mogą być za daleko bo będzie "walenie w mur" double fAxleDistHalf = fAxleDist * 0.5; // przesuwanie pojazdu tak, aby jego początek był we wskazanym miejcu fDist -= 0.5 * MoverParameters->Dim.L; // dodajemy pół długości pojazdu, bo ustawiamy jego środek (zliczanie na minus) switch (iNumAxles) { // Ra: pojazdy wstawiane są na tor początkowy, a potem przesuwane case 2: // ustawianie osi na torze Axle0.Init(Track, this, iDirection ? 1 : -1); Axle0.Move((iDirection ? fDist : -fDist) + fAxleDistHalf, false); Axle1.Init(Track, this, iDirection ? 1 : -1); Axle1.Move((iDirection ? fDist : -fDist) - fAxleDistHalf, false); // false, żeby nie generować eventów break; case 4: Axle0.Init(Track, this, iDirection ? 1 : -1); Axle0.Move((iDirection ? fDist : -fDist) + (fAxleDistHalf + MoverParameters->ADist * 0.5), false); Axle1.Init(Track, this, iDirection ? 1 : -1); Axle1.Move((iDirection ? fDist : -fDist) - (fAxleDistHalf + MoverParameters->ADist * 0.5), false); // Axle2.Init(Track,this,iDirection?1:-1); // Axle2.Move((iDirection?fDist:-fDist)-(fAxleDistHalf-MoverParameters->ADist*0.5),false); // Axle3.Init(Track,this,iDirection?1:-1); // Axle3.Move((iDirection?fDist:-fDist)+(fAxleDistHalf-MoverParameters->ADist*0.5),false); break; } // potrzebne do wyliczenia aktualnej pozycji; nie może być zero, bo nie przeliczy pozycji // teraz jeszcze trzeba przypisać pojazdy do nowego toru, bo przesuwanie początkowe osi nie // zrobiło tego Move( 0.0001 ); ABuCheckMyTrack(); // zmiana toru na ten, co oś Axle0 (oś z przodu) // Ra: ustawienie pozycji do obliczania sprzęgów MoverParameters->Loc = { -vPosition.x, vPosition.z, vPosition.y }; // normalnie przesuwa ComputeMovement() w Update() // ABuWozki 060504 if (mdModel) // jeśli ma w czym szukać { smBogie[0] = mdModel->GetFromName("bogie1"); // Ra: bo nazwy są małymi smBogie[1] = mdModel->GetFromName("bogie2"); if (!smBogie[0]) smBogie[0] = mdModel->GetFromName("boogie01"); // Ra: alternatywna nazwa if (!smBogie[1]) smBogie[1] = mdModel->GetFromName("boogie02"); // Ra: alternatywna nazwa if (smBogie[0]) smBogie[0]->WillBeAnimated(); if (smBogie[1]) smBogie[1]->WillBeAnimated(); } // ABu: zainicjowanie zmiennej, zeby nic sie nie ruszylo w pierwszej klatce, // potem juz liczona prawidlowa wartosc masy MoverParameters->ComputeConstans(); // wektor podłogi dla wagonów, przesuwa ładunek vFloor = Math3D::vector3(0, 0, MoverParameters->Floor); // długość większa od zera oznacza OK; 2mm docisku? return MoverParameters->Dim.L; } int TDynamicObject::init_sections( TModel3d const *Model, std::string const &Nameprefix, bool const Overrideselfillum ) { auto sectioncount = 0; auto sectionindex = 0; TSubModel *sectionsubmodel { nullptr }; do { // section names for index < 10 match either prefix0X or prefixX // section names above 10 match prefixX auto const sectionindexname { std::to_string( sectionindex ) }; sectionsubmodel = Model->GetFromName( Nameprefix + sectionindexname ); if( ( sectionsubmodel == nullptr ) && ( sectionindex < 10 ) ) { sectionsubmodel = Model->GetFromName( Nameprefix + "0" + sectionindexname ); } if( sectionsubmodel != nullptr ) { // HACK: disable automatic self-illumination threshold, at least until 3d model update if( Overrideselfillum ) { sectionsubmodel->SetSelfIllum( 2.0f, true, false ); } Sections.push_back( { sectionsubmodel, nullptr, // pointers to load sections are generated afterwards 0, 0.0f } ); ++sectioncount; } ++sectionindex; } while( ( sectionsubmodel != nullptr ) || ( sectionindex < 2 ) ); // chain can start from prefix00 or prefix01 return sectioncount; } bool TDynamicObject::init_destination( TModel3d *Model ) { if( Model->GetSMRoot() == nullptr ) { return false; } std::tie( DestinationSign.sign, DestinationSign.has_light ) = Model->GetSMRoot()->find_replacable4(); return DestinationSign.sign != nullptr; } void TDynamicObject::create_controller( std::string const Type, bool const Trainset ) { if( Type == "" ) { return; } // McZapkie-151102: rozkład jazdy czytany z pliku *.txt z katalogu w którym jest sceneria if( ( Type == "1" ) || ( Type == "2" ) ) { // McZapkie-110303: mechanik i rozklad tylko gdy jest obsada Mechanik = new TController( Controller, this, Aggressive ); if( false == Trainset ) { // jeśli nie w składzie // załączenie rozrządu (wirtualne kabiny) itd. Mechanik->DirectionInitial(); // tryb pociągowy z ustaloną prędkością (względem sprzęgów) Mechanik->PutCommand( "Timetable:", MoverParameters->V * 3.6 * ( iDirection ? -1.0 : 1.0 ), 0, nullptr ); } } else if( Type == "p" ) { // obserwator w charakterze pasażera // Ra: to jest niebezpieczne, bo w razie co będzie pomagał hamulcem bezpieczeństwa Mechanik = new TController( Controller, this, Easyman, false ); } } void TDynamicObject::FastMove(double fDistance) { MoverParameters->dMoveLen = MoverParameters->dMoveLen + fDistance; } void TDynamicObject::Move(double fDistance) { // przesuwanie pojazdu po // trajektorii polega na // przesuwaniu poszczególnych osi // Ra: wartość prędkości 2km/h ma ograniczyć aktywację eventów w przypadku // drgań if (Axle0.GetTrack() == Axle1.GetTrack()) // przed przesunięciem { // powiązanie pojazdu z osią można zmienić tylko wtedy, gdy skrajne osie są // na tym samym torze if (MoverParameters->Vel > 2) //|[km/h]| nie ma sensu zmiana osi, jesli pojazd drga na postoju iAxleFirst = (MoverParameters->V >= 0.0) ? 1 : 0; //[m/s] ?1:0 - aktywna druga oś w kierunku jazdy // aktualnie eventy aktywuje druga oś, żeby AI nie wyłączało sobie semafora // za szybko } if (fDistance > 0.0) { // gdy ruch w stronę sprzęgu 0, doliczyć korektę do osi 1 bEnabled &= Axle0.Move(fDistance, iAxleFirst == 0); // oś z przodu pojazdu bEnabled &= Axle1.Move(fDistance /*-fAdjustment*/, iAxleFirst != 0); // oś z tyłu pojazdu } else if (fDistance < 0.0) { // gdy ruch w stronę sprzęgu 1, doliczyć korektę do osi 0 bEnabled &= Axle1.Move(fDistance, iAxleFirst != 0); // oś z tyłu pojazdu prusza się pierwsza bEnabled &= Axle0.Move(fDistance /*-fAdjustment*/, iAxleFirst == 0); // oś z przodu pojazdu } else // gf: bez wywolania Move na postoju nie ma event0 { bEnabled &= Axle1.Move(fDistance, iAxleFirst != 0); // oś z tyłu pojazdu prusza się pierwsza bEnabled &= Axle0.Move(fDistance, iAxleFirst == 0); // oś z przodu pojazdu } if (fDistance != 0.0) // nie liczyć ponownie, jeśli stoi { // liczenie pozycji pojazdu tutaj, bo jest używane w wielu miejscach vPosition = 0.5 * (Axle1.pPosition + Axle0.pPosition); //środek między skrajnymi osiami vFront = Axle0.pPosition - Axle1.pPosition; // wektor pomiędzy skrajnymi osiami // Ra 2F1J: to nie jest stabilne (powoduje rzucanie taborem) i wymaga // dopracowania fAdjustment = vFront.Length() - fAxleDist; // na łuku będzie ujemny // if (fabs(fAdjustment)>0.02) //jeśli jest zbyt dużo, to rozłożyć na kilka przeliczeń (wygasza drgania?) //{//parę centymetrów trzeba by już skorygować; te błędy mogą się też // generować na ostrych łukach // fAdjustment*=0.5; //w jednym kroku korygowany jest ułamek błędu //} // else // fAdjustment=0.0; vFront = Normalize(vFront); // kierunek ustawienia pojazdu (wektor jednostkowy) vLeft = Normalize(CrossProduct(vWorldUp, vFront)); // wektor poziomy w lewo, // normalizacja potrzebna z powodu pochylenia (vFront) vUp = CrossProduct(vFront, vLeft); // wektor w górę, będzie jednostkowy modelRot.z = atan2(-vFront.x, vFront.z); // kąt obrotu pojazdu [rad]; z ABuBogies() auto const roll { Roll() }; // suma przechyłek if (roll != 0.0) { // wyznaczanie przechylenia tylko jeśli jest przechyłka // można by pobrać wektory normalne z toru... mMatrix.Identity(); // ta macierz jest potrzebna głównie do wyświetlania mMatrix.Rotation(roll * 0.5, vFront); // obrót wzdłuż osi o przechyłkę vUp = mMatrix * vUp; // wektor w górę pojazdu (przekręcenie na przechyłce) // vLeft=mMatrix*DynamicObject->vLeft; // vUp=CrossProduct(vFront,vLeft); //wektor w górę // vLeft=Normalize(CrossProduct(vWorldUp,vFront)); //wektor w lewo vLeft = Normalize(CrossProduct(vUp, vFront)); // wektor w lewo // vUp=CrossProduct(vFront,vLeft); //wektor w górę } mMatrix.Identity(); // to też można by od razu policzyć, ale potrzebne jest do wyświetlania mMatrix.BasisChange(vLeft, vUp, vFront); // przesuwanie jest jednak rzadziej niż renderowanie mMatrix = Inverse(mMatrix); // wyliczenie macierzy dla pojazdu (potrzebna tylko do wyświetlania?) // if (MoverParameters->CategoryFlag&2) { // przesunięcia są używane po wyrzuceniu pociągu z toru vPosition.x += MoverParameters->OffsetTrackH * vLeft.x; // dodanie przesunięcia w bok vPosition.z += MoverParameters->OffsetTrackH * vLeft.z; // vLeft jest wektorem poprzecznym // if () na przechyłce będzie dodatkowo zmiana wysokości samochodu vPosition.y += MoverParameters->OffsetTrackV; // te offsety są liczone przez moverparam } // obliczanie pozycji sprzęgów do liczenia zderzeń auto dir = (0.5 * MoverParameters->Dim.L) * vFront; // wektor sprzęgu vCoulpler[end::front] = vPosition + dir; // współrzędne sprzęgu na początku vCoulpler[end::rear] = vPosition - dir; // współrzędne sprzęgu na końcu // bCameraNear= // if (bCameraNear) //jeśli istotne są szczegóły (blisko kamery) { // przeliczenie cienia TTrack *t0 = Axle0.GetTrack(); // już po przesunięciu TTrack *t1 = Axle1.GetTrack(); if ((t0->eEnvironment == e_flat) && (t1->eEnvironment == e_flat)) // może być e_bridge... fShade = 0.0; // standardowe oświetlenie else { // jeżeli te tory mają niestandardowy stopień zacienienia // (e_canyon, e_tunnel) if (t0->eEnvironment == t1->eEnvironment) { switch (t0->eEnvironment) { // typ zmiany oświetlenia case e_canyon: fShade = 0.65f; break; // zacienienie w kanionie case e_tunnel: fShade = 0.20f; break; // zacienienie w tunelu } } else // dwa różne { // liczymy proporcję double d = Axle0.GetTranslation(); // aktualne położenie na torze if (Axle0.GetDirection() < 0) d = t0->Length() - d; // od drugiej strony liczona długość d /= fAxleDist; // rozsataw osi procentowe znajdowanie się na torze float shadefrom = 1.0f, shadeto = 1.0f; // NOTE, TODO: calculating brightness level is used enough times to warrant encapsulation into a function switch( t0->eEnvironment ) { case e_canyon: { shadeto = 0.65f; break; } case e_tunnel: { shadeto = 0.2f; break; } default: {break; } } switch( t1->eEnvironment ) { case e_canyon: { shadefrom = 0.65f; break; } case e_tunnel: { shadefrom = 0.2f; break; } default: {break; } } fShade = interpolate( shadefrom, shadeto, static_cast( d ) ); /* switch (t0->eEnvironment) { // typ zmiany oświetlenia - zakładam, że // drugi tor ma e_flat case e_canyon: fShade = (d * 0.65) + (1.0 - d); break; // zacienienie w kanionie case e_tunnel: fShade = (d * 0.20) + (1.0 - d); break; // zacienienie w tunelu } switch (t1->eEnvironment) { // typ zmiany oświetlenia - zakładam, że // pierwszy tor ma e_flat case e_canyon: fShade = d + (1.0 - d) * 0.65; break; // zacienienie w kanionie case e_tunnel: fShade = d + (1.0 - d) * 0.20; break; // zacienienie w tunelu } */ } } } } }; void TDynamicObject::AttachNext(TDynamicObject *Object, int iType) { // Ra: doczepia Object na końcu składu (nazwa funkcji może być myląca) // Ra: używane tylko przy wczytywaniu scenerii auto const vehicleend { iDirection }; auto const othervehicleend { Object->iDirection ^ 1 }; MoverParameters->Attach( vehicleend, othervehicleend, Object->MoverParameters, iType, true, false ); // update neighbour data for both affected vehicles update_neighbours(); Object->update_neighbours(); // potentially attach automatic coupler adapter to allow the connection // HACK: we're doing it after establishin actual connection, as the method needs valid neighbour data auto &coupler { MoverParameters->Couplers[ vehicleend ] }; auto &othercoupler { Object->MoverParameters->Couplers[ ( othervehicleend != 2 ? othervehicleend : coupler.ConnectedNr ) ] }; if( coupler.type() != othercoupler.type() ) { if( othercoupler.type() == TCouplerType::Automatic ) { // try to attach adapter to the vehicle attach_coupler_adapter( vehicleend, true ); } else if( coupler.type() == TCouplerType::Automatic ) { // try to attach adapter to the other vehicle Object->attach_coupler_adapter( ( othervehicleend != 2 ? othervehicleend : coupler.ConnectedNr ), true ); } // update distance to neighbours on account of potentially attached adapter update_neighbours(); Object->update_neighbours(); } // potentially adjust vehicle position to avoid collision at the simulation start if( MoverParameters->Neighbours[ vehicleend ].distance > -0.001 ) { return; } Object->Move( MoverParameters->Neighbours[ vehicleend ].distance * Object->DirectionGet() ); // HACK: manually update vehicle position as it's used by neighbour distance update we do next Object->MoverParameters->Loc = { -Object->vPosition.x, Object->vPosition.z, Object->vPosition.y }; // update neighbour distance data after moving our vehicle update_neighbours(); Object->update_neighbours(); } bool TDynamicObject::UpdateForce(double dt) { if (!bEnabled) return false; if( dt > 0 ) { // wywalenie WS zależy od ustawienia kierunku MoverParameters->ComputeTotalForce( dt ); } return true; } // initiates load change by specified amounts, with a platform on specified side void TDynamicObject::LoadExchange( int const Disembark, int const Embark, int const Platform ) { /* if( ( MoverParameters->Doors.open_control == control_t::passenger ) || ( MoverParameters->Doors.open_control == control_t::mixed ) ) { // jeśli jedzie do tyłu, to drzwi otwiera odwrotnie auto const lewe = ( DirectionGet() > 0 ) ? 1 : 2; auto const prawe = 3 - lewe; if( Platform & lewe ) { MoverParameters->OperateDoors( side::left, true, range_t::local ); } if( Platform & prawe ) { MoverParameters->OperateDoors( side::right, true, range_t::local ); } } */ if( Platform == 0 ) { return; } // edge case, if there's no accessible platforms discard the request m_exchange.unload_count += Disembark; m_exchange.load_count += Embark; m_exchange.platforms = Platform; m_exchange.time = 0.0; } // calculates time needed to complete current load change float TDynamicObject::LoadExchangeTime() const { if( ( m_exchange.unload_count < 0.01 ) && ( m_exchange.load_count < 0.01 ) ) { return 0.f; } auto const baseexchangetime { m_exchange.unload_count / MoverParameters->UnLoadSpeed + m_exchange.load_count / MoverParameters->LoadSpeed }; auto const nominalexchangespeedfactor { ( m_exchange.platforms == 3 ? 2.f : 1.f ) }; auto const actualexchangespeedfactor { LoadExchangeSpeed() }; return baseexchangetime / ( actualexchangespeedfactor > 0.f ? actualexchangespeedfactor : nominalexchangespeedfactor ); } // calculates current load exchange rate float TDynamicObject::LoadExchangeSpeed() const { // platforms (1:left, 2:right, 3:both) // with exchange performed on both sides waiting times are halved auto exchangespeedfactor { 0.f }; auto const lewe { ( DirectionGet() > 0 ) ? 1 : 2 }; auto const prawe { 3 - lewe }; if( m_exchange.platforms & lewe ) { exchangespeedfactor += ( MoverParameters->Doors.instances[side::left].is_open ? 1.f : 0.f ); } if( m_exchange.platforms & prawe ) { exchangespeedfactor += ( MoverParameters->Doors.instances[ side::right ].is_open ? 1.f : 0.f ); } return exchangespeedfactor; } // update state of load exchange operation void TDynamicObject::update_exchange( double const Deltatime ) { // TODO: move offset calculation after initial check, when the loading system is all unified update_load_offset(); if( ( m_exchange.unload_count < 0.01 ) && ( m_exchange.load_count < 0.01 ) ) { return; } if( MoverParameters->Vel < 2.0 ) { auto const exchangespeed { LoadExchangeSpeed() }; if( exchangespeed < ( m_exchange.platforms == 3 ? 2.f : 1.f ) ) { // the exchange isn't performed at optimal rate, or at all. try to open viable vehicle doors to speed it up auto const lewe { ( DirectionGet() > 0 ) ? 1 : 2 }; auto const prawe { 3 - lewe }; if( ( m_exchange.platforms & lewe ) && ( false == ( MoverParameters->Doors.instances[side::left].is_open || MoverParameters->Doors.instances[side::left].is_opening ) ) ) { // try to open left door MoverParameters->OperateDoors( side::left, true, range_t::local ); } if( ( m_exchange.platforms & prawe ) && ( false == ( MoverParameters->Doors.instances[side::right].is_open || MoverParameters->Doors.instances[side::right].is_opening ) ) ) { // try to open right door MoverParameters->OperateDoors( side::right, true, range_t::local ); } } if( exchangespeed > 0.f ) { m_exchange.time += Deltatime; while( ( m_exchange.unload_count > 0.01 ) && ( m_exchange.time >= 1.0 ) ) { m_exchange.time -= 1.0; auto const exchangesize = std::min( m_exchange.unload_count, MoverParameters->UnLoadSpeed * exchangespeed ); m_exchange.unload_count -= exchangesize; MoverParameters->LoadStatus = 1; MoverParameters->LoadAmount = std::max( 0.f, MoverParameters->LoadAmount - exchangesize ); MoverParameters->ComputeMass(); update_load_visibility(); } if( m_exchange.unload_count < 0.01 ) { // finish any potential unloading operation before adding new load // don't load more than can fit m_exchange.load_count = std::min( m_exchange.load_count, MoverParameters->MaxLoad - MoverParameters->LoadAmount ); while( ( m_exchange.load_count > 0.01 ) && ( m_exchange.time >= 1.0 ) ) { m_exchange.time -= 1.0; auto const exchangesize = std::min( m_exchange.load_count, MoverParameters->LoadSpeed * exchangespeed ); m_exchange.load_count -= exchangesize; MoverParameters->LoadStatus = 2; MoverParameters->LoadAmount = std::min( MoverParameters->MaxLoad, MoverParameters->LoadAmount + exchangesize ); // std::max not strictly needed but, eh MoverParameters->ComputeMass(); update_load_visibility(); } } } } if( MoverParameters->Vel > 2.0 ) { // if the vehicle moves too fast cancel the exchange m_exchange.unload_count = 0; m_exchange.load_count = 0; } if( ( m_exchange.unload_count < 0.01 ) && ( m_exchange.load_count < 0.01 ) ) { MoverParameters->LoadStatus = 0; // if the exchange is completed (or canceled) close the door, if applicable if( ( MoverParameters->Doors.close_control == control_t::passenger ) || ( MoverParameters->Doors.close_control == control_t::mixed ) ) { if( ( MoverParameters->Vel > 2.0 ) || ( Random() < ( // remotely controlled door are more likely to be left open MoverParameters->Doors.close_control == control_t::passenger ? 0.75 : 0.50 ) ) ) { MoverParameters->OperateDoors( side::left, false, range_t::local ); MoverParameters->OperateDoors( side::right, false, range_t::local ); } } } } void TDynamicObject::LoadUpdate() { // przeładowanie modelu ładunku // Ra: nie próbujemy wczytywać modeli miliony razy podczas renderowania!!! if( ( mdLoad == nullptr ) && ( MoverParameters->LoadAmount > 0 ) ) { if( false == MoverParameters->LoadType.name.empty() ) { // bieżąca ścieżka do tekstur to dynamic/... Global.asCurrentTexturePath = asBaseDir; mdLoad = LoadMMediaFile_mdload( MoverParameters->LoadType.name ); // TODO: discern from vehicle component which merely uses vehicle directory and has no animations, so it can be initialized outright // and actual vehicles which get their initialization after their animations are set up if( mdLoad != nullptr ) { mdLoad->Init(); } // update bindings between lowpoly sections and potential load chunks placed inside them update_load_sections(); // z powrotem defaultowa sciezka do tekstur Global.asCurrentTexturePath = std::string( szTexturePath ); } // Ra: w MMD można by zapisać położenie modelu ładunku (np. węgiel) w zależności od załadowania } else if( MoverParameters->LoadAmount == 0 ) { // nie ma ładunku // MoverParameters->AssignLoad( "" ); mdLoad = nullptr; // erase bindings between lowpoly sections and potential load chunks placed inside them update_load_sections(); } MoverParameters->LoadStatus &= 3; // po zakończeniu będzie równe zero } void TDynamicObject::update_load_sections() { SectionLoadOrder.clear(); for( auto §ion : Sections ) { section.load = GetSubmodelFromName( mdLoad, section.compartment->pName ); if( section.load != nullptr ) { // create entry for each load model chunk assigned to the section // TBD, TODO: store also pointer to chunk submodel and control its visibility more directly, instead of per-section visibility flag? auto loadchunkcount { section.load->count_children() }; while( loadchunkcount-- ) { SectionLoadOrder.push_back( §ion ); } // HACK: disable automatic self-illumination threshold, at least until 3d model update if( MoverParameters->CompartmentLights.start_type == start_t::manual ) { section.load->SetSelfIllum( 2.0f, true, false ); } } } shuffle_load_order(); } void TDynamicObject::update_load_visibility() { // start with clean load chunk visibility slate for( auto §ion : Sections ) { section.load_chunks_visible = 0; } // each entry in load order sequence matches a single chunk of the section it points to // the length of load order sequence matches total number of load chunks auto const loadpercentage { ( MoverParameters->MaxLoad == 0.f ? 0.0 : MoverParameters->LoadAmount / MoverParameters->MaxLoad ) }; auto visiblechunkcount { ( SectionLoadOrder.empty() ? 0 : static_cast( std::ceil( loadpercentage * SectionLoadOrder.size() ) ) ) }; for( auto *section : SectionLoadOrder ) { if( visiblechunkcount == 0 ) { break; } section->load_chunks_visible++; --visiblechunkcount; } } void TDynamicObject::update_load_offset() { if( MoverParameters->LoadType.offset_min == 0.f ) { return; } auto const loadpercentage { ( MoverParameters->MaxLoad == 0.f ? 0.0 : 100.0 * MoverParameters->LoadAmount / MoverParameters->MaxLoad ) }; LoadOffset = interpolate( MoverParameters->LoadType.offset_min, 0.f, clamp( 0.0, 1.0, loadpercentage * 0.01 ) ); } void TDynamicObject::shuffle_load_order() { std::shuffle( std::begin( SectionLoadOrder ), std::end( SectionLoadOrder ), Global.random_engine ); // shift chunks assigned to corridors to the end of the list, so they show up last std::stable_partition( std::begin( SectionLoadOrder ), std::end( SectionLoadOrder ), []( vehicle_section const *section ) { return ( ( section->compartment->pName.find( "compartment" ) == 0 ) || ( section->compartment->pName.find( "przedzial" ) == 0 ) ); } ); // NOTE: potentially we're left with a mix of corridor and external section loads // but that's not necessarily a wrong outcome, so we leave it this way for the time being } /* Ra: Powinny być dwie funkcje wykonujące aktualizację fizyki. Jedna wykonująca krok obliczeń, powtarzana odpowiednią liczbę razy, a druga wykonująca zbiorczą aktualzację mniej istotnych elementów. Ponadto należało by ustalić odległość składów od kamery i jeśli przekracza ona np. 10km, to traktować składy jako uproszczone, np. bez wnikania w siły na sprzęgach, opóźnienie działania hamulca itp. Oczywiście musi mieć to pewną histerezę czasową, aby te tryby pracy nie przełączały się zbyt szybko. */ void TDynamicObject::update_destinations() { if( DestinationSign.sign == nullptr ) { return; } auto const lowvoltagepower { ( MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable ) }; DestinationSign.sign->fLight = ( ( ( DestinationSign.has_light ) && ( lowvoltagepower ) ) ? 2.0 : -1.0 ); // jak są 4 tekstury wymienne, to nie zmieniać rozkładem if( std::abs( m_materialdata.multi_textures ) >= 4 ) { return; } // TODO: dedicated setting to discern electronic signs, instead of fallback on light presence m_materialdata.replacable_skins[ 4 ] = ( ( ( DestinationSign.destination != null_handle ) && ( ( false == DestinationSign.has_light ) // physical destination signs remain up until manually changed || ( ( lowvoltagepower ) // lcd signs are off without power && ( ctOwner != nullptr ) ) ) ) ? // lcd signs are off for carriages without engine, potentially left on a siding DestinationSign.destination : DestinationSign.destination_off ); } bool TDynamicObject::Update(double dt, double dt1) { if (dt1 == 0) return true; // Ra: pauza if (!MoverParameters->PhysicActivation && !MechInside) // to drugie, bo będąc w maszynowym blokuje się fizyka return true; // McZapkie: wylaczanie fizyki gdy nie potrzeba if (!MyTrack) return false; // pojazdy postawione na torach portalowych mają MyTrack==NULL if (!bEnabled) return false; // a normalnie powinny mieć bEnabled==false double dDOMoveLen; // McZapkie: parametry powinny byc pobierane z toru if (Axle0.vAngles.z != Axle1.vAngles.z) { // wyliczenie promienia z obrotów osi - modyfikację zgłosił youBy ts.R = Axle0.vAngles.z - Axle1.vAngles.z; // różnica może dawać stałą ±M_2PI if( ( ts.R > 15000.0 ) || ( ts.R < -15000.0 ) ) { // szkoda czasu na zbyt duże promienie, 4km to promień nie wymagający przechyłki ts.R = 0.0; } else { // normalizacja if( ts.R > M_PI ) { ts.R -= M_2PI; } else if( ts.R < -M_PI ) { ts.R += M_2PI; } if( ts.R != 0.0 ) { // sin(0) results in division by zero ts.R = -0.5 * MoverParameters->BDist / sin( ts.R * 0.5 ); } } } else ts.R = 0.0; // ts.R=ComputeRadius(Axle1.pPosition,Axle2.pPosition,Axle3.pPosition,Axle0.pPosition); // Ra: składową pochylenia wzdłużnego mamy policzoną w jednostkowym wektorze // vFront ts.Len = 1.0; // Max0R(MoverParameters->BDist,MoverParameters->ADist); ts.dHtrack = -vFront.y; // Axle1.pPosition.y-Axle0.pPosition.y; //wektor // między skrajnymi osiami // (!!!odwrotny) ts.dHrail = (Axle1.GetRoll() + Axle0.GetRoll()) * 0.5; //średnia przechyłka pudła // TTrackParam tp; tp.Width = MyTrack->fTrackWidth; // McZapkie-250202 tp.friction = MyTrack->Friction() * Global.fFriction * Global.FrictionWeatherFactor; tp.CategoryFlag = MyTrack->iCategoryFlag & 15; tp.DamageFlag = MyTrack->iDamageFlag; tp.QualityFlag = MyTrack->iQualityFlag; // couplers if( ( MoverParameters->Couplers[ 0 ].CouplingFlag != coupling::faux ) && ( MoverParameters->Couplers[ 1 ].CouplingFlag != coupling::faux ) ) { MoverParameters->InsideConsist = true; } else { MoverParameters->InsideConsist = false; } // HACK: we're using sound event to detect whether vehicle was connected to another if( TestFlag( MoverParameters->AIFlag, sound::attachcoupler ) ) { auto *driver{ ctOwner ? ctOwner : Mechanik }; if( driver != nullptr ) { driver->CheckVehicles( Connect ); } SetFlag( MoverParameters->AIFlag, -sound::attachcoupler ); } // napiecie sieci trakcyjnej // Ra 15-01: przeliczenie poboru prądu powinno być robione wcześniej, żeby na // tym etapie były // znane napięcia // TTractionParam tmpTraction; // tmpTraction.TractionVoltage=0; if (MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) { // dla EZT tylko silnikowy // if (Global.bLiveTraction) { // Ra 2013-12: to niżej jest chyba trochę bez sensu tmpTraction.TractionVoltage = std::max( std::abs( MoverParameters->PantRearVolt ), std::abs( MoverParameters->PantFrontVolt ) ); /* if (v == 0.0) { v = MoverParameters->PantFrontVolt; if( v == 0.0 ) { // if( MoverParameters->TrainType & ( dt_EZT | dt_ET40 | dt_ET41 | dt_ET42 ) ) { // dwuczłony mogą mieć sprzęg WN // NOTE: condition disabled, other vehicles types can have power cables as well v = MoverParameters->GetTrainsetVoltage(); // ostatnia szansa // } } } */ if ( tmpTraction.TractionVoltage > 0.0) { // jeśli jest zasilanie NoVoltTime = 0; } else { NoVoltTime += dt1; 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->Pantographs[end::front].is_active || MoverParameters->Pantographs[end::rear].is_active ) { if( ( MoverParameters->Mains ) && ( MoverParameters->GetTrainsetHighVoltage() < 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 } ) ); } } } } } } } else { tmpTraction.TractionVoltage = 0.95 * MoverParameters->EnginePowerSource.MaxVoltage; } tmpTraction.TractionFreq = 0; tmpTraction.TractionMaxCurrent = 7500; // Ra: chyba za dużo? powinno wywalać przy 1500 tmpTraction.TractionResistivity = 0.3; MoverParameters->PantographVoltage = tmpTraction.TractionVoltage; // McZapkie: predkosc w torze przekazac do TrackParam // McZapkie: Vel ma wymiar [km/h] (absolutny), V ma wymiar [m/s], taka // przyjalem notacje tp.Velmax = MyTrack->VelocityGet(); if (Mechanik) { // Ra 2F3F: do Driver.cpp to przenieść? MoverParameters->EqvtPipePress = GetEPP(); // srednie cisnienie w PG if ((Mechanik->primary()) && ((MoverParameters->EngineType == TEngineType::DieselEngine) || (MoverParameters->EngineType == TEngineType::DieselElectric)) && (MoverParameters->EIMCtrlType > 0)) { MoverParameters->CheckEIMIC(dt1); if (MoverParameters->SpeedCtrl) MoverParameters->CheckSpeedCtrl(dt1); MoverParameters->eimic_real = std::min(MoverParameters->eimic,MoverParameters->eimicSpeedCtrl); MoverParameters->SendCtrlToNext("EIMIC", MoverParameters->eimic_real, MoverParameters->CabActive); } if( ( Mechanik->primary() ) && ( MoverParameters->EngineType == TEngineType::ElectricInductionMotor ) ) { // jesli glowny i z asynchronami, to niech steruje hamulcem i napedem lacznie dla calego pociagu/ezt auto const kier = (DirectionGet() * MoverParameters->CabOccupied > 0); auto FED { 0.0 }; auto np { 0 }; auto masa { 0.0 }; auto FrED { 0.0 }; auto masamax { 0.0 }; auto FmaxPN { 0.0 }; auto FfulED { 0.0 }; auto FmaxED { 0.0 }; auto Frj { 0.0 }; auto osie { 0 }; // 0a. ustal aktualna nastawe zadania sily napedowej i hamowania if( ( MoverParameters->Power < 1 ) && ( ctOwner != nullptr ) ) { MoverParameters->MainCtrlPos = ctOwner->Controlling()->MainCtrlPos*MoverParameters->MainCtrlPosNo / std::max(1, ctOwner->Controlling()->MainCtrlPosNo); MoverParameters->SpeedCtrlValue = ctOwner->Controlling()->SpeedCtrlValue; MoverParameters->SpeedCtrlUnit.IsActive = ctOwner->Controlling()->SpeedCtrlUnit.IsActive; } MoverParameters->CheckEIMIC(dt1); MoverParameters->CheckSpeedCtrl(dt1); auto eimic = Min0R(MoverParameters->eimic, MoverParameters->eimicSpeedCtrl); MoverParameters->eimic_real = eimic; if (MoverParameters->EIMCtrlType == 2 && MoverParameters->MainCtrlPos == 0) eimic = -1.0; MoverParameters->SendCtrlToNext("EIMIC", Max0R(0, eimic), MoverParameters->CabActive); auto LBR = Max0R(-eimic, 0); auto eim_lb = (Mechanik->AIControllFlag || !MoverParameters->LocHandleTimeTraxx ? 0 : MoverParameters->eim_localbrake); // 1. ustal wymagana sile hamowania calego pociagu // - opoznienie moze byc ustalane na podstawie charakterystyki // - opoznienie moze byc ustalane na podstawie mas i cisnien granicznych // // 2. ustal mozliwa do realizacji sile hamowania ED // - w szczegolnosci powinien brac pod uwage rozne sily hamowania for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p; (kier ? p = p->NextC(4) : p = p->PrevC(4))) { ++np; masamax += p->MoverParameters->MBPM + ( p->MoverParameters->MBPM > 1.0 ? 0.0 : p->MoverParameters->Mass ) + p->MoverParameters->Mred; auto const Nmax = ((p->MoverParameters->P2FTrans * p->MoverParameters->MaxBrakePress[0] - p->MoverParameters->BrakeCylSpring) * p->MoverParameters->BrakeCylMult[0] - p->MoverParameters->BrakeSlckAdj) * p->MoverParameters->BrakeCylNo * p->MoverParameters->BrakeRigEff; FmaxPN += Nmax * p->MoverParameters->Hamulec->GetFC( Nmax / (p->MoverParameters->NAxles * p->MoverParameters->NBpA), p->MoverParameters->MED_Vref) * 1000; // sila hamowania pn FmaxED += ((p->MoverParameters->Mains) && (p->MoverParameters->DirActive != 0) && (p->MoverParameters->eimc[eimc_p_Fh] * p->MoverParameters->NPoweredAxles > 0) ? p->MoverParameters->eimc[eimc_p_Fh] * 1000 : 0); // chwilowy max ED -> do rozdzialu sil FED -= std::min(p->MoverParameters->eimv[eimv_Fmax], 0.0) * 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_Fmax], 0.0) * 1000; // chwilowo realizowane ED -> do pneumatyki 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; } double RapidMult = 1.0; if (((MoverParameters->BrakeDelays & (bdelay_P + bdelay_R)) == (bdelay_P + bdelay_R)) && (MoverParameters->BrakeDelayFlag & bdelay_P)) RapidMult = MoverParameters->RapidMult; auto const amax = RapidMult * std::min(FmaxPN / masamax, MoverParameters->MED_amax); auto const doorisopen { ( false == MoverParameters->Doors.instances[ side::left ].is_closed ) || ( false == MoverParameters->Doors.instances[ side::right ].is_closed ) || ( MoverParameters->Doors.permit_needed && ( MoverParameters->Doors.instances[ side::left ].open_permit || MoverParameters->Doors.instances[ side::right ].open_permit ) ) }; if ((MoverParameters->Vel < 0.5) && (MoverParameters->BrakePress > 0.2 || doorisopen)) { MoverParameters->ShuntMode = true; } if (MoverParameters->ShuntMode) { MoverParameters->ShuntModeAllow = ( false == doorisopen ) && (LBR < 0.01); } if( ( MoverParameters->Vel > 1 ) && ( false == doorisopen ) ) { MoverParameters->ShuntMode = false; MoverParameters->ShuntModeAllow = (MoverParameters->BrakePress > 0.2) && (LBR < 0.01); } auto Fzad = amax * LBR * masa; if ((MoverParameters->BrakeCtrlPos == MoverParameters->Handle->GetPos(bh_EB)) && (MoverParameters->eimc[eimc_p_abed] < 0.001)) Fzad = amax * masa; //pętla bezpieczeństwa - pełne służbowe if ((MoverParameters->ScndS) && (MoverParameters->Vel > MoverParameters->eimc[eimc_p_Vh1]) && (FmaxED > 0)) { Fzad = std::min(LBR * FmaxED, FfulED); } if (((MoverParameters->ShuntMode) && (Frj < 0.0015 * masa)) || (MoverParameters->V * MoverParameters->DirAbsolute < -0.2)) { auto const sbd { ( ( MoverParameters->SpringBrake.IsActive && MoverParameters->ReleaseParkingBySpringBrake ) ? 0.0 : MoverParameters->StopBrakeDecc ) }; Fzad = std::max( Fzad, sbd * masa ); } if ((Fzad > 1) && (!MEDLogFile.is_open()) && (MoverParameters->Vel > 1)) { MEDLogFile.open( "MEDLOGS/" + MoverParameters->Name + "_" + to_string( ++MEDLogCount ) + ".csv", std::ios::in | std::ios::out | std::ios::trunc ); MEDLogFile << "t\tVel\tMasa\tOsie\tFmaxPN\tFmaxED\tFfulED\tFrED\tFzad\tFzadED\tFzadPN"; for(int k=1;k<=np;k++) { MEDLogFile << "\tBP" << k; } MEDLogFile << "\n"; MEDLogFile.flush(); MEDLogInactiveTime = 0; MEDLogTime = 0; } auto FzadED { 0.0 }; if( ( LBR > MoverParameters->MED_MinBrakeReqED ) && ( MoverParameters->BrakeHandle == TBrakeHandle::MHZ_EN57 ? ( ( MoverParameters->BrakeOpModeFlag & bom_MED ) != 0 ) : MoverParameters->EpFuse ) ) { FzadED = std::min( Fzad, FmaxED ); } if (MoverParameters->EIMCtrlType == 2 && MoverParameters->MainCtrlPos < 2 && MoverParameters->eimic > -0.999) { FzadED = std::min(FzadED, MED_oldFED); } if ((MoverParameters->BrakeCtrlPos == MoverParameters->Handle->GetPos(bh_EB)) && (MoverParameters->eimc[eimc_p_abed] < 0.001)) FzadED = 0; //pętla bezpieczeństwa - bez ED auto const FzadPN = Fzad - FrED; //np = 0; // BUG: likely memory leak, allocation per inner loop, deleted only once outside // TODO: sort this shit out bool* PrzekrF = new bool[np]; float nPrzekrF = 0; bool test = true; float* FzED = new float[np]; float* FzEP = new float[np]; float* FmaxEP = new float[np]; // 3. ustaw pojazdom sile hamowania ED // - proporcjonalnie do mozliwosci // 4. ustal potrzebne dohamowanie pneumatyczne // - od sily zadanej trzeba odjac realizowana przez ED // 5. w razie potrzeby wlacz hamulec utrzymujacy // - gdy zahamowany ma ponizej 2 km/h // 6. ustaw pojazdom sile hamowania ep // - proporcjonalnie do masy, do liczby osi, rowne cisnienia - jak // bedzie, tak bedzie dobrze float Fpoj = 0; // MoverParameters->CabOccupied < 0 ////ALGORYTM 2 - KAZDEMU PO ROWNO, ale nie wiecej niz eped * masa // 1. najpierw daj kazdemu tyle samo int i = 0; for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p; p = (kier == true ? p->NextC(4) : p->PrevC(4)) ) { auto const Nmax = ((p->MoverParameters->P2FTrans * p->MoverParameters->MaxBrakePress[0] - p->MoverParameters->BrakeCylSpring) * p->MoverParameters->BrakeCylMult[0] - p->MoverParameters->BrakeSlckAdj) * p->MoverParameters->BrakeCylNo * p->MoverParameters->BrakeRigEff; FmaxEP[i] = Nmax * p->MoverParameters->Hamulec->GetFC( Nmax / (p->MoverParameters->NAxles * p->MoverParameters->NBpA), p->MoverParameters->MED_Vref) * 1000; // sila hamowania pn PrzekrF[i] = false; FzED[i] = (FmaxED > 0 ? FzadED / FmaxED : 0); p->MoverParameters->AnPos = (MoverParameters->ScndS ? LBR : FzED[i]); FzEP[ i ] = static_cast( FzadPN * p->MoverParameters->NAxles ) / static_cast( osie ); ++i; p->MoverParameters->ShuntMode = MoverParameters->ShuntMode; p->MoverParameters->ShuntModeAllow = MoverParameters->ShuntModeAllow; } while (test) { test = false; i = 0; float przek = 0; for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p; p = (kier == true ? p->NextC(4) : p->PrevC(4)) ) { if ((FzEP[i] > 0.01) && (FzEP[i] > p->MoverParameters->TotalMass * p->MoverParameters->eimc[eimc_p_eped] + Min0R(p->MoverParameters->eimv[eimv_Fmax], 0) * 1000) && (!PrzekrF[i])) { float przek1 = -Min0R(p->MoverParameters->eimv[eimv_Fmax], 0) * 1000 + FzEP[i] - p->MoverParameters->TotalMass * p->MoverParameters->eimc[eimc_p_eped] * 0.999; PrzekrF[i] = true; test = true; nPrzekrF++; przek1 = Min0R(przek1, FzEP[i]); FzEP[i] -= przek1; if (FzEP[i] < 0) FzEP[i] = 0; przek += przek1; } ++i; } i = 0; przek = przek / (np - nPrzekrF); for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p; (true == kier ? p = p->NextC(4) : p = p->PrevC(4))) { if (!PrzekrF[i]) { FzEP[i] += przek; } ++i; } } i = 0; for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p; (true == kier ? p = p->NextC(4) : p = p->PrevC(4))) { float Nmax = ((p->MoverParameters->P2FTrans * p->MoverParameters->MaxBrakePress[0] - p->MoverParameters->BrakeCylSpring) * p->MoverParameters->BrakeCylMult[0] - p->MoverParameters->BrakeSlckAdj) * p->MoverParameters->BrakeCylNo * p->MoverParameters->BrakeRigEff; if (FrED > 0.1) p->MoverParameters->MED_EPVC_CurrentTime = 0; else p->MoverParameters->MED_EPVC_CurrentTime += dt1; bool EPVC = ((p->MoverParameters->MED_EPVC) && ((p->MoverParameters->MED_EPVC_Time < 0) || (p->MoverParameters->MED_EPVC_CurrentTime < p->MoverParameters->MED_EPVC_Time))); float VelC = (EPVC ? clamp(p->MoverParameters->Vel, p->MoverParameters->MED_Vmin, p->MoverParameters->MED_Vmax) : p->MoverParameters->MED_Vref);//korekcja EP po prędkości float FmaxPoj = Nmax * p->MoverParameters->Hamulec->GetFC( Nmax / (p->MoverParameters->NAxles * p->MoverParameters->NBpA), VelC) * 1000; // sila hamowania pn p->MoverParameters->LocalBrakePosAEIM = FzEP[i] / FmaxPoj; if (p->MoverParameters->LocalBrakePosAEIM>0.009) if (p->MoverParameters->P2FTrans * p->MoverParameters->BrakeCylMult[0] * p->MoverParameters->MaxBrakePress[0] != 0) { float x = (p->MoverParameters->BrakeSlckAdj / p->MoverParameters->BrakeCylMult[0] + p->MoverParameters->BrakeCylSpring) / (p->MoverParameters->P2FTrans * p->MoverParameters->MaxBrakePress[0]); p->MoverParameters->LocalBrakePosAEIM = x + (1 - x) * p->MoverParameters->LocalBrakePosAEIM; } else p->MoverParameters->LocalBrakePosAEIM = p->MoverParameters->LocalBrakePosAEIM; else p->MoverParameters->LocalBrakePosAEIM = 0; if (p->MoverParameters->LocHandleTimeTraxx) { p->MoverParameters->eim_localbrake = eim_lb; p->MoverParameters->LocalBrakePosAEIM = std::max(p->MoverParameters->LocalBrakePosAEIM, eim_lb); } ++i; } MED[0][0] = masa*0.001; MED[0][1] = amax; MED[0][2] = Fzad*0.001; MED[0][3] = FmaxPN*0.001; MED[0][4] = FmaxED*0.001; MED[0][5] = FrED*0.001; MED[0][6] = FzadPN*0.001; MED[0][7] = nPrzekrF; if (MEDLogFile.is_open()) { MEDLogFile << MEDLogTime << "\t" << MoverParameters->Vel << "\t" << masa*0.001 << "\t" << osie << "\t" << FmaxPN*0.001 << "\t" << FmaxED*0.001 << "\t" << FfulED*0.001 << "\t" << FrED*0.001 << "\t" << Fzad*0.001 << "\t" << FzadED*0.001 << "\t" << FzadPN*0.001; for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p; (true == kier ? p = p->NextC(4) : p = p->PrevC(4))) { MEDLogFile << "\t" << p->MoverParameters->BrakePress; } MEDLogFile << "\n"; if (floor(MEDLogTime + dt1) > floor(MEDLogTime)) { MEDLogFile.flush(); } MEDLogTime += dt1; if ((MoverParameters->Vel < 0.1) || (MoverParameters->MainCtrlPowerPos() > 0)) { MEDLogInactiveTime += dt1; } else { MEDLogInactiveTime = 0; } if (MEDLogInactiveTime > 5) { MEDLogFile.flush(); MEDLogFile.close(); } } delete[] PrzekrF; delete[] FzED; delete[] FzEP; delete[] FmaxEP; MED_oldFED = FzadED; } Mechanik->UpdateSituation(dt1); // przebłyski świadomości AI } // fragment "z EXE Kursa" if( MoverParameters->Mains ) { // nie wchodzić w funkcję bez potrzeby if( ( false == ( MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable ) ) /* // NOTE: disabled on account of multi-unit setups, where the unmanned unit wouldn't be affected && ( Controller == Humandriver ) */ && ( MoverParameters->EngineType != TEngineType::DieselEngine ) && ( MoverParameters->EngineType != TEngineType::WheelsDriven ) ) { // jeśli bateria wyłączona, a nie diesel ani drezyna reczna if( MoverParameters->MainSwitch( false, ( MoverParameters->TrainType == dt_EZT ? range_t::unit : range_t::local ) ) ) { // wyłączyć zasilanie // NOTE: we turn off entire EMU, but only the affected unit for other multi-unit consists MoverParameters->EventFlag = true; // drop pantographs // NOTE: this isn't universal behaviour // TODO: have this dependant on .fiz-driven flag // NOTE: moved to pantspeed calculation part a little later in the function. all remarks and todo still apply /* MoverParameters->PantFront( false, ( MoverParameters->TrainType == dt_EZT ? range::unit : range::local ) ); MoverParameters->PantRear( false, ( MoverParameters->TrainType == dt_EZT ? range::unit : range::local ) ); */ } } } // przekazanie pozycji do fizyki // NOTE: coordinate system swap // TODO: replace with regular glm vectors TLocation const l { -vPosition.x, vPosition.z, vPosition.y }; TRotation const r { 0.0, 0.0, modelRot.z }; // McZapkie-260202 - dMoveLen przyda sie przy stukocie kol dDOMoveLen = GetdMoveLen() + MoverParameters->ComputeMovement(dt, dt1, ts, tp, tmpTraction, l, r); if( Mechanik ) { // dodanie aktualnego przemieszczenia Mechanik->MoveDistanceAdd( dDOMoveLen ); } if( ( simulation::Train != nullptr ) && ( simulation::Train->Dynamic() == this ) ) { // update distance meter in user-controlled cab // TBD: place the meter on mover logic level? simulation::Train->add_distance( dDOMoveLen ); } glm::dvec3 old_pos = vPosition; Move(dDOMoveLen); m_future_movement = (glm::dvec3(vPosition) - old_pos) / dt1 * Timer::GetDeltaRenderTime(); if (!bEnabled) // usuwane pojazdy nie mają toru { // pojazd do usunięcia bDynamicRemove = true; // sprawdzić return false; } ResetdMoveLen(); // McZapkie-260202 // tupot mew, tfu, stukot kol: if( MyTrack->fSoundDistance != -1 ) { if( MyTrack->fSoundDistance != dRailLength ) { if( dRailLength > 0.0 ) { for( auto &axle : m_axlesounds ) { axle.distance = axle.offset; } } dRailLength = MyTrack->fSoundDistance; } if( dRailLength > 0.0 ) { if( MoverParameters->Vel > 0 ) { // TODO: track quality and/or environment factors as separate subroutine auto volume = interpolate( 0.8, 1.2, clamp( MyTrack->iQualityFlag / 20.0, 0.0, 1.0 ) ); switch( MyTrack->eEnvironment ) { case e_tunnel: { volume *= 1.1; break; } case e_bridge: { volume *= 1.2; break; } default: { break; } } if( dRailLength > 0.0 ) { auto axleindex { 0 }; auto const directioninconsist { ( ctOwner == nullptr ? 1 : ( ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) ) }; for( auto &axle : m_axlesounds ) { axle.distance += dDOMoveLen * directioninconsist; if( ( axle.distance < 0 ) || ( axle.distance > dRailLength ) ) { axle.distance = clamp_circular( axle.distance, dRailLength ); if( MoverParameters->Vel > 0.1 ) { // NOTE: for combined clatter sound we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range auto const frequency = ( true == axle.clatter.is_combined() ? MoverParameters->Vel * 0.01 : 1.0 ); axle.clatter .pitch( frequency ) .gain( volume ) .play(); // crude bump simulation, drop down on even axles, move back up on the odd ones MoverParameters->AccVert += interpolate( 0.01, 0.05, clamp( GetVelocity() / ( 1 + MoverParameters->Vmax ), 0.0, 1.0 ) ) * ( ( axleindex % 2 ) != 0 ? 1 : -1 ); } } ++axleindex; } } } } } // McZapkie-260202 end // fragment z EXE Kursa /* if (MoverParameters->TrainType==dt_ET42) { if ((MoverParameters->DynamicBrakeType=dbrake_switch) && ((MoverParameters->BrakePress > 0.2) || ( MoverParameters->PipePress < 0.36 ))) { MoverParameters->StLinFlag=true; } else if ((MoverParameters->DynamicBrakeType=dbrake_switch) && (MoverParameters->BrakePress < 0.1)) { MoverParameters->StLinFlag=false; } } */ if (MoverParameters->Vel != 0) { // McZapkie-050402: krecenie kolami: glm::dvec3 old_wheels = glm::dvec3(dWheelAngle[0], dWheelAngle[1], dWheelAngle[2]); dWheelAngle[0] += 114.59155902616464175359630962821 * MoverParameters->V * dt1 / MoverParameters->WheelDiameterL; // przednie toczne dWheelAngle[1] += MoverParameters->nrot * dt1 * 360.0; // napędne dWheelAngle[2] += 114.59155902616464175359630962821 * MoverParameters->V * dt1 / MoverParameters->WheelDiameterT; // tylne toczne m_future_wheels_angle = (glm::dvec3(dWheelAngle[0], dWheelAngle[1], dWheelAngle[2]) - old_wheels) / dt1 * Timer::GetDeltaRenderTime(); dWheelAngle[0] = clamp_circular( dWheelAngle[0] ); dWheelAngle[1] = clamp_circular( dWheelAngle[1] ); dWheelAngle[2] = clamp_circular( dWheelAngle[2] ); } if (pants) // pantograf może być w wagonie kuchennym albo pojeździe rewizyjnym (np. SR61) { // przeliczanie kątów dla pantografów double k; // tymczasowy kąt double PantDiff; TAnimPant *p; // wskaźnik do obiektu danych pantografu double fCurrent = ( ( MoverParameters->DynamicBrakeFlag && MoverParameters->ResistorsFlag ) ? 0 : std::abs( MoverParameters->Itot ) * MoverParameters->IsVehicleEIMBrakingFactor() ) + MoverParameters->TotalCurrent; // prąd pobierany przez pojazd - bez sensu z tym (TotalCurrent) // TotalCurrent to bedzie prad nietrakcyjny (niezwiazany z napedem) // fCurrent+=fabs(MoverParameters->Voltage)*1e-6; //prąd płynący przez woltomierz, rozładowuje kondensator orgromowy 4µF /* double fPantCurrent = fCurrent; // normalnie cały prąd przez jeden pantograf if (iAnimType[ANIM_PANTS] > 1) { // a jeśli są dwa pantografy //Ra 1014-11: proteza, trzeba zrobić sensowniej if (pants[0].fParamPants->hvPowerWire && pants[1].fParamPants->hvPowerWire) { // i oba podłączone do drutów fPantCurrent = fCurrent * 0.5; // to dzielimy prąd równo na oba (trochę bez sensu, ale lepiej tak niż podwoić prąd) } } */ // test whether more than one pantograph touches the wire // NOTE: we're duplicating lot of code from below // TODO: clean this up auto activepantographs { 0 }; for( int idx = 0; idx < iAnimType[ ANIM_PANTS ]; ++idx ) { auto const *pantograph { pants[ idx ].fParamPants }; if( Global.bLiveTraction == false ) { if( pantograph->PantWys >= 1.2 ) { ++activepantographs; } } else { if( ( pantograph->hvPowerWire != nullptr ) && ( true == MoverParameters->Pantographs[ end::front ].is_active ) && ( pantograph->PantTraction - pantograph->PantWys < 0.01 ) ) { // tolerancja niedolegania ++activepantographs; } } } auto const fPantCurrent { fCurrent / std::max( 1, activepantographs ) }; for (int i = 0; i < iAnimType[ANIM_PANTS]; ++i) { // pętla po wszystkich pantografach p = pants[i].fParamPants; if (p->PantWys < 0) { // patograf został połamany, liczony nie będzie if (p->fAngleL > p->fAngleL0) p->fAngleL -= 0.2 * dt1; // nieco szybciej niż jak dla opuszczania if (p->fAngleL < p->fAngleL0) p->fAngleL = p->fAngleL0; // kąt graniczny if (p->fAngleU < M_PI) p->fAngleU += 0.5 * dt1; // górne się musi ruszać szybciej. if (p->fAngleU > M_PI) p->fAngleU = M_PI; if (i & 1) // zgłoszono, że po połamaniu potrafi zostać zasilanie MoverParameters->PantRearVolt = 0.0; else MoverParameters->PantFrontVolt = 0.0; continue; // reszta wtedy nie jest wykonywana } PantDiff = p->PantTraction - p->PantWys; // docelowy-aktualny switch (i) // numer pantografu { // trzeba usunąć to rozróżnienie case 0: if( ( Global.bLiveTraction == false ) && ( p->hvPowerWire == nullptr ) ) { // jeśli nie ma drutu, może pooszukiwać MoverParameters->PantFrontVolt = ( p->PantWys >= 1.2 ) ? 0.95 * MoverParameters->EnginePowerSource.MaxVoltage : 0.0; } else if( ( true == MoverParameters->Pantographs[ end::front ].is_active ) && ( PantDiff < 0.01 ) ) // tolerancja niedolegania { if (p->hvPowerWire) { auto const lastvoltage { MoverParameters->PantFrontVolt }; // TODO: wyliczyć trzeba prąd przypadający na pantograf i wstawić do GetVoltage() if( lastvoltage == 0.0 ) { // HACK: retrieve the wire voltage for calculations down the road without blowing up the supply MoverParameters->PantFrontVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, 0.0 ); } else { MoverParameters->PantFrontVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, fPantCurrent ); if( MoverParameters->PantFrontVolt > 0.0 ) { fCurrent -= fPantCurrent; // taki prąd płynie przez powyższy pantograf (unless it doesn't) } } // TODO: refactor reaction to voltage change to mover as sound event for specific pantograph if( ( lastvoltage == 0.0 ) && ( MoverParameters->PantFrontVolt > 0.0 ) ) { for( auto &pantograph : m_pantographsounds ) { if( pantograph.sPantUp.offset().z > 0 ) { // limit to pantographs located in the front half of the vehicle pantograph.sPantUp.play( sound_flags::exclusive ); } } } } else MoverParameters->PantFrontVolt = 0.0; } else MoverParameters->PantFrontVolt = 0.0; break; case 1: if( ( false == Global.bLiveTraction ) && ( nullptr == p->hvPowerWire ) ) { // jeśli nie ma drutu, może pooszukiwać MoverParameters->PantRearVolt = ( p->PantWys >= 1.2 ) ? 0.95 * MoverParameters->EnginePowerSource.MaxVoltage : 0.0; } else if ( ( true == MoverParameters->Pantographs[ end::rear ].is_active ) && ( PantDiff < 0.01 ) ) { if (p->hvPowerWire) { auto const lastvoltage { MoverParameters->PantRearVolt }; // TODO: wyliczyć trzeba prąd przypadający na pantograf i wstawić do GetVoltage() if( lastvoltage == 0.0 ) { // HACK: retrieve the wire voltage for calculations down the road without blowing up the supply MoverParameters->PantRearVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, 0.0 ); } else { MoverParameters->PantRearVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, fPantCurrent ); if( MoverParameters->PantRearVolt > 0.0 ) { fCurrent -= fPantCurrent; // taki prąd płynie przez powyższy pantograf (unless it doesn't) } } // TODO: refactor reaction to voltage change to mover as sound event for specific pantograph if( ( lastvoltage == 0.0 ) && ( MoverParameters->PantRearVolt > 0.0 ) ) { for( auto &pantograph : m_pantographsounds ) { if( pantograph.sPantUp.offset().z < 0 ) { // limit to pantographs located in the rear half of the vehicle pantograph.sPantUp.play( sound_flags::exclusive ); } } } } else MoverParameters->PantRearVolt = 0.0; } else { // Global.iPause ^= 2; MoverParameters->PantRearVolt = 0.0; } break; } // pozostałe na razie nie obsługiwane if( MoverParameters->PantPress > ( MoverParameters->TrainType == dt_EZT ? 2.45 : // Ra 2013-12: Niebugocław mówi, że w EZT podnoszą się przy 2.5 3.45 ) ) { // z EXE Kursa // Ra: wysokość zależy od ciśnienia !!! pantspeedfactor = 0.015 * ( MoverParameters->PantPress ) * dt1; } else { pantspeedfactor = 0.0; } if( false == ( MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable ) ) { pantspeedfactor = 0.0; } pantspeedfactor = std::max( 0.0, pantspeedfactor ); k = p->fAngleL; if( ( pantspeedfactor > 0.0 ) && ( MoverParameters->Pantographs[i].is_active ) )// jeśli ma być podniesiony { if (PantDiff > 0.001) // jeśli nie dolega do drutu { // jeśli poprzednia wysokość jest mniejsza niż pożądana, zwiększyć kąt dolnego // ramienia zgodnie z ciśnieniem if (pantspeedfactor > 0.55 * PantDiff) // 0.55 to około pochodna kąta po wysokości k += 0.55 * PantDiff; // ograniczenie "skoku" w danej klatce else k += pantspeedfactor; // dolne ramię // jeśli przekroczono kąt graniczny, zablokować pantograf // (wymaga interwencji pociągu sieciowego) } else if (PantDiff < -0.001) { // drut się obniżył albo pantograf podniesiony za wysoko // jeśli wysokość jest zbyt duża, wyznaczyć zmniejszenie kąta // jeśli zmniejszenie kąta jest zbyt duże, przejść do trybu łamania pantografu // if (PantFrontDiff<-0.05) //skok w dół o 5cm daje złąmanie pantografu k += 0.4 * PantDiff; // mniej niż pochodna kąta po wysokości } // jeśli wysokość jest dobra, nic więcej nie liczyć } else { // jeśli ma być na dole if (k > p->fAngleL0) // jeśli wyżej niż położenie wyjściowe k -= 0.15 * dt1; // ruch w dół if (k < p->fAngleL0) k = p->fAngleL0; // położenie minimalne } if (k != p->fAngleL) { //żeby nie liczyć w kilku miejscach ani gdy nie potrzeba if (k + p->fAngleU < M_PI) { // o ile nie został osiągnięty kąt maksymalny p->fAngleL = k; // zmieniony kąt // wyliczyć kąt górnego ramienia z wzoru (a)cosinusowego //=acos((b*cos()+c)/a) // p->dPantAngleT=acos((1.22*cos(k)+0.535)/1.755); //górne ramię 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() // wysokość całości p->PantWys = p->fLenL1 * sin(k) + p->fLenU1 * sin(p->fAngleU) + p->fHeight; } } } // koniec pętli po pantografach // TBD, TODO: generate sound event during mover update instead? if( MoverParameters->Pantographs[end::front].sound_event != MoverParameters->Pantographs[ end::front ].is_active ) { if( MoverParameters->Pantographs[ end::front ].is_active ) { // pantograph moving up // TBD: add a sound? } else { // pantograph dropping for( auto &pantograph : m_pantographsounds ) { if( pantograph.sPantDown.offset().z > 0 ) { // limit to pantographs located in the front half of the vehicle pantograph.sPantDown.play( sound_flags::exclusive ); } } } MoverParameters->Pantographs[ end::front ].sound_event = MoverParameters->Pantographs[ end::front ].is_active; } if( MoverParameters->Pantographs[ end::rear ].sound_event != MoverParameters->Pantographs[ end::rear ].is_active ) { if( MoverParameters->Pantographs[ end::rear ].is_active ) { // pantograph moving up // TBD: add a sound? } else { // pantograph dropping for( auto &pantograph : m_pantographsounds ) { if( pantograph.sPantDown.offset().z < 0 ) { // limit to pantographs located in the front half of the vehicle pantograph.sPantDown.play( sound_flags::exclusive ); } } } MoverParameters->Pantographs[ end::rear ].sound_event = MoverParameters->Pantographs[ end::rear ].is_active; } } else if (MoverParameters->EnginePowerSource.SourceType == TPowerSource::InternalSource) if (MoverParameters->EnginePowerSource.PowerType == TPowerType::SteamPower) // if (smPatykird1[0]) { // Ra: animacja rozrządu parowozu, na razie nieoptymalizowane /* //Ra: tymczasowo wyłączone ze względu na porządkowanie animacji pantografów double fi,dx,c2,ka,kc; double sin_fi,cos_fi; double L1=1.6688888888888889; double L2=5.6666666666666667; //2550/450 double Lc=0.4; double L=5.686422222; //2558.89/450 double G1,G2,G3,ksi,sin_ksi,gam; double G1_2,G2_2,G3_2; //kwadraty //ruch tłoków oraz korbowodów for (int i=0;i<=1;++i) {//obie strony w ten sam sposób fi=DegToRad(dWheelAngle[1]+(i?pant2x:pant1x)); //kąt obrotu koła dla tłoka 1 sin_fi=sin(fi); cos_fi=cos(fi); dx=panty*cos_fi+sqrt(panth*panth-panty*panty*sin_fi*sin_fi)-panth; //nieoptymalne if (smPatykird1[i]) //na razie zabezpieczenie smPatykird1[i]->SetTranslate(float3(dx,0,0)); ka=-asin(panty/panth)*sin_fi; if (smPatykirg1[i]) //na razie zabezpieczenie smPatykirg1[i]->SetRotateXYZ(vector3(RadToDeg(ka),0,0)); //smPatykirg1[0]->SetRotate(float3(0,1,0),RadToDeg(fi)); //obracamy //ruch drążka mimośrodkowego oraz jarzma //korzystałem z pliku PDF "mm.pdf" (opis czworoboku korbowo-wahaczowego): //"MECHANIKA MASZYN. Szkic wykładu i laboratorium komputerowego." //Prof. dr hab. inż. Jerzy Zajączkowski, 2007, Politechnika Łódzka //L1 - wysokość (w pionie) osi jarzma ponad osią koła //L2 - odległość w poziomie osi jarzma od osi koła //Lc - długość korby mimośrodu na kole //Lr - promień jarzma =1.0 (pozostałe przeliczone proporcjonalnie) //L - długość drążka mimośrodowego //fi - kąt obrotu koła //ksi - kąt obrotu jarzma (od pionu) //gam - odchylenie drążka mimośrodowego od poziomu //G1=(Lr*Lr+L1*L1+L2*L2+Kc*Lc-L*L-2.0*Lc*L2*cos(fi)+2.0*Lc*L1*sin(fi))/(Lr*Lr); //G2=2.0*(L2-Lc*cos(fi))/Lr; //G3=2.0*(L1-Lc*sin(fi))/Lr; fi=DegToRad(dWheelAngle[1]+(i?pant2x:pant1x)-96.77416667); //kąt obrotu koła dla tłoka 1 //1) dla dWheelAngle[1]=0° korba jest w dół, a mimośród w stronę jarzma, czyli fi=-7° //2) dla dWheelAngle[1]=90° korba jest do tyłu, a mimośród w dół, czyli fi=83° sin_fi=sin(fi); cos_fi=cos(fi); G1=(1.0+L1*L1+L2*L2+Lc*Lc-L*L-2.0*Lc*L2*cos_fi+2.0*Lc*L1*sin_fi); G1_2=G1*G1; G2=2.0*(L2-Lc*cos_fi); G2_2=G2*G2; G3=2.0*(L1-Lc*sin_fi); G3_2=G3*G3; sin_ksi=(G1*G2-G3*_fm_sqrt(G2_2+G3_2-G1_2))/(G2_2+G3_2); //x1 (minus delta) ksi=asin(sin_ksi); //kąt jarzma if (smPatykirg2[i]) smPatykirg2[i]->SetRotateXYZ(vector3(RadToDeg(ksi),0,0)); //obrócenie jarzma //1) ksi=-23°, gam= //2) ksi=10°, gam= //gam=acos((L2-sin_ksi-Lc*cos_fi)/L); //kąt od poziomu, liczony względem poziomu //gam=asin((L1-cos_ksi-Lc*sin_fi)/L); //kąt od poziomu, liczony względem pionu gam=atan2((L1-cos(ksi)+Lc*sin_fi),(L2-sin_ksi+Lc*cos_fi)); //kąt od poziomu if (smPatykird2[i]) //na razie zabezpieczenie smPatykird2[i]->SetRotateXYZ(vector3(RadToDeg(-gam-ksi),0,0)); //obrócenie drążka mimośrodowego } */ } // mirrors if( MoverParameters->Vel > 5.0 ) { // automatically fold mirrors when above velocity threshold if( dMirrorMoveL > 0.0 ) { dMirrorMoveL = std::max( 0.0, dMirrorMoveL - 1.0 * dt1 ); } if( dMirrorMoveR > 0.0 ) { dMirrorMoveR = std::max( 0.0, dMirrorMoveR - 1.0 * dt1 ); } } else { // unfold mirror on the side with open doors, if not moving too fast if( ( dMirrorMoveL < 1.0 ) && ( true == MoverParameters->Doors.instances[side::left].open_permit ) ) { dMirrorMoveL = std::min( 1.0, dMirrorMoveL + 1.0 * dt1 ); } if( ( dMirrorMoveR < 1.0 ) && ( true == MoverParameters->Doors.instances[side::right].open_permit ) ) { dMirrorMoveR = std::min( 1.0, dMirrorMoveR + 1.0 * dt1 ); } } // compartment lights // if the vehicle has a controller, we base the light state on state of the controller otherwise we check the vehicle itself if( ( ctOwner != nullptr ? SectionLightsActive != MoverParameters->CompartmentLights.is_active : Mechanik != nullptr ? SectionLightsActive != MoverParameters->CompartmentLights.is_active : SectionLightsActive ) ) { // without controller switch the lights off toggle_lights(); } if (MoverParameters->DerailReason > 0) { switch (MoverParameters->DerailReason) { case 1: ErrorLog("Bad driving: " + asName + " derailed due to end of track"); break; case 2: ErrorLog("Bad driving: " + asName + " derailed due to too high speed"); break; case 3: ErrorLog("Bad dynamic: " + asName + " derailed due to track width"); break; // błąd w scenerii case 4: ErrorLog("Bad dynamic: " + asName + " derailed due to wrong track type"); break; // błąd w scenerii } MoverParameters->DerailReason = 0; //żeby tylko raz } if( MoverParameters->LoadStatus ) { LoadUpdate(); // zmiana modelu ładunku } update_exchange( dt ); return true; // Ra: chyba tak? } bool TDynamicObject::FastUpdate(double dt) { if (dt == 0.0) return true; // Ra: pauza double dDOMoveLen; if (!MoverParameters->PhysicActivation) return true; // McZapkie: wylaczanie fizyki gdy nie potrzeba if (!bEnabled) return false; // NOTE: coordinate system swap // TODO: replace with regular glm vectors TLocation const l { -vPosition.x, vPosition.z, vPosition.y }; TRotation const r { 0.0, 0.0, modelRot.z }; // McZapkie: parametry powinny byc pobierane z toru // ts.R=MyTrack->fRadius; // ts.Len= Max0R(MoverParameters->BDist,MoverParameters->ADist); // ts.dHtrack=Axle1.pPosition.y-Axle0.pPosition.y; // ts.dHrail=((Axle1.GetRoll())+(Axle0.GetRoll()))*0.5f; // tp.Width=MyTrack->fTrackWidth; // McZapkie-250202 // tp.friction= MyTrack->fFriction; // tp.CategoryFlag= MyTrack->iCategoryFlag&15; // tp.DamageFlag=MyTrack->iDamageFlag; // tp.QualityFlag=MyTrack->iQualityFlag; dDOMoveLen = MoverParameters->FastComputeMovement(dt, ts, tp, l, r); // ,ts,tp,tmpTraction); // Move(dDOMoveLen); // ResetdMoveLen(); FastMove(dDOMoveLen); if( MoverParameters->LoadStatus ) { LoadUpdate(); // zmiana modelu ładunku } update_exchange( dt ); return true; // Ra: chyba tak? } // McZapkie-040402: liczenie pozycji uwzgledniajac wysokosc szyn itp. // vector3 TDynamicObject::GetPosition() //{//Ra: pozycja pojazdu jest liczona zaraz po przesunięciu // return vPosition; //}; void TDynamicObject::TurnOff() { // wyłączenie rysowania submodeli zmiennych dla // egemplarza pojazdu btnOn = false; btCoupler1.TurnOff(); btCoupler2.TurnOff(); btCPneumatic1.TurnOff(); btCPneumatic1r.TurnOff(); btCPneumatic2.TurnOff(); btCPneumatic2r.TurnOff(); btPneumatic1.TurnOff(); btPneumatic1r.TurnOff(); btPneumatic2.TurnOff(); btPneumatic2r.TurnOff(); btCCtrl1.Turn( false ); btCCtrl2.Turn( false ); btCPass1.Turn( false ); btCPass2.Turn( false ); btEndSignals11.Turn( false ); btEndSignals13.Turn( false ); btEndSignals21.Turn( false ); btEndSignals23.Turn( false ); btEndSignals1.Turn( false ); btEndSignals2.Turn( false ); btEndSignalsTab1.Turn( false ); btEndSignalsTab2.Turn( false ); btHeadSignals11.TurnOff(); btHeadSignals12.TurnOff(); btHeadSignals13.TurnOff(); btHeadSignals21.TurnOff(); btHeadSignals22.TurnOff(); btHeadSignals23.TurnOff(); btMechanik1.Turn( false ); btMechanik2.Turn( false ); btShutters1.Turn( false ); btShutters2.Turn( false ); }; // przeliczanie dźwięków, bo będzie słychać bez wyświetlania sektora z pojazdem void TDynamicObject::RenderSounds() { if( false == simulation::is_ready ) { return; } if( Global.iPause != 0 ) { return; } if( ( m_startjoltplayed ) && ( ( std::abs( MoverParameters->AccSVBased ) < 0.01 ) || ( GetVelocity() < 0.01 ) ) ) { // if the vehicle comes to a stop set the movement jolt to play when it starts moving again m_startjoltplayed = false; } auto const dt{ Timer::GetDeltaRenderTime() }; m_powertrainsounds.render( *MoverParameters, dt ); auto volume{ 0.0 }; auto frequency{ 1.0 }; // NBMX dzwiek przetwornicy if( MoverParameters->ConverterFlag ) { if( MoverParameters->EngineType == TEngineType::ElectricSeriesMotor ) { auto const voltage { std::max( MoverParameters->GetTrainsetHighVoltage(), MoverParameters->PantographVoltage ) }; if( voltage > 0.0 ) { // NOTE: we do sound modulation here to avoid sudden jump on voltage loss frequency = ( voltage / ( MoverParameters->NominalVoltage * MoverParameters->RList[ MoverParameters->RlistSize ].Mn ) ); frequency *= sConverter.m_frequencyfactor + sConverter.m_frequencyoffset; sConverter.pitch( clamp( frequency, 0.75, 1.25 ) ); // arbitrary limits ) } } else { frequency = sConverter.m_frequencyoffset + sConverter.m_frequencyfactor * frequency; sConverter.pitch( clamp( frequency, 0.75, 1.25 ) ); // arbitrary limits ) } sConverter.play( sound_flags::exclusive | sound_flags::looping ); } else { sConverter.stop(); } if( MoverParameters->CompressorSpeed > 0.0 ) { // McZapkie! - dzwiek compressor.wav tylko gdy dziala sprezarka if( MoverParameters->CompressorFlag ) { sCompressor.play( sound_flags::exclusive | sound_flags::looping ); } else { sCompressor.stop(); } } // Winger 160404 - dzwiek malej sprezarki if( MoverParameters->PantCompFlag ) { sSmallCompressor.play( sound_flags::exclusive | sound_flags::looping ); } else { sSmallCompressor.stop(); } // heater sound if( ( true == MoverParameters->Heating ) && ( std::abs( MoverParameters->enrot ) > 0.01 ) ) { // TBD: check whether heating should depend on 'engine rotations' for electric vehicles sHeater .pitch( true == sHeater.is_combined() ? std::abs( MoverParameters->enrot ) * 60.f * 0.01f : 1.f ) .play( sound_flags::exclusive | sound_flags::looping ); } else { sHeater.stop(); } // brake system and braking sounds: // brake cylinder piston auto const brakepressureratio { std::max( 0.0, MoverParameters->BrakePress ) / std::max( 1.0, MoverParameters->MaxBrakePress[ 3 ] ) }; if( m_lastbrakepressure != -1.f ) { auto const quantizedratio { static_cast( 15 * brakepressureratio ) }; auto const lastbrakepressureratio { std::max( 0.f, m_lastbrakepressure ) / std::max( 1.0, MoverParameters->MaxBrakePress[ 3 ] ) }; auto const quantizedratiochange { quantizedratio - static_cast( 15 * lastbrakepressureratio ) }; if( quantizedratiochange > 0 ) { m_brakecylinderpistonadvance .pitch( true == m_brakecylinderpistonadvance.is_combined() ? quantizedratio * 0.01f : m_brakecylinderpistonadvance.m_frequencyoffset + m_brakecylinderpistonadvance.m_frequencyfactor * 1.f ) .play(); } else if( quantizedratiochange < 0 ) { m_brakecylinderpistonrecede .pitch( true == m_brakecylinderpistonrecede.is_combined() ? quantizedratio * 0.01f : m_brakecylinderpistonrecede.m_frequencyoffset + m_brakecylinderpistonrecede.m_frequencyfactor * 1.f ) .play(); } } // emergency brake if( MoverParameters->EmergencyValveFlow > 0.025 ) { // smooth out air flow rate m_emergencybrakeflow = ( m_emergencybrakeflow == 0.0 ? MoverParameters->EmergencyValveFlow : interpolate( m_emergencybrakeflow, MoverParameters->EmergencyValveFlow, 0.1 ) ); // scale volume based on the flow rate and on the pressure in the main pipe auto const flowpressure { clamp( m_emergencybrakeflow, 0.0, 1.0 ) + clamp( 0.1 * MoverParameters->PipePress, 0.0, 0.5 ) }; m_emergencybrake .pitch( m_emergencybrake.m_frequencyoffset + 1.0 * m_emergencybrake.m_frequencyfactor ) .gain( m_emergencybrake.m_amplitudeoffset + clamp( flowpressure, 0.0, 1.0 ) * m_emergencybrake.m_amplitudefactor ) .play( sound_flags::exclusive | sound_flags::looping ); } else if( MoverParameters->EmergencyValveFlow < 0.015 ) { m_emergencybrakeflow = 0.0; m_emergencybrake.stop(); } // air release if( m_lastbrakepressure != -1.f ) { // calculate rate of pressure drop in brake cylinder, once it's been initialized auto const brakepressuredifference{ m_lastbrakepressure - MoverParameters->BrakePress }; m_brakepressurechange = interpolate( m_brakepressurechange, brakepressuredifference / dt, 0.05f ); } m_lastbrakepressure = MoverParameters->BrakePress; // ensure some basic level of volume and scale it up depending on pressure in the cylinder; scale this by the air release rate volume = 20 * m_brakepressurechange * ( 0.25 + 0.75 * brakepressureratio ); if( ( m_brakepressurechange > 0.05 ) && ( brakepressureratio > 0.05 ) ) { rsUnbrake .gain( volume ) .play( sound_flags::exclusive | sound_flags::looping ); } else { // don't stop the sound too abruptly volume = std::max( 0.0, rsUnbrake.gain() - 0.5 * dt ); rsUnbrake.gain( volume ); if( volume < 0.05 ) { rsUnbrake.stop(); } } // Dzwiek odluzniacza if( MoverParameters->Hamulec->GetStatus() & b_rls ) { sReleaser .gain( clamp( MoverParameters->BrakePress * 1.25f, // arbitrary multiplier 0.f, 1.f ) ) .play( sound_flags::exclusive | sound_flags::looping ); } else { sReleaser.stop(); } // slipping wheels if( MoverParameters->SlippingWheels ) { if( ( MoverParameters->UnitBrakeForce > 100.0 ) && ( GetVelocity() > 1.0 ) ) { auto const velocitydifference{ GetVelocity() / MoverParameters->Vmax }; rsSlippery .gain( rsSlippery.m_amplitudeoffset + rsSlippery.m_amplitudefactor * velocitydifference ) .play( sound_flags::exclusive | sound_flags::looping ); } } else { rsSlippery.stop(); } // Dzwiek piasecznicy if( MoverParameters->SandDose ) { sSand.play( sound_flags::exclusive | sound_flags::looping ); } else { sSand.stop(); } // brakes auto brakeforceratio{ 0.0 }; if( //( false == mvOccupied->SlippingWheels ) && ( MoverParameters->UnitBrakeForce > 10.0 ) && ( MoverParameters->Vel > 0.05 ) ) { brakeforceratio = clamp( MoverParameters->UnitBrakeForce / std::max( 1.0, MoverParameters->BrakeForceR( 1.0, MoverParameters->Vel ) / ( MoverParameters->NAxles * std::max( 1, MoverParameters->NBpA ) ) ), 0.0, 1.0 ); rsBrake .pitch( rsBrake.m_frequencyoffset + MoverParameters->Vel * rsBrake.m_frequencyfactor ) .gain( rsBrake.m_amplitudeoffset + std::sqrt( brakeforceratio * interpolate( 0.4, 1.0, ( MoverParameters->Vel / ( 1 + MoverParameters->Vmax ) ) ) ) * rsBrake.m_amplitudefactor ) .play( sound_flags::exclusive | sound_flags::looping ); } else { rsBrake.stop(); } // yB: przyspieszacz (moze zadziala, ale dzwiek juz jest) if( true == bBrakeAcc ) { if( true == TestFlag( MoverParameters->Hamulec->GetSoundFlag(), sf_Acc ) ) { sBrakeAcc.play( sound_flags::exclusive ); } } // McZapkie-280302 - pisk mocno zacisnietych hamulcow if( MoverParameters->Vel > 2.5 ) { volume = rsPisk.m_amplitudeoffset + interpolate( -1.0, 1.0, brakeforceratio ) * rsPisk.m_amplitudefactor; if( volume > 0.075 ) { rsPisk .pitch( true == rsPisk.is_combined() ? MoverParameters->Vel * 0.01f : rsPisk.m_frequencyoffset + rsPisk.m_frequencyfactor * 1.f ) .gain( volume ) .play( sound_flags::exclusive | sound_flags::looping ); } } else { // don't stop the sound too abruptly volume = std::max( 0.0, rsPisk.gain() - ( rsPisk.gain() * 2.5 * dt ) ); rsPisk.gain( volume ); } if( volume < 0.05 ) { rsPisk.stop(); } // other sounds // load exchange if( MoverParameters->LoadStatus & 1 ) { m_exchangesounds.unloading.play( sound_flags::exclusive ); } else { m_exchangesounds.unloading.stop(); } if( MoverParameters->LoadStatus & 2 ) { m_exchangesounds.loading.play( sound_flags::exclusive ); } else { m_exchangesounds.loading.stop(); } // NBMX sygnal odjazdu if( MoverParameters->Doors.has_warning ) { auto const lowvoltagepower { MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable }; for( auto &departuresignalsound : m_departuresignalsounds ) { // TBD, TODO: per-location door state triggers? if( ( MoverParameters->DepartureSignal ) && ( lowvoltagepower ) /* || ( ( MoverParameters->DoorCloseCtrl = control::autonomous ) && ( ( ( false == MoverParameters->DoorLeftOpened ) && ( dDoorMoveL > 0.0 ) ) || ( ( false == MoverParameters->DoorRightOpened ) && ( dDoorMoveR > 0.0 ) ) ) ) */ ) { // for the autonomous doors play the warning automatically whenever a door is closing // MC: pod warunkiem ze jest zdefiniowane w chk departuresignalsound.play( sound_flags::exclusive | sound_flags::looping ); } else { departuresignalsound.stop(); } } } // NBMX Obsluga drzwi, MC: zuniwersalnione std::array const sides { side::right, side::left }; for( auto const side : sides ) { auto const &door { MoverParameters->Doors.instances[ side ] }; if( true == door.is_opening ) { // door sounds // due to potential wait for the doorstep we play the sound only during actual animation if( door.position > 0.f ) { for( auto &doorsounds : m_doorsounds ) { if( doorsounds.placement == side ) { // determine left side doors from their offset doorsounds.rsDoorOpen.play( sound_flags::exclusive ); doorsounds.rsDoorClose.stop(); } } } } if( true == door.is_closing ) { // door sounds can start playing before the door begins moving but shouldn't cease once the door closes if( door.position > 0.f ) { for( auto &doorsounds : m_doorsounds ) { if( doorsounds.placement == side ) { // determine left side doors from their offset doorsounds.rsDoorClose.play( sound_flags::exclusive ); doorsounds.rsDoorOpen.stop(); } } } } // doorstep sounds if( door.step_unfolding ) { for( auto &doorsounds : m_doorsounds ) { if( doorsounds.placement == side ) { doorsounds.step_open.play( sound_flags::exclusive ); doorsounds.step_close.stop(); } } } if( door.step_folding ) { if( door.step_position < 1.0f ) { // sanity check, the vehicles may keep the doorstep unfolded until the door close for( auto &doorsounds : m_doorsounds ) { if( doorsounds.placement == side ) { // determine left side doors from their offset doorsounds.step_close.play( sound_flags::exclusive ); doorsounds.step_open.stop(); } } } } } // door locks if( MoverParameters->Doors.is_locked != m_doorlocks ) { // toggle state of the locks... m_doorlocks = !m_doorlocks; // ...and play relevant sounds for( auto &door : m_doorsounds ) { if( m_doorlocks ) { door.lock.play( sound_flags::exclusive ); } else { door.unlock.play( sound_flags::exclusive ); } } } // horns { // for moving vehicle combine regular horn activation flag with emergency brake horn activation flag, if the brake is active auto const warningsignal { ( ( MoverParameters->Vel > 0.5 ) && ( MoverParameters->AlarmChainFlag ) ? MoverParameters->EmergencyBrakeWarningSignal : 0 ) | ( MoverParameters->WarningSignal ) }; if( TestFlag( warningsignal, 1 ) ) { sHorn1.play( sound_flags::exclusive | sound_flags::looping ); } else { sHorn1.stop(); } if( TestFlag( warningsignal, 2 ) ) { sHorn2.play( sound_flags::exclusive | sound_flags::looping ); } else { sHorn2.stop(); } if( TestFlag( warningsignal, 4 ) ) { sHorn3.play( sound_flags::exclusive | sound_flags::looping ); } else { sHorn3.stop(); } } // szum w czasie jazdy if( ( GetVelocity() > 0.5 ) #ifdef EU07_SOUND_BOGIESOUNDS && ( false == m_bogiesounds.empty() ) #endif && ( // compound test whether the vehicle belongs to user-driven consist (as these don't emit outer noise in cab view) FreeFlyModeFlag ? true : // in external view all vehicles emit outer noise // Global.pWorld->train() == nullptr ? true : // (can skip this check, with no player train the external view is a given) ctOwner == nullptr ? true : // standalone vehicle, can't be part of user-driven train ctOwner != simulation::Train->Dynamic()->ctOwner ? true : // confirmed isn't a part of the user-driven train Global.CabWindowOpen ? true : // sticking head out we get to hear outer noise false ) ) { #ifdef EU07_SOUND_BOGIESOUNDS auto const &bogiesound { m_bogiesounds.front() }; #else auto const &bogiesound { m_outernoise }; #endif // frequency calculation auto const normalizer { ( true == bogiesound.is_combined() ? MoverParameters->Vmax * 0.01f : 1.f ) }; frequency = bogiesound.m_frequencyoffset + bogiesound.m_frequencyfactor * MoverParameters->Vel * normalizer; // volume calculation volume = bogiesound.m_amplitudeoffset + bogiesound.m_amplitudefactor * MoverParameters->Vel; if( brakeforceratio > 0.0 ) { // hamulce wzmagaja halas volume *= 1 + 0.125 * brakeforceratio; } // scale volume by track quality // TODO: track quality and/or environment factors as separate subroutine volume *= interpolate( 0.8, 1.2, clamp( MyTrack->iQualityFlag / 20.0, 0.0, 1.0 ) ); // for single sample sounds muffle the playback at low speeds if( false == bogiesound.is_combined() ) { volume *= interpolate( 0.0, 1.0, clamp( MoverParameters->Vel / 40.0, 0.0, 1.0 ) ); } if( volume > 0.05 ) { // apply calculated parameters to all motor instances #ifdef EU07_SOUND_BOGIESOUNDS for( auto &bogiesound : m_bogiesounds ) { bogiesound .pitch( frequency ) // arbitrary limits to prevent the pitch going out of whack .gain( volume ) .play( sound_flags::exclusive | sound_flags::looping ); } #else m_outernoise .pitch( frequency ) // arbitrary limits to prevent the pitch going out of whack .gain( volume ) .play( sound_flags::exclusive | sound_flags::looping ); #endif } else { // stop all noise instances #ifdef EU07_SOUND_BOGIESOUNDS for( auto &bogiesound : m_bogiesounds ) { bogiesound.stop(); } #else m_outernoise.stop(); #endif } } else { // don't play the optional ending sound if the listener switches views #ifdef EU07_SOUND_BOGIESOUNDS for( auto &bogiesound : m_bogiesounds ) { bogiesound.stop( false == FreeFlyModeFlag ); } #else m_outernoise.stop( false == FreeFlyModeFlag ); #endif } // flat spot sound if( MoverParameters->CategoryFlag == 1 ) { // trains only if( ( GetVelocity() > 1.0 ) && ( MoverParameters->WheelFlat > 5.0 ) ) { m_wheelflat .pitch( m_wheelflat.m_frequencyoffset + std::abs( MoverParameters->nrot ) * m_wheelflat.m_frequencyfactor ) .gain( m_wheelflat.m_amplitudeoffset + m_wheelflat.m_amplitudefactor * ( ( 1.0 + ( MoverParameters->Vel / MoverParameters->Vmax ) + clamp( MoverParameters->WheelFlat / 60.0, 0.0, 1.0 ) ) / 3.0 ) ) .play( sound_flags::exclusive | sound_flags::looping ); } else { m_wheelflat.stop(); } } // youBy: dzwiek ostrych lukow i ciasnych zwrotek if( ( MoverParameters->Vel > 5.0 ) && ( ts.R * ts.R > 1.0 ) && ( std::abs( ts.R ) < 15000.0 ) ) { // scale volume with curve radius and vehicle speed volume = std::abs( MoverParameters->AccN ) // * MoverParameters->AccN * interpolate( 0.5, 1.0, clamp( MoverParameters->Vel / 40.0, 0.0, 1.0 ) ) * ( ( ( MyTrack->eType == tt_Switch ) && ( std::abs( ts.R ) < 1500.0 ) ) ? 100.0 : 1.0 ); } else { volume = 0; } if( volume > 0.05 ) { rscurve .pitch( true == rscurve.is_combined() ? MoverParameters->Vel * 0.01f : rscurve.m_frequencyoffset + rscurve.m_frequencyfactor * 1.f ) .gain( 2.5 * volume ) .play( sound_flags::exclusive | sound_flags::looping ); } else { rscurve.stop(); } // movement start jolt if( false == m_startjoltplayed ) { auto const velocity { GetVelocity() }; if( ( MoverParameters->V > 0.0 ? ( MoverParameters->AccSVBased > 0.1 ) : ( MoverParameters->AccSVBased < 0.1 ) ) && ( velocity > 1.0 ) && ( velocity < 15.0 ) ) { m_startjolt.play( sound_flags::exclusive ); m_startjoltplayed = true; } } // McZapkie! - to wazne - SoundFlag wystawiane jest przez moje moduly // gdy zachodza pewne wydarzenia komentowane dzwiekiem. if( TestFlag( MoverParameters->SoundFlag, sound::pneumatic ) ) { // pneumatic relay dsbPneumaticRelay .gain( true == TestFlag( MoverParameters->SoundFlag, sound::loud ) ? 1.0f : 0.8f ) .play(); } // couplers int couplerindex { 0 }; for( auto &couplersounds : m_couplersounds ) { auto &coupler { MoverParameters->Couplers[ couplerindex ] }; if( coupler.sounds == sound::none ) { ++couplerindex; continue; } if( true == TestFlag( coupler.sounds, sound::bufferclash ) ) { // zderzaki uderzaja o siebie if( true == TestFlag( coupler.sounds, sound::loud ) ) { // loud clash if( false == couplersounds.dsbBufferClamp_loud.empty() ) { // dedicated sound for loud clash couplersounds.dsbBufferClamp_loud .gain( 1.f ) .play( sound_flags::exclusive ); } else { // fallback on the standard sound couplersounds.dsbBufferClamp .gain( 1.f ) .play( sound_flags::exclusive ); } } else { // basic clash couplersounds.dsbBufferClamp .gain( 1.f ) .play( sound_flags::exclusive ); } } if( true == TestFlag( coupler.sounds, sound::couplerstretch ) ) { // sprzegi sie rozciagaja if( true == TestFlag( coupler.sounds, sound::loud ) ) { // loud stretch if( false == couplersounds.dsbCouplerStretch_loud.empty() ) { // dedicated sound for loud stretch couplersounds.dsbCouplerStretch_loud .gain( 1.f ) .play( sound_flags::exclusive ); } else { // fallback on the standard sound couplersounds.dsbCouplerStretch .gain( 1.f ) .play( sound_flags::exclusive ); } } else { // basic clash couplersounds.dsbCouplerStretch .gain( 1.f ) .play( sound_flags::exclusive ); } } // TODO: dedicated sound for each connection type // until then, play legacy placeholders: if( ( coupler.sounds & ( sound::attachcoupler | sound::attachcontrol | sound::attachgangway ) ) != 0 ) { m_couplersounds[ couplerindex ].dsbCouplerAttach.play(); } if( ( coupler.sounds & ( sound::attachbrakehose | sound::attachmainhose | sound::attachheating ) ) != 0 ) { m_couplersounds[ couplerindex ].dsbCouplerDetach.play(); } if( true == TestFlag( coupler.sounds, sound::detachall ) ) { // TODO: dedicated disconnect sounds m_couplersounds[ couplerindex ].dsbCouplerAttach.play(); m_couplersounds[ couplerindex ].dsbCouplerDetach.play(); } if( true == TestFlag( coupler.sounds, sound::attachadapter ) ) { m_couplersounds[ couplerindex ].dsbAdapterAttach.play(); } if( true == TestFlag( coupler.sounds, sound::removeadapter ) ) { m_couplersounds[ couplerindex ].dsbAdapterRemove.play(); } ++couplerindex; coupler.sounds = 0; } MoverParameters->SoundFlag = 0; // McZapkie! - koniec obslugi dzwiekow z mover.pas // special events if( MoverParameters->EventFlag ) { // McZapkie: w razie wykolejenia if( true == TestFlag( MoverParameters->DamageFlag, dtrain_out ) ) { rsDerailment.play( sound_flags::exclusive ); } MoverParameters->EventFlag = false; } } // calculates distance between event-starting axle and front of the vehicle double TDynamicObject::tracing_offset() const { auto const axletoend{ ( GetLength() - fAxleDist ) * 0.5 }; return ( iAxleFirst ? axletoend : axletoend + iDirection * fAxleDist ); } // TODO: compute and cache radius during vehicle initialization double TDynamicObject::radius() const { glm::vec3 diagonal( static_cast( MoverParameters->Dim.L ), static_cast( MoverParameters->Dim.H ), static_cast( MoverParameters->Dim.W ) ); return glm::length( diagonal ) * 0.5f; } // McZapkie-250202 // wczytywanie pliku z danymi multimedialnymi (dzwieki) void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string const &ReplacableSkin ) { Global.asCurrentDynamicPath = asBaseDir; std::string asFileName = asBaseDir + TypeName + ".mmd"; std::string asAnimName; bool Stop_InternalData = false; pants = NULL; // wskaźnik pierwszego obiektu animującego dla pantografów { // preliminary check whether the file exists cParser parser( TypeName + ".mmd", cParser::buffer_FILE, asBaseDir ); if( false == parser.ok() ) { ErrorLog( "Failed to load appearance data for vehicle " + MoverParameters->Name ); return; } } // use #include wrapper to access the appearance data file // this allows us to provide the file content with user-defined parameters cParser parser( "include " + TypeName + ".mmd" + " " + asName // (p1) + " " + TypeName // (p2) + " " + ReplacableSkin // (p3) + " end", cParser::buffer_TEXT, asBaseDir ); std::string token; do { token = ""; parser.getTokens(); parser >> token; if( ( token == "models:" ) || ( token == "\xef\xbb\xbfmodels:" ) ) { // crude way to handle utf8 bom potentially appearing before the first token // modele i podmodele m_materialdata.multi_textures = 0; // czy jest wiele tekstur wymiennych? parser.getTokens(); parser >> asModel; replace_slashes( asModel ); if( asModel[asModel.size() - 1] == '#' ) // Ra 2015-01: nie podoba mi siê to { // model wymaga wielu tekstur wymiennych m_materialdata.multi_textures = 1; asModel.erase( asModel.length() - 1 ); } // name can contain leading slash, erase it to avoid creation of double slashes when the name is combined with current directory if( asModel[ 0 ] == '/' ) { asModel.erase( 0, 1 ); } /* // never really used, may as well get rid of it std::size_t i = asModel.find( ',' ); if ( i != std::string::npos ) { // Ra 2015-01: może szukać przecinka w nazwie modelu, a po przecinku była by liczba tekstur? if( i < asModel.length() ) { m_materialdata.multi_textures = asModel[ i + 1 ] - '0'; } m_materialdata.multi_textures = clamp( m_materialdata.multi_textures, 0, 1 ); // na razie ustawiamy na 1 } */ asModel = asBaseDir + asModel; // McZapkie 2002-07-20: dynamics maja swoje modele w dynamics/basedir Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/... mdModel = TModelsManager::GetModel(asModel, true); if (ReplacableSkin != "none") { m_materialdata.assign( ReplacableSkin ); } Global.asCurrentTexturePath = szTexturePath; // z powrotem defaultowa sciezka do tekstur do { token = ""; parser.getTokens(); parser >> token; if( token == "animations:" ) { // Ra: ustawienie ilości poszczególnych animacji - musi być jako pierwsze, inaczej ilości będą domyślne /* if( nullptr == pAnimations ) */ if( true == pAnimations.empty() ) { // jeśli nie ma jeszcze tabeli animacji, można odczytać nowe ilości int co = 0; iAnimations = 0; int ile; do { // kolejne liczby to ilość animacj, -1 to znacznik końca ile = -1; parser.getTokens( 1, false ); parser >> ile; // ilość danego typu animacji if (ile >= 0) { iAnimType[co] = ile; // zapamiętanie iAnimations += ile; // ogólna ilość animacji } else { iAnimType[co] = 0; } ++co; } while ( (ile >= 0) && (co < ANIM_TYPES) ); //-1 to znacznik końca pAnimations.resize( iAnimations ); int i, j, k = 0, sm = 0; for (j = 0; j < ANIM_TYPES; ++j) for (i = 0; i < iAnimType[j]; ++i) { if (j == ANIM_PANTS) // zliczamy poprzednie animacje if (!pants) if (iAnimType[ANIM_PANTS]) // o ile jakieś pantografy są (a domyślnie są) pants = &pAnimations[k]; // zapamiętanie na potrzeby wyszukania submodeli pAnimations[k].iShift = sm; // przesunięcie do przydzielenia wskaźnika sm += pAnimations[k++].TypeSet(j); // ustawienie typu animacji i zliczanie tablicowanych submodeli } if (sm) // o ile są bardziej złożone animacje { pAnimated = new TSubModel *[sm]; // tabela na animowane submodele for (k = 0; k < iAnimations; ++k) pAnimations[k].smElement = pAnimated + pAnimations[k].iShift; // przydzielenie wskaźnika do tabelki } } } else if(token == "lowpolyinterior:") { // ABu: wnetrze lowpoly parser.getTokens(); parser >> asModel; replace_slashes( asModel ); erase_leading_slashes( asModel ); asModel = asBaseDir + asModel; // McZapkie-200702 - dynamics maja swoje modele w dynamic/basedir Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/... mdLowPolyInt = TModelsManager::GetModel(asModel, true); } else if(token == "coupleradapter:") { // coupling adapter data parser.getTokens( 3 ); parser >> m_coupleradapter.model >> m_coupleradapter.position.x >> m_coupleradapter.position.y; replace_slashes( m_coupleradapter.model ); erase_leading_slashes( m_coupleradapter.model ); } else if(token == "attachments:") { // additional 3d models attached to main body // content provided as a series of values together enclosed in "{}" // each value is a name of additional 3d model // value can be optionally set of values enclosed in "[]" in which case one value will be picked randomly // TBD: reconsider something more yaml-compliant and/or ability to define offset and rotation while( ( ( token = deserialize_random_set( parser ) ) != "" ) && ( token != "}" ) ) { if( token == "{" ) { continue; } replace_slashes( token ); Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/... auto *attachmentmodel { TModelsManager::GetModel( asBaseDir + token, true ) }; if( attachmentmodel != nullptr ) { mdAttachments.emplace_back( attachmentmodel ); } } } else if(token == "loads:") { // default load visualization models overrides // content provided as "key: value" pairs together enclosed in "{}" // value can be optionally set of values enclosed in "[]" in which case one value will be picked randomly while( ( ( token = parser.getToken() ) != "" ) && ( token != "}" ) ) { if( token[ token.size() - 1 ] == ':' ) { auto loadmodel { deserialize_random_set( parser ) }; replace_slashes( loadmodel ); LoadModelOverrides.emplace( token.erase( token.size() - 1 ), loadmodel ); } } } else if( token == "brakemode:" ) { // Ra 15-01: gałka nastawy hamulca parser.getTokens(); parser >> asAnimName; smBrakeMode = GetSubmodelFromName( mdModel, asAnimName ); // jeszcze wczytać kąty obrotu dla poszczególnych ustawień } else if( token == "loadmode:" ) { // Ra 15-01: gałka nastawy hamulca parser.getTokens(); parser >> asAnimName; smLoadMode = GetSubmodelFromName( mdModel, asAnimName ); // jeszcze wczytać kąty obrotu dla poszczególnych ustawień } else if (token == "animwheelprefix:") { // prefiks kręcących się kół int i, k, m; unsigned int j; parser.getTokens( 1, false ); parser >> token; 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 = GetSubmodelFromName( mdModel, asAnimName ); if (pAnimations[i].smAnimated) { //++iAnimatedAxles; pAnimations[i].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu pAnimations[i].yUpdate = std::bind( &TDynamicObject::UpdateAxle, this, std::placeholders::_1 ); pAnimations[i].fMaxDist = 50 * MoverParameters->WheelDiameter; // nie kręcić w większej odległości pAnimations[i].fMaxDist *= pAnimations[i].fMaxDist * MoverParameters->WheelDiameter; // 50m do kwadratu, a średnica do trzeciej pAnimations[i].fMaxDist *= Global.fDistanceFactor; // współczynnik przeliczeniowy jakości ekranu } } // Ra: ustawianie indeksów osi for (i = 0; i < iAnimType[ANIM_WHEELS]; ++i) // ilość osi (zabezpieczenie przed błędami w CHK) pAnimations[i].dWheelAngle = 1; // domyślnie wskaźnik na napędzające i = 0; j = 1; k = 0; m = 0; // numer osi; kolejny znak; ile osi danego typu; która średnica if ((MoverParameters->WheelDiameterL != MoverParameters->WheelDiameter) || (MoverParameters->WheelDiameterT != MoverParameters->WheelDiameter)) { // obsługa różnych średnic, o ile występują while ((i < iAnimType[ANIM_WHEELS]) && (j <= MoverParameters->AxleArangement.length())) { // wersja ze wskaźnikami jest bardziej elastyczna na nietypowe układy if ((k >= 'A') && (k <= 'J')) // 10 chyba maksimum? { pAnimations[i++].dWheelAngle = 1; // obrót osi napędzających --k; // następna będzie albo taka sama, albo bierzemy kolejny znak m = 2; // następujące toczne będą miały inną średnicę } else if ((k >= '1') && (k <= '9')) { pAnimations[i++].dWheelAngle = m; // obrót osi tocznych --k; // następna będzie albo taka sama, albo bierzemy kolejny znak } else k = MoverParameters->AxleArangement[j++]; // pobranie kolejnego znaku } } } // else if (str==AnsiString("animrodprefix:")) //prefiks wiazarow dwoch // { // str= Parser->GetNextSymbol(); // for (int i=1; i<=2; i++) // {//McZapkie-050402: wyszukiwanie max 2 wiazarow o nazwie str* // asAnimName=str+i; // smWiazary[i-1]=mdModel->GetFromName(asAnimName.c_str()); // smWiazary[i-1]->WillBeAnimated(); // } // } else if( token == "animpantprefix:" ) { // Ra: pantografy po nowemu mają literki i numerki } // Pantografy - Winger 160204 else if( token == "animpantrd1prefix:" ) { // prefiks ramion dolnych 1 parser.getTokens(); parser >> token; float4x4 m; // macierz do wyliczenia pozycji i wektora ruchu pantografu TSubModel *sm; if (pants) for (int i = 0; i < iAnimType[ANIM_PANTS]; i++) { // Winger 160204: wyszukiwanie max 2 patykow o nazwie str* asAnimName = token + std::to_string(i + 1); sm = GetSubmodelFromName( mdModel, asAnimName ); pants[i].smElement[0] = sm; // jak NULL, to nie będzie animowany if (sm) { // w EP09 wywalało się tu z powodu NULL sm->WillBeAnimated(); sm->ParentMatrix(&m); // pobranie macierzy transformacji // m(3)[1]=m[3][1]+0.054; //w górę o wysokość ślizgu (na razie tak) pants[i].fParamPants->vPos.z = m[3][0]; // przesunięcie w bok (asymetria) pants[i].fParamPants->vPos.y = m[3][1]; // przesunięcie w górę odczytane z modelu if ((sm = pants[i].smElement[0]->ChildGet()) != NULL) { // jeśli ma potomny, można policzyć długość (odległość potomnego od osi obrotu) m = float4x4(*sm->GetMatrix()); // wystarczyłby wskaźnik, nie trzeba kopiować // może trzeba: pobrać macierz dolnego ramienia, wyzerować przesunięcie, przemnożyć przez macierz górnego pants[i].fParamPants->fHoriz = -fabs(m[3][1]); pants[i].fParamPants->fLenL1 = hypot(m[3][1], m[3][2]); // po osi OX nie potrzeba pants[i].fParamPants->fAngleL0 = atan2(fabs(m[3][2]), fabs(m[3][1])); // if (pants[i].fParamPants->fAngleL0fAngleL0+=M_PI; //gdyby w odwrotną stronę wyszło // if // ((pants[i].fParamPants->fAngleL0<0.03)||(pants[i].fParamPants->fAngleL0>0.09)) // //normalnie ok. 0.05 // pants[i].fParamPants->fAngleL0=pants[i].fParamPants->fAngleL; pants[i].fParamPants->fAngleL = pants[i].fParamPants->fAngleL0; // początkowy kąt dolnego ramienia if ((sm = sm->ChildGet()) != NULL) { // jeśli dalej jest ślizg, można policzyć długość górnego ramienia m = float4x4(*sm->GetMatrix()); // wystarczyłby wskaźnik, // nie trzeba kopiować trzeba by uwzględnić macierz dolnego ramienia, żeby uzyskać kąt do poziomu... pants[i].fParamPants->fHoriz += fabs(m(3)[1]); // różnica długości rzutów ramion na // płaszczyznę podstawy (jedna dodatnia, druga ujemna) pants[i].fParamPants->fLenU1 = hypot( m[3][1], m[3][2] ); // po osi OX nie potrzeba // pants[i].fParamPants->pantu=acos((1.22*cos(pants[i].fParamPants->fAngleL)+0.535)/1.755); //górne ramię // pants[i].fParamPants->fAngleU0=acos((1.176289*cos(pants[i].fParamPants->fAngleL)+0.54555075)/1.724482197); //górne ramię pants[i].fParamPants->fAngleU0 = atan2( fabs(m[3][2]), fabs(m[3][1]) ); // początkowy kąt górnego ramienia, odczytany z modelu // if (pants[i].fParamPants->fAngleU0fAngleU0+=M_PI; //gdyby w odwrotną stronę wyszło // if (pants[i].fParamPants->fAngleU0<0) // pants[i].fParamPants->fAngleU0=-pants[i].fParamPants->fAngleU0; // if // ((pants[i].fParamPants->fAngleU0<0.00)||(pants[i].fParamPants->fAngleU0>0.09)) //normalnie ok. 0.07 // pants[i].fParamPants->fAngleU0=acos((pants[i].fParamPants->fLenL1*cos(pants[i].fParamPants->fAngleL)+pants[i].fParamPants->fHoriz)/pants[i].fParamPants->fLenU1); pants[i].fParamPants->fAngleU = pants[i].fParamPants->fAngleU0; // początkowy kąt // Ra: ze względu na to, że niektóre modele pantografów są zrąbane, ich mierzenie ma obecnie ograniczony sens sm->ParentMatrix(&m); // pobranie macierzy transformacji pivota ślizgu względem wstawienia pojazdu float det = Det(m); if (std::fabs(det - 1.0) < 0.001) // dopuszczamy 1 promil błędu na skalowaniu ślizgu { // skalowanie jest w normie, można pobrać wymiary z modelu pants[i].fParamPants->fHeight = sm->MaxY(m); // przeliczenie maksimum wysokości wierzchołków względem macierzy pants[i].fParamPants->fHeight -= m[3][1]; // odjęcie wysokości pivota ślizgu pants[i].fParamPants->vPos.x = m[3][2]; // przy okazji odczytać z modelu pozycję w długości // ErrorLog("Model OK: "+asModel+", // height="+pants[i].fParamPants->fHeight); // ErrorLog("Model OK: "+asModel+", // pos.x="+pants[i].fParamPants->vPos.x); } else { // gdy ktoś przesadził ze skalowaniem pants[i].fParamPants->fHeight = 0.0; // niech będzie odczyt z pantfactors: ErrorLog( "Bad model: " + asModel + ", scale of " + (sm->pName) + " is " + std::to_string(100.0 * det) + "%", logtype::model ); } } } } else { // brak ramienia pants[ i ].fParamPants->fHeight = 0.0; // niech będzie odczyt z pantfactors: ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); } } } else if( token == "animpantrd2prefix:" ) { // prefiks ramion dolnych 2 parser.getTokens(); parser >> token; float4x4 m; // macierz do wyliczenia pozycji i wektora ruchu pantografu TSubModel *sm; if( pants ) { for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) { // Winger 160204: wyszukiwanie max 2 patykow o nazwie str* asAnimName = token + std::to_string( i + 1 ); sm = GetSubmodelFromName( mdModel, asAnimName ); pants[ i ].smElement[ 1 ] = sm; // jak NULL, to nie będzie animowany if( sm ) { // w EP09 wywalało się tu z powodu NULL sm->WillBeAnimated(); if( pants[ i ].fParamPants->vPos.y == 0.0 ) { // jeśli pierwsze ramię nie ustawiło tej wartości, próbować drugim //!!!! docelowo zrobić niezależną animację ramion z każdej strony m = float4x4( *sm->GetMatrix()); // skopiowanie, bo będziemy mnożyć m( 3 )[ 1 ] = m[ 3 ][ 1 ] + 0.054; // w górę o wysokość ślizgu (na razie tak) while( sm->Parent ) { if( sm->Parent->GetMatrix() ) m = *sm->Parent->GetMatrix() * m; sm = sm->Parent; } pants[ i ].fParamPants->vPos.z = m[3][0]; // przesunięcie w bok (asymetria) pants[ i ].fParamPants->vPos.y = m[3][1]; // przesunięcie w górę odczytane z modelu } } else ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia } } } else if( token == "animpantrg1prefix:" ) { // prefiks ramion górnych 1 parser.getTokens(); parser >> token; if( pants ) { for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) { // Winger 160204: wyszukiwanie max 2 patykow o nazwie str* asAnimName = token + std::to_string( i + 1 ); pants[ i ].smElement[ 2 ] = GetSubmodelFromName( mdModel, asAnimName ); if( pants[ i ].smElement[ 2 ] ) { pants[ i ].smElement[ 2 ]->WillBeAnimated(); } else { ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia } } } } else if( token == "animpantrg2prefix:" ) { // prefiks ramion górnych 2 parser.getTokens(); parser >> token; if( pants ) { for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) { // Winger 160204: wyszukiwanie max 2 patykow o nazwie str* asAnimName = token + std::to_string( i + 1 ); pants[ i ].smElement[ 3 ] = GetSubmodelFromName( mdModel, asAnimName ); if( pants[ i ].smElement[ 3 ] ) { pants[ i ].smElement[ 3 ]->WillBeAnimated(); } else { ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia } } } } else if( token == "animpantslprefix:" ) { // prefiks ślizgaczy parser.getTokens(); parser >> token; if( pants ) { for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) { // Winger 160204: wyszukiwanie max 2 patykow o nazwie str* asAnimName = token + std::to_string( i + 1 ); pants[ i ].smElement[ 4 ] = GetSubmodelFromName( mdModel, asAnimName ); if( pants[ i ].smElement[ 4 ] ) { pants[ i ].smElement[ 4 ]->WillBeAnimated(); pants[ i ].yUpdate = std::bind( &TDynamicObject::UpdatePant, this, std::placeholders::_1 ); pants[ i ].fMaxDist = 300 * 300; // nie podnosić w większej odległości pants[ i ].iNumber = i; } else { ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia } } } } else if( token == "pantfactors:" ) { // Winger 010304: // parametry pantografow double pant1x, pant2x, pant1h, pant2h; parser.getTokens( 4, false ); parser >> pant1x >> pant2x >> pant1h // wysokość pierwszego ślizgu >> pant2h;// wysokość drugiego ślizgu if( pant1h > 0.5 ) { pant1h = pant2h; // tu może być zbyt duża wartość } if ((pant1x < 0) && (pant2x > 0)) // pierwsza powinna być dodatnia, a druga ujemna { pant1x = -pant1x; pant2x = -pant2x; } if( pants ) { for( int i = 0; i < iAnimType[ ANIM_PANTS ]; ++i ) { // przepisanie współczynników do pantografów (na razie nie będzie lepiej) auto &pantograph { *(pants[ i ].fParamPants) }; pantograph.fAngleL = pantograph.fAngleL0; // początkowy kąt dolnego ramienia pantograph.fAngleU = pantograph.fAngleU0; // początkowy kąt // pants[i].fParamPants->PantWys=1.22*sin(pants[i].fParamPants->fAngleL)+1.755*sin(pants[i].fParamPants->fAngleU); // //wysokość początkowa // pants[i].fParamPants->PantWys=1.176289*sin(pants[i].fParamPants->fAngleL)+1.724482197*sin(pants[i].fParamPants->fAngleU); // //wysokość początkowa if( pantograph.fHeight == 0.0 ) // gdy jest nieprawdopodobna wartość (np. nie znaleziony ślizg) { // gdy pomiary modelu nie udały się, odczyt podanych parametrów z MMD pantograph.vPos.x = ( i & 1 ) ? pant2x : pant1x; pantograph.fHeight = ( i & 1 ) ? pant2h : pant1h; // wysokość ślizgu jest zapisana w MMD } pantograph.PantWys = pantograph.fLenL1 * sin( pantograph.fAngleL ) + pantograph.fLenU1 * sin( pantograph.fAngleU ) + pantograph.fHeight; // wysokość początkowa // pants[i].fParamPants->vPos.y=panty-panth-pants[i].fParamPants->PantWys; //np. 4.429-0.097=4.332=~4.335 if( pantograph.vPos.y == 0.0 ) { // crude fallback, place the pantograph(s) atop of the vehicle-sized box pantograph.vPos.y = MoverParameters->Dim.H - pantograph.fHeight - pantograph.PantWys; } // pants[i].fParamPants->vPos.z=0; //niezerowe dla pantografów asymetrycznych pantograph.PantTraction = pantograph.PantWys; // połowa szerokości ślizgu; jest w "Power: CSW=" pantograph.fWidth = 0.5 * MoverParameters->EnginePowerSource.CollectorParameters.CSW; // create sound emitters for the pantograph m_pantographsounds.emplace_back(); } } } else if (token == "animpistonprefix:") { // prefiks tłoczysk - na razie uzywamy modeli pantografów parser.getTokens(1, false); parser >> token; for( int i = 1; i <= 2; ++i ) { // asAnimName=str+i; // smPatykird1[i-1]=mdModel->GetFromName(asAnimName.c_str()); // smPatykird1[i-1]->WillBeAnimated(); } } else if( token == "animconrodprefix:" ) { // prefiks korbowodów - na razie używamy modeli pantografów parser.getTokens(); parser >> token; for( int i = 1; i <= 2; i++ ) { // asAnimName=str+i; // smPatykirg1[i-1]=mdModel->GetFromName(asAnimName.c_str()); // smPatykirg1[i-1]->WillBeAnimated(); } } else if( token == "pistonfactors:" ) { // Ra: parametry // silnika parowego // (tłoka) /* //Ra: tymczasowo wyłączone ze względu na porządkowanie animacji pantografów pant1x=Parser->GetNextSymbol().ToDouble(); //kąt przesunięcia dla pierwszego tłoka pant2x=Parser->GetNextSymbol().ToDouble(); //kąt przesunięcia dla drugiego tłoka panty=Parser->GetNextSymbol().ToDouble(); //długość korby (r) panth=Parser->GetNextSymbol().ToDouble(); //długoś korbowodu (k) */ MoverParameters->EnginePowerSource.PowerType = TPowerType::SteamPower; // Ra: po chamsku, ale z CHK nie działa } else if( token == "animreturnprefix:" ) { // prefiks drążka mimośrodowego - na razie używamy modeli pantografów parser.getTokens(1, false); parser >> token; for( int i = 1; i <= 2; i++ ) { // asAnimName=str+i; // smPatykird2[i-1]=mdModel->GetFromName(asAnimName.c_str()); // smPatykird2[i-1]->WillBeAnimated(); } } else if (token == "animexplinkprefix:"){ // animreturnprefix: // prefiks jarzma - na razie używamy modeli pantografów parser.getTokens(1, false); parser >> token; for( int i = 1; i <= 2; i++ ) { // asAnimName=str+i; // smPatykirg2[i-1]=mdModel->GetFromName(asAnimName.c_str()); // smPatykirg2[i-1]->WillBeAnimated(); } } else if( token == "animpendulumprefix:" ) { // prefiks wahaczy parser.getTokens(); parser >> token; asAnimName = ""; for( int i = 1; i <= 4; ++i ) { // McZapkie-050402: wyszukiwanie max 4 wahaczy o nazwie str* asAnimName = token + std::to_string( i ); smWahacze[ i - 1 ] = GetSubmodelFromName( mdModel, asAnimName ); if( smWahacze[ i - 1 ] ) { smWahacze[ i - 1 ]->WillBeAnimated(); } } parser.getTokens(); parser >> token; if( token == "pendulumamplitude:" ) { parser.getTokens( 1, false ); parser >> fWahaczeAmp; } } else if( token == "animdoorprefix:" ) { // nazwa animowanych drzwi int i, j; parser.getTokens(1, false); parser >> token; for (i = 0, j = 0; i < ANIM_DOORS; ++i) j += iAnimType[i]; // zliczanie wcześniejszych animacji for (i = 0; i < iAnimType[ANIM_DOORS]; ++i) // liczba drzwi { // NBMX wrzesien 2003: wyszukiwanie drzwi o nazwie str* // ustalenie submodelu asAnimName = token + std::to_string(i + 1); pAnimations[ i + j ].smAnimated = GetSubmodelFromName( mdModel, asAnimName ); if (pAnimations[i + j].smAnimated) { //++iAnimatedDoors; pAnimations[i + j].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu switch( MoverParameters->Doors.type ) { // od razu zapinamy potrzebny typ animacji case 1: pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorTranslate, this, std::placeholders::_1 ); break; case 2: pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorRotate, this, std::placeholders::_1 ); break; case 3: pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorFold, this, std::placeholders::_1 ); break; // obrót 3 kolejnych submodeli case 4: pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorPlug, this, std::placeholders::_1 ); break; default: break; } pAnimations[i + j].iNumber = i + 1; // parzyste działają inaczej niż nieparzyste pAnimations[i + j].fMaxDist = 300 * 300; // drzwi to z daleka widać /* // NOTE: no longer used pAnimations[i + j].fSpeed = Random(150); // oryginalny koncept z DoorSpeedFactor pAnimations[i + j].fSpeed = (pAnimations[i + j].fSpeed + 100) / 100; */ } } } else if( token == "animstepprefix:" ) { // animated doorstep submodel name prefix int i, j; parser.getTokens(1, false); parser >> token; for (i = 0, j = 0; i < ANIM_DOORSTEPS; ++i) j += iAnimType[i]; // zliczanie wcześniejszych animacji for (i = 0; i < iAnimType[ANIM_DOORSTEPS]; ++i) // liczba drzwi { // NBMX wrzesien 2003: wyszukiwanie drzwi o nazwie str* // ustalenie submodelu asAnimName = token + std::to_string(i + 1); pAnimations[i + j].smAnimated = GetSubmodelFromName( mdModel, asAnimName ); if (pAnimations[i + j].smAnimated) { //++iAnimatedDoors; pAnimations[i + j].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu switch( MoverParameters->Doors.step_type ) { // od razu zapinamy potrzebny typ animacji case 1: // shift pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdatePlatformTranslate, this, std::placeholders::_1 ); break; case 2: // rotate pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdatePlatformRotate, this, std::placeholders::_1 ); break; default: break; } pAnimations[i + j].iNumber = i + 1; // parzyste działają inaczej niż nieparzyste pAnimations[i + j].fMaxDist = 150 * 150; // drzwi to z daleka widać /* // NOTE: no longer used pAnimations[i + j].fSpeed = Random(150); // oryginalny koncept z DoorSpeedFactor pAnimations[i + j].fSpeed = (pAnimations[i + j].fSpeed + 100) / 100; */ } } } else if( token == "animmirrorprefix:" ) { // animated mirror submodel name prefix int i, j; parser.getTokens( 1, false ); parser >> token; for( i = 0, j = 0; i < ANIM_MIRRORS; ++i ) j += iAnimType[ i ]; // zliczanie wcześniejszych animacji for( i = 0; i < iAnimType[ ANIM_MIRRORS ]; ++i ) // liczba drzwi { // NBMX wrzesien 2003: wyszukiwanie drzwi o nazwie str* // ustalenie submodelu asAnimName = token + std::to_string( i + 1 ); pAnimations[ i + j ].smAnimated = GetSubmodelFromName( mdModel, asAnimName ); if( pAnimations[ i + j ].smAnimated ) { pAnimations[ i + j ].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu // od razu zapinamy potrzebny typ animacji auto const offset { pAnimations[ i + j ].smAnimated->offset() }; pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateMirror, this, std::placeholders::_1 ); // we don't expect more than 2-4 mirrors, so it should be safe to store submodel location (front/rear) in the higher bits // parzyste działają inaczej niż nieparzyste pAnimations[ i + j ].iNumber = ( ( pAnimations[ i + j ].smAnimated->offset().z > 0 ? end::front : end::rear ) << 4 ) + i; pAnimations[ i + j ].fMaxDist = 150 * 150; // drzwi to z daleka widać /* // NOTE: no longer used pAnimations[i + j].fSpeed = Random(150); // oryginalny koncept z DoorSpeedFactor pAnimations[i + j].fSpeed = (pAnimations[i + j].fSpeed + 100) / 100; */ } } } } while( ( token != "" ) && ( token != "endmodels" ) ); if( false == MoverParameters->LoadAttributes.empty() ) { // Ra: tu wczytywanie modelu ładunku jest w porządku // bieżąca ścieżka do tekstur to dynamic/... Global.asCurrentTexturePath = asBaseDir; mdLoad = LoadMMediaFile_mdload( MoverParameters->LoadType.name ); // z powrotem defaultowa sciezka do tekstur Global.asCurrentTexturePath = std::string( szTexturePath ); } } // models else if( token == "sounds:" ) { // dzwieki do { token = ""; parser.getTokens(); parser >> token; if( token == "wheel_clatter:" ){ // polozenia osi w/m srodka pojazdu double dSDist; parser.getTokens( 1, false ); parser >> dSDist; while( ( ( token = parser.getToken() ) != "" ) && ( token != "end" ) ) { // add another axle entry to the list axle_sounds axle { 0, std::atof( token.c_str() ) * -1.0, // for axle locations negative value means ahead of centre but vehicle faces +Z in 'its' space { sound_placement::external, static_cast( dSDist ) } }; axle.clatter.deserialize( parser, sound_type::single ); axle.clatter.owner( this ); axle.clatter.offset( { 0, 0, axle.offset } ); m_axlesounds.emplace_back( axle ); } // arrange the axles in case they're listed out of order std::sort( std::begin( m_axlesounds ), std::end( m_axlesounds ), []( axle_sounds const &Left, axle_sounds const &Right ) { return ( Left.offset > Right.offset ); } ); } else if( ( token == "engine:" ) && ( MoverParameters->Power > 0 ) ) { // plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest. m_powertrainsounds.engine.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); m_powertrainsounds.engine.owner( this ); auto const amplitudedivisor = static_cast( ( MoverParameters->EngineType == TEngineType::DieselEngine ? 1 : MoverParameters->EngineType == TEngineType::DieselElectric ? 1 : MoverParameters->nmax * 60 + MoverParameters->Power * 3 ) ); m_powertrainsounds.engine.m_amplitudefactor /= amplitudedivisor; } else if( token == "dieselinc:" ) { // dzwiek przy wlazeniu na obroty woodwarda m_powertrainsounds.engine_revving.deserialize( parser, sound_type::single, sound_parameters::range ); m_powertrainsounds.engine_revving.owner( this ); } else if( token == "oilpump:" ) { m_powertrainsounds.oil_pump.deserialize( parser, sound_type::single ); m_powertrainsounds.oil_pump.owner( this ); } else if( token == "fuelpump:" ) { m_powertrainsounds.fuel_pump.deserialize( parser, sound_type::single ); m_powertrainsounds.fuel_pump.owner( this ); } else if( token == "waterpump:" ) { m_powertrainsounds.water_pump.deserialize( parser, sound_type::single ); m_powertrainsounds.water_pump.owner( this ); } else if( token == "waterheater:" ) { m_powertrainsounds.water_heater.deserialize( parser, sound_type::single ); m_powertrainsounds.water_heater.owner( this ); } else if( ( token == "tractionmotor:" ) && ( MoverParameters->Power > 0 ) ) { // plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest. sound_source motortemplate { sound_placement::external }; motortemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); motortemplate.owner( this ); auto const amplitudedivisor = static_cast( MoverParameters->nmax * 60 + MoverParameters->Power * 3 ); motortemplate.m_amplitudefactor /= amplitudedivisor; if( true == m_powertrainsounds.motors.empty() ) { // fallback for cases without specified motor locations, convert sound template to a single sound source m_powertrainsounds.motors.emplace_back( motortemplate ); } else { // apply configuration to all defined motors for( auto &motor : m_powertrainsounds.motors ) { // combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual motors auto motoroffset { motortemplate.offset() }; motoroffset.z = motor.offset().z; motor = motortemplate; motor.offset( motoroffset ); } } } else if( token == "motorblower:" ) { sound_source blowertemplate { sound_placement::engine }; blowertemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); blowertemplate.owner( this ); auto const amplitudedivisor = static_cast( MoverParameters->MotorBlowers[ end::front ].speed > 0.f ? MoverParameters->MotorBlowers[ end::front ].speed * MoverParameters->nmax * 60 + MoverParameters->Power * 3 : MoverParameters->MotorBlowers[ end::front ].speed * -1 ); blowertemplate.m_amplitudefactor /= amplitudedivisor; blowertemplate.m_frequencyfactor /= amplitudedivisor; if( true == m_powertrainsounds.motors.empty() ) { // fallback for cases without specified motor locations, convert sound template to a single sound source m_powertrainsounds.motorblowers.emplace_back( blowertemplate ); } else { // apply configuration to all defined motor blowers for( auto &blower : m_powertrainsounds.motorblowers ) { // combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual blowers auto bloweroffset { blowertemplate.offset() }; bloweroffset.z = blower.offset().z; blower = blowertemplate; blower.offset( bloweroffset ); } } } else if( token == "inverter:" ) { // plik z dzwiekiem wentylatora, mnozniki i ofsety amp. i czest. m_powertrainsounds.inverter.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); m_powertrainsounds.inverter.owner( this ); } else if( token == "ventilator:" ) { // plik z dzwiekiem wentylatora, mnozniki i ofsety amp. i czest. m_powertrainsounds.rsWentylator.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); m_powertrainsounds.rsWentylator.owner( this ); m_powertrainsounds.rsWentylator.m_amplitudefactor /= MoverParameters->RVentnmax; m_powertrainsounds.rsWentylator.m_frequencyfactor /= MoverParameters->RVentnmax; } else if( token == "radiatorfan1:" ) { // primary circuit radiator fan m_powertrainsounds.radiator_fan.deserialize( parser, sound_type::single ); m_powertrainsounds.radiator_fan.owner( this ); } else if( token == "radiatorfan2" ) { // auxiliary circuit radiator fan m_powertrainsounds.radiator_fan.deserialize( parser, sound_type::single ); m_powertrainsounds.radiator_fan.owner( this ); } else if( token == "transmission:" ) { // plik z dzwiekiem, mnozniki i ofsety amp. i czest. // NOTE, fixed default parameters, legacy system leftover m_powertrainsounds.transmission.m_amplitudefactor = 0.029; m_powertrainsounds.transmission.m_amplitudeoffset = 0.1; m_powertrainsounds.transmission.m_frequencyfactor = 0.005; m_powertrainsounds.transmission.m_frequencyoffset = 1.0; m_powertrainsounds.transmission.deserialize( parser, sound_type::single, sound_parameters::range ); m_powertrainsounds.transmission.owner( this ); } else if( token == "brake:" ) { // plik z piskiem hamulca, mnozniki i ofsety amplitudy. rsPisk.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude ); rsPisk.owner( this ); if( rsPisk.m_amplitudefactor > 10.f ) { // HACK: convert old style activation point threshold to the new, regular amplitude adjustment system rsPisk.m_amplitudefactor = 1.f; rsPisk.m_amplitudeoffset = 0.f; } rsPisk.m_amplitudeoffset *= ( 105.f - Random( 10.f ) ) / 100.f; } else if( token == "brakecylinderinc:" ) { // brake cylinder pressure increase sounds m_brakecylinderpistonadvance.deserialize( parser, sound_type::single ); m_brakecylinderpistonadvance.owner( this ); } else if( token == "brakecylinderdec:" ) { // brake cylinder pressure decrease sounds m_brakecylinderpistonrecede.deserialize( parser, sound_type::single ); m_brakecylinderpistonrecede.owner( this ); } else if( token == "emergencybrake:" ) { // emergency brake sound m_emergencybrake.deserialize( parser, sound_type::single ); m_emergencybrake.owner( this ); } else if( token == "brakeacc:" ) { // plik z przyspieszaczem (upust po zlapaniu hamowania) sBrakeAcc.deserialize( parser, sound_type::single ); sBrakeAcc.owner( this ); bBrakeAcc = true; } else if( token == "unbrake:" ) { // plik z piskiem hamulca, mnozniki i ofsety amplitudy. rsUnbrake.deserialize( parser, sound_type::single, sound_parameters::range ); rsUnbrake.owner( this ); } else if( token == "derail:" ) { // dzwiek przy wykolejeniu rsDerailment.deserialize( parser, sound_type::single, sound_parameters::range ); rsDerailment.owner( this ); } else if( token == "curve:" ) { rscurve.deserialize( parser, sound_type::single, sound_parameters::range ); rscurve.owner( this ); } else if( token == "horn1:" ) { // pliki z trabieniem sHorn1.deserialize( parser, sound_type::multipart, sound_parameters::range ); sHorn1.owner( this ); } else if( token == "horn2:" ) { // pliki z trabieniem wysokoton. sHorn2.deserialize( parser, sound_type::multipart, sound_parameters::range ); sHorn2.owner( this ); // TBD, TODO: move horn selection to ai config file if( iHornWarning ) { iHornWarning = 2; // numer syreny do użycia po otrzymaniu sygnału do jazdy } } else if( token == "horn3:" ) { sHorn3.deserialize( parser, sound_type::multipart, sound_parameters::range ); sHorn3.owner( this ); } else if( token == "pantographup:" ) { // pliki dzwiekow pantografow sound_source pantographup { sound_placement::external }; pantographup.deserialize( parser, sound_type::single ); pantographup.owner( this ); for( auto &pantograph : m_pantographsounds ) { pantograph.sPantUp = pantographup; } } else if( token == "pantographdown:" ) { // pliki dzwiekow pantografow sound_source pantographdown { sound_placement::external }; pantographdown.deserialize( parser, sound_type::single ); pantographdown.owner( this ); for( auto &pantograph : m_pantographsounds ) { pantograph.sPantDown = pantographdown; } } else if( token == "compressor:" ) { // pliki ze sprezarka sCompressor.deserialize( parser, sound_type::multipart, sound_parameters::range ); sCompressor.owner( this ); } else if( token == "converter:" ) { // pliki z przetwornica sConverter.deserialize( parser, sound_type::multipart, sound_parameters::range ); sConverter.owner( this ); } else if( token == "heater:" ) { // train heating device sHeater.deserialize( parser, sound_type::single ); sHeater.owner( this ); } else if( token == "turbo:" ) { // pliki z turbogeneratorem m_powertrainsounds.engine_turbo.deserialize( parser, sound_type::multipart, sound_parameters::range ); m_powertrainsounds.engine_turbo.owner( this ); m_powertrainsounds.engine_turbo.gain( 0 ); } else if( token == "small-compressor:" ) { // pliki z przetwornica sSmallCompressor.deserialize( parser, sound_type::multipart, sound_parameters::range ); sSmallCompressor.owner( this ); } else if( token == "departuresignal:" ) { // pliki z sygnalem odjazdu sound_source soundtemplate { sound_placement::general }; soundtemplate.deserialize( parser, sound_type::multipart, sound_parameters::range ); soundtemplate.owner( this ); for( auto &departuresignalsound : m_departuresignalsounds ) { // apply configuration to all defined doors, but preserve their individual offsets auto const soundoffset { departuresignalsound.offset() }; departuresignalsound = soundtemplate; departuresignalsound.offset( soundoffset ); } } else if( token == "dooropen:" ) { sound_source soundtemplate { sound_placement::general }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { // apply configuration to all defined doors, but preserve their individual offsets auto const dooroffset { door.rsDoorOpen.offset() }; door.rsDoorOpen = soundtemplate; door.rsDoorOpen.offset( dooroffset ); } } else if( token == "doorclose:" ) { sound_source soundtemplate { sound_placement::general }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { // apply configuration to all defined doors, but preserve their individual offsets auto const dooroffset { door.rsDoorClose.offset() }; door.rsDoorClose = soundtemplate; door.rsDoorClose.offset( dooroffset ); } } else if( token == "doorlock:" ) { sound_source soundtemplate { sound_placement::general }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { // apply configuration to all defined doors, but preserve their individual offsets auto const dooroffset { door.lock.offset() }; door.lock = soundtemplate; door.lock.offset( dooroffset ); } } else if( token == "doorunlock:" ) { sound_source soundtemplate { sound_placement::general }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { // apply configuration to all defined doors, but preserve their individual offsets auto const dooroffset { door.unlock.offset() }; door.unlock = soundtemplate; door.unlock.offset( dooroffset ); } } else if( token == "doorstepopen:" ) { sound_source soundtemplate { sound_placement::general }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { // apply configuration to all defined doors, but preserve their individual offsets auto const dooroffset { door.step_open.offset() }; door.step_open = soundtemplate; door.step_open.offset( dooroffset ); } } else if( token == "doorstepclose:" ) { sound_source soundtemplate { sound_placement::general }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { // apply configuration to all defined doors, but preserve their individual offsets auto const dooroffset { door.step_close.offset() }; door.step_close = soundtemplate; door.step_close.offset( dooroffset ); } } else if( token == "unloading:" ) { m_exchangesounds.unloading.deserialize( parser, sound_type::single ); m_exchangesounds.unloading.owner( this ); } else if( token == "loading:" ) { m_exchangesounds.loading.deserialize( parser, sound_type::single ); m_exchangesounds.loading.owner( this ); } else if( token == "sand:" ) { sSand.deserialize( parser, sound_type::multipart, sound_parameters::range ); sSand.owner( this ); } else if( token == "releaser:" ) { // pliki z odluzniaczem sReleaser.deserialize( parser, sound_type::multipart, sound_parameters::range ); sReleaser.owner( this ); } else if( token == "outernoise:" ) { // szum podczas jazdy: sound_source noisetemplate{ sound_placement::external, EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; noisetemplate.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, MoverParameters->Vmax ); noisetemplate.owner( this ); noisetemplate.m_amplitudefactor /= ( 1 + MoverParameters->Vmax ); noisetemplate.m_frequencyfactor /= ( 1 + MoverParameters->Vmax ); #ifdef EU07_SOUND_BOGIESOUNDS if( true == m_bogiesounds.empty() ) { // fallback for cases without specified noise locations, convert sound template to a single sound source m_bogiesounds.emplace_back( noisetemplate ); } else { // apply configuration to all defined bogies for( auto &bogie : m_bogiesounds ) { // combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual motors auto bogieoffset{ noisetemplate.offset() }; bogieoffset.z = bogie.offset().z; bogie = noisetemplate; bogie.offset( bogieoffset ); } } // apply randomized playback start offset for each bogie, to reduce potential reverb with identical nearby sources auto bogieidx( 0 ); for( auto &bogie : m_bogiesounds ) { bogie.start( ( bogieidx % 2 ? LocalRandom( 0.0, 30.0 ) : LocalRandom( 50.0, 80.0 ) ) * 0.01 ); ++bogieidx; } #else m_outernoise = noisetemplate; // apply randomized playback start offset, to reduce potential reverb with identical nearby sources m_outernoise.start( Random( 0.0, 80.0 ) * 0.01 ); #endif } else if( token == "wheelflat:" ) { // szum podczas jazdy: m_wheelflat.deserialize( parser, sound_type::single, sound_parameters::frequency ); m_wheelflat.owner( this ); } } while( ( token != "" ) && ( token != "endsounds" ) ); } // sounds: else if( token == "locations:" ) { do { token = ""; parser.getTokens(); parser >> token; if( token == "doors:" ) { // a list of pairs: offset along vehicle's z-axis and sides on which the door is present; followed with "end" while( ( ( token = parser.getToken() ) != "" ) && ( token != "end" ) ) { // vehicle faces +Z in 'its' space, for door locations negative value means ahead of centre auto const offset { std::atof( token.c_str() ) * -1.f }; // recognized side specifications are "right", "left" and "both" auto const sides { parser.getToken() }; // NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization door_sounds door; // add entries to the list: if( ( sides == "both" ) || ( sides == "left" ) ) { // left... auto const location { glm::vec3 { MoverParameters->Dim.W * 0.5f, MoverParameters->Dim.H * 0.5f, offset } }; door.placement = side::left; door.rsDoorClose.offset( location ); door.rsDoorOpen.offset( location ); door.lock.offset( location ); door.unlock.offset( location ); door.step_close.offset( location ); door.step_open.offset( location ); m_doorsounds.emplace_back( door ); } if( ( sides == "both" ) || ( sides == "right" ) ) { // ...and right auto const location { glm::vec3 { MoverParameters->Dim.W * -0.5f, 2.f, offset } }; door.placement = side::right; door.rsDoorClose.offset( location ); door.rsDoorOpen.offset( location ); door.lock.offset( location ); door.unlock.offset( location ); door.step_close.offset( location ); door.step_open.offset( location ); m_doorsounds.emplace_back( door ); } // potential departure sound, one per door (pair) on vehicle centreline sound_source departuresignalsound { sound_placement::general, 25.f }; departuresignalsound.offset( glm::vec3{ 0.f, 3.f, offset } ); m_departuresignalsounds.emplace_back( departuresignalsound ); } } else if( token == "tractionmotors:" ) { // a list of offsets along vehicle's z-axis; followed with "end" while( ( ( token = parser.getToken() ) != "" ) && ( token != "end" ) ) { // vehicle faces +Z in 'its' space, for motor locations negative value means ahead of centre auto const offset { std::atof( token.c_str() ) * -1.f }; // NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization sound_source motor { sound_placement::external }; // generally traction motor sound_source motorblower { sound_placement::engine }; // associated motor blowers // add entry to the list auto const location { glm::vec3 { 0.f, 0.f, offset } }; motor.offset( location ); m_powertrainsounds.motors.emplace_back( motor ); motorblower.offset( location ); m_powertrainsounds.motorblowers.emplace_back( motorblower ); } } else if( token == "bogies:" ) { // a list of offsets along vehicle's z-axis; followed with "end" while( ( ( token = parser.getToken() ) != "" ) && ( token != "end" ) ) { // vehicle faces +Z in 'its' space, for bogie locations negative value means ahead of centre auto const offset { std::atof( token.c_str() ) * -1.f }; // NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization sound_source bogienoise { sound_placement::external, EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; // add entry to the list auto const location { glm::vec3 { 0.f, 0.f, offset } }; bogienoise.offset( location ); #ifdef EU07_SOUND_BOGIESOUNDS m_bogiesounds.emplace_back( bogienoise ); #endif } } } while( ( token != "" ) && ( token != "endlocations" ) ); } // locations: else if( token == "internaldata:" ) { // dalej nie czytaj do { // zbieranie informacji o kabinach token = ""; parser.getTokens(); parser >> token; if( token == "cab0model:" ) { parser.getTokens(); parser >> token; if( token != "none" ) { iCabs |= 0x2; } } else if( token == "cab1model:" ) { parser.getTokens(); parser >> token; if( token != "none" ) { iCabs |= 0x1; } } else if( token == "cab2model:" ) { parser.getTokens(); parser >> token; if( token != "none" ) { iCabs |= 0x4; } } // engine sounds else if( token == "ignition:" ) { // odpalanie silnika m_powertrainsounds.engine_ignition.deserialize( parser, sound_type::single ); m_powertrainsounds.engine_ignition.owner( this ); } else if (token == "shutdown:") { m_powertrainsounds.engine_shutdown.deserialize(parser, sound_type::single); m_powertrainsounds.engine_shutdown.owner(this); } else if( token == "engageslippery:" ) { // tarcie tarcz sprzegla: m_powertrainsounds.rsEngageSlippery.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency ); m_powertrainsounds.rsEngageSlippery.owner( this ); m_powertrainsounds.rsEngageSlippery.m_frequencyfactor /= ( 1 + MoverParameters->nmax ); } else if( token == "linebreakerclose:" ) { m_powertrainsounds.linebreaker_close.deserialize( parser, sound_type::single ); m_powertrainsounds.linebreaker_close.owner( this ); } else if( token == "linebreakeropen:" ) { m_powertrainsounds.linebreaker_open.deserialize( parser, sound_type::single ); m_powertrainsounds.linebreaker_open.owner( this ); } else if( token == "relay:" ) { // styczniki itp: m_powertrainsounds.motor_relay.deserialize( parser, sound_type::single ); m_powertrainsounds.motor_relay.owner( this ); } else if( token == "shuntfield:" ) { // styczniki itp: m_powertrainsounds.motor_shuntfield.deserialize( parser, sound_type::single ); m_powertrainsounds.motor_shuntfield.owner( this ); } else if( token == "wejscie_na_bezoporow:" ) { // hunter-111211: wydzielenie wejscia na bezoporowa i na drugi uklad do pliku m_powertrainsounds.dsbWejscie_na_bezoporow.deserialize( parser, sound_type::single ); m_powertrainsounds.dsbWejscie_na_bezoporow.owner( this ); } else if( token == "wejscie_na_drugi_uklad:" ) { m_powertrainsounds.motor_parallel.deserialize( parser, sound_type::single ); m_powertrainsounds.motor_parallel.owner( this ); } else if( token == "pneumaticrelay:" ) { // wylaczniki pneumatyczne: dsbPneumaticRelay.deserialize( parser, sound_type::single ); dsbPneumaticRelay.owner( this ); } // braking sounds else if( token == "brakesound:" ) { // hamowanie zwykle: rsBrake.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency ); rsBrake.owner( this ); // NOTE: can't pre-calculate amplitude normalization based on max brake force, as this varies depending on vehicle speed rsBrake.m_frequencyfactor /= ( 1 + MoverParameters->Vmax ); } else if( token == "slipperysound:" ) { // sanie: rsSlippery.deserialize( parser, sound_type::single, sound_parameters::amplitude ); rsSlippery.owner( this ); rsSlippery.m_amplitudefactor /= ( 1 + MoverParameters->Vmax ); } // coupler sounds else if( token == "couplerattach:" ) { // laczenie: sound_source couplerattach { sound_placement::external }; couplerattach.deserialize( parser, sound_type::single ); couplerattach.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbCouplerAttach = couplerattach; } } else if( token == "couplerdetach:" ) { // rozlaczanie: sound_source couplerdetach { sound_placement::external }; couplerdetach.deserialize( parser, sound_type::single ); couplerdetach.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbCouplerDetach = couplerdetach; } } else if( token == "couplerstretch:" ) { // coupler stretching sound_source couplerstretch { sound_placement::external }; couplerstretch.deserialize( parser, sound_type::single ); couplerstretch.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbCouplerStretch = couplerstretch; } } else if( token == "couplerstretch_loud:" ) { // coupler stretching sound_source couplerstretch { sound_placement::external }; couplerstretch.deserialize( parser, sound_type::single ); couplerstretch.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbCouplerStretch_loud = couplerstretch; } } else if( token == "bufferclamp:" ) { // buffers hitting one another sound_source bufferclash { sound_placement::external }; bufferclash.deserialize( parser, sound_type::single ); bufferclash.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbBufferClamp = bufferclash; } } else if( token == "bufferclamp_loud:" ) { // buffers hitting one another sound_source bufferclash { sound_placement::external }; bufferclash.deserialize( parser, sound_type::single ); bufferclash.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbBufferClamp_loud = bufferclash; } } else if( token == "coupleradapterattach:" ) { // laczenie: sound_source adapterattach { sound_placement::external }; adapterattach.deserialize( parser, sound_type::single ); adapterattach.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbAdapterAttach = adapterattach; } } else if( token == "coupleradapterremove:" ) { // rozlaczanie: sound_source adapterremove { sound_placement::external }; adapterremove.deserialize( parser, sound_type::single ); adapterremove.owner( this ); for( auto &couplersounds : m_couplersounds ) { couplersounds.dsbAdapterRemove = adapterremove; } } else if( token == "startjolt:" ) { // movement start jolt m_startjolt.deserialize( parser, sound_type::single ); m_startjolt.owner( this ); } else if (token == "mechspring:") { // parametry bujania kamery: double ks, kd; parser.getTokens(2, false); parser >> ks >> kd; ShakeSpring.Init(ks, kd); parser.getTokens(6, false); parser >> BaseShake.jolt_scale.x >> BaseShake.jolt_scale.y >> BaseShake.jolt_scale.z >> BaseShake.jolt_limit >> BaseShake.angle_scale.x >> BaseShake.angle_scale.z; } else if( token == "enginespring:" ) { parser.getTokens( 5, false ); parser >> EngineShake.scale >> EngineShake.fadein_offset >> EngineShake.fadein_factor >> EngineShake.fadeout_offset >> EngineShake.fadeout_factor; // offsets values are provided as rpm for convenience EngineShake.fadein_offset /= 60.f; EngineShake.fadeout_offset /= 60.f; } else if( token == "huntingspring:" ) { parser.getTokens( 4, false ); parser >> HuntingShake.scale >> HuntingShake.frequency >> HuntingShake.fadein_begin >> HuntingShake.fadein_end; } else if( token == "jointcabs:" ) { parser.getTokens(); parser >> JointCabs; } else if( token == "cablight:" ) { parser.getTokens( 3, false ); // low power light, ignore parser.getTokens( 3, false ); // base light parser >> InteriorLight.r >> InteriorLight.g >> InteriorLight.b; InteriorLight = glm::clamp( InteriorLight / 255.f, glm::vec3( 0.f ), glm::vec3( 1.f ) ); parser.getTokens( 3, false ); // dimmed light, ignore } else if( token == "pydestinationsign:" ) { DestinationSign.deserialize( parser ); // supply vehicle folder as script path if none is provided if( ( false == DestinationSign.script.empty() ) && ( substr_path( DestinationSign.script ).empty() ) ) { DestinationSign.script = asBaseDir + DestinationSign.script; } } else if( token == "destinationsignbackground:" ) { parser.getTokens(); parser >> DestinationSign.background; } } while( token != "" ); } // internaldata: } while( token != "" ); if( !iAnimations ) { // if the animations weren't defined the model is likely to be non-functional. warrants a warning. ErrorLog( "Animations tag is missing from the .mmd file \"" + asFileName + "\"" ); } if( ReplacableSkin != "none" ) { // potentially set blank destination texture DestinationSign.destination_off = DestinationFind( ( DestinationSign.background.empty() ? "nowhere" : DestinationSign.background ) ); } // assign default samples to sound emitters which weren't included in the config file if( TestFlag( MoverParameters->CategoryFlag, 1 ) ) { // rail vehicles: // engine if( MoverParameters->Power > 0 ) { if( true == m_powertrainsounds.dsbWejscie_na_bezoporow.empty() ) { // hunter-111211: domyslne, gdy brak m_powertrainsounds.dsbWejscie_na_bezoporow.deserialize( "wejscie_na_bezoporow", sound_type::single ); m_powertrainsounds.dsbWejscie_na_bezoporow.owner( this ); } if( true == m_powertrainsounds.motor_parallel.empty() ) { m_powertrainsounds.motor_parallel.deserialize( "wescie_na_drugi_uklad", sound_type::single ); m_powertrainsounds.motor_parallel.owner( this ); } } // braking sounds if( true == rsUnbrake.empty() ) { rsUnbrake.deserialize( "[1007]estluz", sound_type::single ); rsUnbrake.owner( this ); } // couplers for( auto &couplersounds : m_couplersounds ) { if( true == couplersounds.dsbCouplerAttach.empty() ) { couplersounds.dsbCouplerAttach.deserialize( "couplerattach_default", sound_type::single ); couplersounds.dsbCouplerAttach.owner( this ); } if( true == couplersounds.dsbCouplerDetach.empty() ) { couplersounds.dsbCouplerDetach.deserialize( "couplerdetach_default", sound_type::single ); couplersounds.dsbCouplerDetach.owner( this ); } if( true == couplersounds.dsbCouplerStretch.empty() ) { couplersounds.dsbCouplerStretch.deserialize( "couplerstretch_default", sound_type::single ); couplersounds.dsbCouplerStretch.owner( this ); } if( true == couplersounds.dsbBufferClamp.empty() ) { couplersounds.dsbBufferClamp.deserialize( "bufferclamp_default", sound_type::single ); couplersounds.dsbBufferClamp.owner( this ); } } // other sounds if( true == m_wheelflat.empty() ) { m_wheelflat.deserialize( "lomotpodkucia 0.23 0.0", sound_type::single, sound_parameters::frequency ); m_wheelflat.owner( this ); } if( true == rscurve.empty() ) { // hunter-111211: domyslne, gdy brak rscurve.deserialize( "curve", sound_type::single ); rscurve.owner( this ); } } if (mdModel) mdModel->Init(); // obrócenie modelu oraz optymalizacja, również zapisanie binarnego if (mdLoad) mdLoad->Init(); if (mdLowPolyInt) mdLowPolyInt->Init(); for( auto *attachment : mdAttachments ) { attachment->Init(); } Global.asCurrentTexturePath = szTexturePath; // kiedyś uproszczone wnętrze mieszało tekstury nieba Global.asCurrentDynamicPath = ""; // position sound emitters which weren't defined in the config file // engine sounds, centre of the vehicle auto const enginelocation { glm::vec3 {0.f, MoverParameters->Dim.H * 0.5f, 0.f } }; m_powertrainsounds.position( enginelocation ); // other engine compartment sounds auto const nullvector { glm::vec3() }; std::vector enginesounds = { &sConverter, &sCompressor, &sSmallCompressor, &sHeater }; for( auto sound : enginesounds ) { if( sound->offset() == nullvector ) { sound->offset( enginelocation ); } } // pantographs if( pants != nullptr ) { std::size_t pantographindex { 0 }; for( auto &pantographsounds : m_pantographsounds ) { auto const pantographoffset { glm::vec3( 0.f, pants[ pantographindex ].fParamPants->vPos.y, -pants[ pantographindex ].fParamPants->vPos.x ) }; pantographsounds.sPantUp.offset( pantographoffset ); pantographsounds.sPantDown.offset( pantographoffset ); ++pantographindex; } } // doors std::size_t dooranimationfirstindex { 0 }; for( std::size_t i = 0; i < ANIM_DOORS; ++i ) { // zliczanie wcześniejszych animacji dooranimationfirstindex += iAnimType[ i ]; } // couplers auto const frontcoupleroffset { glm::vec3{ 0.f, 1.f, MoverParameters->Dim.L * 0.5f } }; m_couplersounds[ end::front ].dsbCouplerAttach.offset( frontcoupleroffset ); m_couplersounds[ end::front ].dsbCouplerDetach.offset( frontcoupleroffset ); m_couplersounds[ end::front ].dsbCouplerStretch.offset( frontcoupleroffset ); m_couplersounds[ end::front ].dsbCouplerStretch_loud.offset( frontcoupleroffset ); m_couplersounds[ end::front ].dsbBufferClamp.offset( frontcoupleroffset ); m_couplersounds[ end::front ].dsbBufferClamp_loud.offset( frontcoupleroffset ); m_couplersounds[ end::front ].dsbAdapterAttach.offset( frontcoupleroffset ); m_couplersounds[ end::front ].dsbAdapterRemove.offset( frontcoupleroffset ); auto const rearcoupleroffset { glm::vec3{ 0.f, 1.f, MoverParameters->Dim.L * -0.5f } }; m_couplersounds[ end::rear ].dsbCouplerAttach.offset( rearcoupleroffset ); m_couplersounds[ end::rear ].dsbCouplerDetach.offset( rearcoupleroffset ); m_couplersounds[ end::rear ].dsbCouplerStretch.offset( rearcoupleroffset ); m_couplersounds[ end::rear ].dsbCouplerStretch_loud.offset( rearcoupleroffset ); m_couplersounds[ end::rear ].dsbBufferClamp.offset( rearcoupleroffset ); m_couplersounds[ end::rear ].dsbBufferClamp_loud.offset( rearcoupleroffset ); m_couplersounds[ end::rear ].dsbAdapterAttach.offset( rearcoupleroffset ); m_couplersounds[ end::rear ].dsbAdapterRemove.offset( rearcoupleroffset ); } TModel3d * TDynamicObject::LoadMMediaFile_mdload( std::string const &Name ) const { if( Name.empty() ) { return nullptr; } TModel3d *loadmodel { nullptr }; // check if we don't have model override for this load type { auto const lookup { LoadModelOverrides.find( Name ) }; if( lookup != LoadModelOverrides.end() ) { loadmodel = TModelsManager::GetModel( asBaseDir + lookup->second, true ); // if the override was succesfully loaded call it a day if( loadmodel != nullptr ) { return loadmodel; } } } // regular routine if there's no override or it couldn't be loaded // try first specialized version of the load model, vehiclename_loadname { auto const specializedloadfilename { asBaseDir + MoverParameters->TypeName + "_" + Name }; loadmodel = TModelsManager::GetModel( specializedloadfilename, true, false ); if( loadmodel != nullptr ) { return loadmodel; } } // try generic version of the load model next, loadname { auto const genericloadfilename { asBaseDir + Name }; loadmodel = TModelsManager::GetModel( genericloadfilename, true, false ); if( loadmodel != nullptr ) { return loadmodel; } } // if we're still here, give up return loadmodel; } //--------------------------------------------------------------------------- void TDynamicObject::RadioStop() { // zatrzymanie pojazdu if( Mechanik ) { // o ile ktoś go prowadzi if( ( MoverParameters->SecuritySystem.RadioStop ) && ( MoverParameters->Radio ) ) { // jeśli pojazd ma RadioStop i jest on aktywny // HACK cast until math types unification Mechanik->PutCommand( "Emergency_brake", 1.0, 1.0, &static_cast(vPosition), stopRadio ); // add onscreen notification for human driver // TODO: do it selectively for the 'local' driver once the multiplayer is in if( false == Mechanik->AIControllFlag ) { ui::Transcripts.AddLine( "!! RADIO-STOP !!", 0.0, 10.0, false ); } } } }; //--------------------------------------------------------------------------- void TDynamicObject::Damage(char flag) { if (flag & 1) //różnicówka nie robi nic { MoverParameters->MainSwitch(false); MoverParameters->FuseOff(); } else { } if (flag & 2) //usterka sterowania { MoverParameters->StLinFlag = false; if (MoverParameters->InitialCtrlDelay<100000000) MoverParameters->InitialCtrlDelay += 100000001; } else { if (MoverParameters->InitialCtrlDelay>100000000) MoverParameters->InitialCtrlDelay -= 100000001; } if (flag & 4) //blokada przetwornicy { if( MoverParameters->ConverterStart != start_t::disabled ) { MoverParameters->ConvOvldFlag = true; } } else { } if (flag & 8) //blokada sprezarki { if (MoverParameters->MinCompressor>0) MoverParameters->MinCompressor -= 100000001; if (MoverParameters->MaxCompressor>0) MoverParameters->MaxCompressor -= 100000001; } else { if (MoverParameters->MinCompressor<0) MoverParameters->MinCompressor += 100000001; if (MoverParameters->MaxCompressor<0) MoverParameters->MaxCompressor += 100000001; } if (flag & 16) //blokada wału { if (MoverParameters->CtrlDelay<100000000) MoverParameters->CtrlDelay += 100000001; if (MoverParameters->CtrlDownDelay<100000000) MoverParameters->CtrlDownDelay += 100000001; } else { if (MoverParameters->CtrlDelay>100000000) MoverParameters->CtrlDelay -= 100000001; if (MoverParameters->CtrlDownDelay>100000000) MoverParameters->CtrlDownDelay -= 100000001; } if (flag & 32) //hamowanie nagŁe { } else { } MoverParameters->EngDmgFlag = flag; }; void TDynamicObject::SetLights() { auto const isfrontcaboccupied { MoverParameters->CabOccupied * DirectionGet() >= 0 }; int const front { ( isfrontcaboccupied ? end::front : end::rear ) }; int const rear { 1 - front }; auto const lightpos { MoverParameters->LightsPos - 1 }; auto const frontlights { MoverParameters->Lights[ front ][ lightpos ] }; auto const rearlights { MoverParameters->Lights[ rear ][ lightpos ] }; auto *vehicle { GetFirstDynamic( MoverParameters->CabOccupied >= 0 ? end::front : end::rear, coupling::control ) }; while( vehicle != nullptr ) { // set lights on given side if there's no coupling with another vehicle, turn them off otherwise auto const *frontvehicle { ( isfrontcaboccupied ? vehicle->PrevC( coupling::coupler ) : vehicle->NextC( coupling::coupler ) ) }; auto const *rearvehicle { ( isfrontcaboccupied ? vehicle->NextC( coupling::coupler ) : vehicle->PrevC( coupling::coupler ) ) }; vehicle->RaLightsSet( ( frontvehicle == nullptr ? frontlights : 0 ), ( rearvehicle == nullptr ? rearlights : 0 ) ); vehicle = ( isfrontcaboccupied ? vehicle->NextC( coupling::control ) : vehicle->PrevC( coupling::control ) ); } }; void TDynamicObject::RaLightsSet(int head, int rear) { // zapalenie świateł z przodu i z // tyłu, zależne od kierunku // pojazdu if (!MoverParameters) return; // może tego nie być na początku if (rear == ( light::redmarker_left | light::redmarker_right | light::rearendsignals ) ) { // jeśli koniec pociągu, to trzeba ustalić, czy // jest tam czynna lokomotywa // EN57 może nie mieć końcówek od środka członu if (MoverParameters->Power > 1.0) // jeśli ma moc napędową if (!MoverParameters->DirActive) // jeśli nie ma ustawionego kierunku { // jeśli ma zarówno światła jak i końcówki, ustalić, czy jest w stanie // aktywnym // np. lokomotywa na zimno będzie mieć końcówki a nie światła rear = light::rearendsignals; // tablice blaszane // trzeba to uzależnić od "załączenia baterii" w pojeździe } if( rear == ( light::redmarker_left | light::redmarker_right | light::rearendsignals ) ) // jeśli nadal obydwie możliwości if( iInventory[ ( iDirection ? end::rear : end::front ) ] & ( light::redmarker_left | light::redmarker_right ) ) { // czy ma jakieś światła czerowone od danej strony rear = ( light::redmarker_left | light::redmarker_right ); // dwa światła czerwone } else { rear = light::rearendsignals; // tablice blaszane } } if (iDirection) // w zależności od kierunku pojazdu w składzie { // jesli pojazd stoi sprzęgiem 0 w stronę czoła if (head >= 0) MoverParameters->iLights[0] = head; if (rear >= 0) MoverParameters->iLights[1] = rear; } else { // jak jest odwrócony w składzie (-1), to zapalamy odwrotnie if (head >= 0) MoverParameters->iLights[1] = head; if (rear >= 0) MoverParameters->iLights[0] = rear; } }; int TDynamicObject::DirectionSet(int d) { // ustawienie kierunku w składzie (wykonuje AI) auto const lastdirection { iDirection }; iDirection = ( d > 0 ) ? 1 : 0; // d:1=zgodny,-1=przeciwny; iDirection:1=zgodny,0=przeciwny; if( iDirection != lastdirection ) { // direction was flipped, switch recorded servicable platform sides for potentially ongoing load exchange auto const left { ( lastdirection > 0 ) ? 1 : 2 }; auto const right { 3 - left }; m_exchange.platforms = ( ( m_exchange.platforms & left ) != 0 ? right : 0 ) + ( ( m_exchange.platforms & right ) != 0 ? left : 0 ); } if (MyTrack) { // podczas wczytywania wstawiane jest AI, ale może jeszcze nie być toru // AI ustawi kierunek ponownie po uruchomieniu silnika update_neighbours(); } // informacja o położeniu następnego return 1 - ( ( iDirection > 0 ) ? NextConnectedNo() : PrevConnectedNo() ); }; // wskaźnik na poprzedni, nawet wirtualny TDynamicObject * TDynamicObject::PrevAny() const { return MoverParameters->Neighbours[ iDirection ^ 1 ].vehicle; } TDynamicObject * TDynamicObject::Prev() const { return ( MoverParameters->Couplers[ iDirection ^ 1 ].CouplingFlag != coupling::faux ? MoverParameters->Neighbours[ iDirection ^ 1 ].vehicle : nullptr );// gdy sprzęg wirtualny, to jakby nic nie było } TDynamicObject * TDynamicObject::Next() const { return ( MoverParameters->Couplers[ iDirection ].CouplingFlag != coupling::faux ? MoverParameters->Neighbours[ iDirection ].vehicle : nullptr );// gdy sprzęg wirtualny, to jakby nic nie było } TDynamicObject * TDynamicObject::PrevC(int C) const { return ( ( MoverParameters->Couplers[ iDirection ^ 1 ].CouplingFlag & C ) == C ? MoverParameters->Neighbours[ iDirection ^ 1 ].vehicle : nullptr ); // hide neighbour lacking specified connection type } TDynamicObject * TDynamicObject::NextC(int C) const { return ( ( MoverParameters->Couplers[ iDirection ].CouplingFlag & C ) == C ? MoverParameters->Neighbours[ iDirection ].vehicle : nullptr ); // hide neighbour lacking specified connection type } // checks whether there's unbroken connection of specified type to specified vehicle bool TDynamicObject::is_connected( TDynamicObject const *Vehicle, coupling const Coupling ) const { auto *vehicle { this }; if( vehicle == Vehicle ) { // edge case, vehicle is always "connected" with itself return true; } // check ahead, it's more likely the "owner" using this method is located there while( ( vehicle = vehicle->PrevC( Coupling ) ) != nullptr ) { if( vehicle == Vehicle ) { return true; } if( vehicle == this ) { // edge case, looping consist return false; } } // start anew in the other direction vehicle = this; while( ( vehicle = vehicle->NextC( Coupling ) ) != nullptr ) { if( vehicle == Vehicle ) { return true; } } // no lack in either direction, give up return false; } // ustalenie następnego (1) albo poprzedniego (0) w składzie bez względu na prawidłowość iDirection TDynamicObject * TDynamicObject::Neighbour(int &dir) { auto *neighbour { ( MoverParameters->Couplers[ dir ].CouplingFlag != coupling::faux ? MoverParameters->Neighbours[ dir ].vehicle : nullptr ) }; // nowa wartość dir = 1 - MoverParameters->Neighbours[ dir ].vehicle_end; return neighbour; }; // updates potential collision sources void TDynamicObject::update_neighbours() { for( int end = end::front; end <= end::rear; ++end ) { auto &neighbour { MoverParameters->Neighbours[ end ] }; auto const &coupler { MoverParameters->Couplers[ end ] }; if( ( coupler.Connected != nullptr ) && ( neighbour.vehicle != nullptr ) ) { // physical connection with another vehicle locks down collision source on this end // neighbour.vehicle = coupler.Connected; // neighbour.vehicle_end = coupler.ConnectedNr; neighbour.distance = TMoverParameters::CouplerDist( MoverParameters, coupler.Connected ); // take into account potential adapters attached to the couplers auto const &othercoupler { neighbour.vehicle->MoverParameters->Couplers[ neighbour.vehicle_end ] }; neighbour.distance -= coupler.adapter_length; neighbour.distance -= othercoupler.adapter_length; } else { // if there's no connected vehicle check for potential collision sources in the vicinity // NOTE: we perform a new scan on each update to ensure we always locate the nearest potential source neighbour = neighbour_data(); // 10m ~= 140 km/h at 4 fps + safety margin, potential distance between outer axles of involved vehicles auto const scanrange { std::max( 10.0, std::abs( MoverParameters->V ) ) + 40.0 }; auto const lookup { find_vehicle( end, scanrange ) }; if( false == std::get( lookup ) ) { continue; } neighbour.vehicle = std::get( lookup ); neighbour.vehicle_end = std::get( lookup ); neighbour.distance = std::get( lookup ); if( neighbour.distance < ( neighbour.vehicle->MoverParameters->CategoryFlag == 2 ? 50 : 100 ) ) { // at short distances (re)calculate range between couplers directly neighbour.distance = TMoverParameters::CouplerDist( MoverParameters, neighbour.vehicle->MoverParameters ); // take into account potential adapters attached to the couplers auto const &othercoupler { neighbour.vehicle->MoverParameters->Couplers[ neighbour.vehicle_end ] }; neighbour.distance -= coupler.adapter_length; neighbour.distance -= othercoupler.adapter_length; } } } } // locates potential collision source within specified range, scanning track in specified direction. returns: true if neighbour was located, false otherwise // NOTE: reuses legacy code. TBD, TODO: review, refactor? std::tuple TDynamicObject::find_vehicle( int const Direction, double const Distance ) const { auto direction { ( Direction == end::front ? 1 : -1 ) }; auto const initialdirection { direction }; // zapamiętanie kierunku poszukiwań na torze początkowym, względem sprzęgów auto const *track { RaTrackGet() }; if( RaDirectionGet() < 0 ) { // czy oś jest ustawiona w stronę Point1? direction = -direction; } // (teraz względem toru) auto const mycoupler { ( initialdirection < 0 ? end::rear : end::front ) }; // numer sprzęgu do podłączenia w obiekcie szukajacym auto foundcoupler { -1 }; // numer sprzęgu w znalezionym obiekcie (znaleziony wypełni) auto distance { 0.0 }; // przeskanowana odleglość; odległość do zawalidrogi auto *foundobject { ABuFindObject( foundcoupler, distance, track, direction, mycoupler ) }; // zaczynamy szukać na tym samym torze if( foundobject == nullptr ) { // jeśli nie ma na tym samym, szukamy po okolicy szukanie najblizszego toru z jakims obiektem // praktycznie przeklejone z TraceRoute()... if (direction >= 0) // uwzględniamy kawalek przeanalizowanego wcześniej toru distance = track->Length() - RaTranslationGet(); // odległość osi od Point2 toru else distance = RaTranslationGet(); // odległość osi od Point1 toru while (distance < Distance) { if (direction > 0) { // w kierunku Point2 toru if( track ? track->iNextDirection : false ) { // jeśli następny tor jest podpięty od Point2 direction = -direction; // to zmieniamy kierunek szukania na tym torze } track = track->CurrentNext(); // potem dopiero zmieniamy wskaźnik } else { // w kierunku Point1 if( track ? !track->iPrevDirection : true ) { // jeśli poprzedni tor nie jest podpięty od Point2 direction = -direction; // to zmieniamy kierunek szukania na tym torze } track = track->CurrentPrev(); // potem dopiero zmieniamy wskaźnik } if (track) { // jesli jest kolejny odcinek toru foundobject = ABuFindObject(foundcoupler, distance, track, direction, mycoupler); // przejrzenie pojazdów tego toru if (foundobject) { break; } } else { // jeśli toru nie ma, to wychodzimy distance = Distance + 1.0; // koniec przeglądania torów break; } } } // Koniec szukania najbliższego toru z jakimś obiektem. if( foundobject == nullptr ) { return {}; } /* auto const *vehicle { MoverParameters }; auto const *foundvehicle { foundobject->MoverParameters }; if( ( false == TestFlag( track->iCategoryFlag, 1 ) ) && ( distance > 50.0 ) ) { // Ra: jeśli dwa samochody się mijają na odcinku przed zawrotką, to odległość między nimi nie może być liczona w linii prostej! // NOTE: the distance is approximated, and additionally less accurate for cars heading in opposite direction distance -= ( 0.5 * ( vehicle->Dim.L + foundvehicle->Dim.L ) ); } else if( distance < 100.0 ) { // at short distances start to calculate range between couplers directly // odległość do najbliższego pojazdu w linii prostej distance = TMoverParameters::CouplerDist( vehicle, foundvehicle ); } */ return { foundobject, foundcoupler, distance, true }; } TDynamicObject * TDynamicObject::FindPowered() { // taka proteza: // chcę podłączyć kabinę EN57 bezpośrednio z silnikowym, aby nie robić tego przez ukrotnienie // drugi silnikowy i tak musi być ukrotniony, podobnie jak kolejna jednostka // lepiej by było przesyłać komendy sterowania, co jednak wymaga przebudowy transmisji komend (LD) // problem się robi ze światłami, które będą zapalane w silnikowym, ale muszą świecić się w rozrządczych // dla EZT światłą czołowe będą "zapalane w silnikowym", ale widziane z rozrządczych // również wczytywanie MMD powinno dotyczyć aktualnego członu // problematyczna może być kwestia wybranej kabiny (w silnikowym...) // jeśli silnikowy będzie zapięty odwrotnie (tzn. -1), to i tak powinno jeździć dobrze // również hamowanie wykonuje się zaworem w członie, a nie w silnikowym... auto const coupling { ( ( MoverParameters->TrainType == dt_EZT ) || ( MoverParameters->TrainType == dt_DMU ) ) ? coupling::permanent : coupling::control }; auto *lookup { find_vehicle( coupling, []( TDynamicObject * vehicle ) { return ( vehicle->MoverParameters->Power > 1.0 ); } ) }; return( lookup != nullptr ? lookup : this ); // always return valid vehicle for backward compatibility } TDynamicObject * TDynamicObject::FindPantographCarrier() { // try first within a single unit, broaden to all vehicles under our control if first attempt fails std::array const couplings = { coupling::permanent, coupling::control }; for( auto const coupling : couplings ) { auto *result = find_vehicle( coupling, []( TDynamicObject * vehicle ) { return ( ( vehicle->MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) && ( vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) ); } ); if( result != nullptr ) { return result; } } // if we're still here, admit failure return nullptr; } //--------------------------------------------------------------------------- void TDynamicObject::ParamSet(int what, int into) { // ustawienie lokalnego parametru (what) na stan (into) switch (what & 0xFF00) { case 0x0100: // to np. są drzwi, bity 0..7 określają numer 1..254 albo maskę // dla 8 różnych if (what & 1) // na razie mamy lewe oraz prawe, czyli używamy maskę 1=lewe, // 2=prawe, 3=wszystkie if( MoverParameters->Doors.instances[side::left].is_open ) { // są otwarte if (!into) // jeśli zamykanie { // dźwięk zamykania } } else { // są zamknięte if (into) // jeśli otwieranie { // dźwięk otwierania } } if (what & 2) // prawe działają niezależnie od lewych if( MoverParameters->Doors.instances[side::right].is_open ) { // są otwarte if (!into) // jeśli zamykanie { // dźwięk zamykania } } else { // są zamknięte if (into) // jeśli otwieranie { // dźwięk otwierania } } break; } }; int TDynamicObject::RouteWish(TTrack *tr) { // zapytanie do AI, po którym // segmencie (-6..6) jechać na // skrzyżowaniu (tr) return Mechanik ? Mechanik->CrossRoute(tr) : 0; // wg AI albo prosto }; void TDynamicObject::DestinationSet(std::string to, std::string numer) { // ustawienie stacji docelowej oraz wymiennej tekstury 4, jeśli istnieje plik // w zasadzie, to każdy wagon mógłby mieć inną stację docelową // zwłaszcza w towarowych, pod kątem zautomatyzowania maewrów albo pracy górki // ale to jeszcze potrwa, zanim będzie możliwe, na razie można wpisać stację z // rozkładu asDestination = to; if( std::abs( m_materialdata.multi_textures ) >= 4 ) { return; } // jak są 4 tekstury wymienne, to nie zmieniać rozkładem if( DestinationSign.sign == nullptr ) { return; } // no sign submodel, no problem // now see if we can find any version of the destination texture std::vector const destinations = { numer, // try dedicated timetable sign first... to }; // ...then generic destination sign for( auto const &destination : destinations ) { DestinationSign.destination = DestinationFind( destination ); if( DestinationSign.destination != null_handle ) { // got what we wanted, we're done here return; } } // if we didn't get static texture we might be able to make one if( DestinationSign.script.empty() ) { return; } // no script so no way to make the texture if( numer == "none" ) { return; } // blank or incomplete/malformed timetable, don't bother std::string signrequest { "make:" + DestinationSign.script + "?" // timetable include + "$timetable=" + ( ctOwner == nullptr ? MoverParameters->Name : // leading vehicle, can point to it directly ctOwner->Vehicle()->MoverParameters->Name ) // owned vehicle, safer to point to owner as carriages can have identical names // basic instancing string // NOTE: underscore doesn't have any magic meaning for the time being, it's just less likely to conflict with regular dictionary keys + "&_id1=" + ( ctOwner != nullptr ? ctOwner->TrainName() : Mechanik != nullptr ? Mechanik->TrainName() : "none" ) }; // shouldn't get here but, eh // TBD, TODO: replace instancing with support for variables in extra parameters string? if( false == DestinationSign.instancing.empty() ) { signrequest += "&_id2=" + ( DestinationSign.instancing == "name" ? MoverParameters->Name : DestinationSign.instancing == "type" ? MoverParameters->TypeName : "none" ); } // optionl extra parameters if( false == DestinationSign.parameters.empty() ) { signrequest += "&" + DestinationSign.parameters; } DestinationSign.destination = GfxRenderer->Fetch_Material( signrequest ); } material_handle TDynamicObject::DestinationFind( std::string Destination ) { if( Destination.empty() ) { return null_handle; } Destination = Bezogonkow( Destination ); // do szukania pliku obcinamy ogonki // destination textures are kept in the vehicle's directory so we point the current texture path there auto const currenttexturepath { Global.asCurrentTexturePath }; Global.asCurrentTexturePath = asBaseDir; // now see if we can find any version of the texture std::vector const destinations { Destination + '@' + MoverParameters->TypeName, Destination }; auto destinationhandle { null_handle }; for( auto const &destination : destinations ) { auto material = TextureTest( ToLower( destination ) ); if( false == material.empty() ) { destinationhandle = GfxRenderer->Fetch_Material( material ); break; } } // whether we got anything, restore previous texture path Global.asCurrentTexturePath = currenttexturepath; return destinationhandle; } void TDynamicObject::OverheadTrack(float o) { // ewentualne wymuszanie jazdy // bezprądowej z powodu informacji // w torze if (ctOwner) // jeśli ma obiekt nadzorujący { // trzeba zaktualizować mapę flag bitowych jazdy bezprądowej if (o < 0.0) { // normalna jazda po tym torze ctOwner->iOverheadZero &= ~iOverheadMask; // zerowanie bitu - może pobierać prąd ctOwner->iOverheadDown &= ~iOverheadMask; // zerowanie bitu - może podnieść pantograf } else if (o > 0.0) { // opuszczenie pantografów ctOwner->iOverheadZero |= iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu ctOwner->iOverheadDown |= iOverheadMask; // ustawienie bitu - ma opuścić pantograf } else { // jazda bezprądowa z podniesionym pantografem ctOwner->iOverheadZero |= iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu ctOwner->iOverheadDown &= ~iOverheadMask; // zerowanie bitu - może podnieść pantograf } } }; glm::dvec3 TDynamicObject::get_future_movement() const { return m_future_movement; } void TDynamicObject::move_set(double distance) { TDynamicObject *d = this; while( d ) { d->Move( distance * d->DirectionGet() ); d = d->Next(); // pozostałe też } d = Prev(); while( d ) { d->Move( distance * d->DirectionGet() ); d = d->Prev(); // w drugą stronę też } } void TDynamicObject::update_shake( double const Timedelta ) { // Ra: mechanik powinien być telepany niezależnie od pozycji pojazdu // Ra: trzeba zrobić model bujania głową i wczepić go do pojazdu // Ra: tu by się przydało uwzględnić rozkład sił: // - na postoju horyzont prosto, kabina skosem // - przy szybkiej jeździe kabina prosto, horyzont pochylony // McZapkie: najpierw policzę pozycję w/m kabiny // ABu: rzucamy kabina tylko przy duzym FPS! // Mala histereza, zeby bez przerwy nie przelaczalo przy FPS~17 // Granice mozna ustalic doswiadczalnie. Ja proponuje 14:20 if( Global.iSlowMotion == 0 ) { // musi być pełna prędkość Math3D::vector3 shakevector; if( ( MoverParameters->EngineType == TEngineType::DieselElectric ) || ( MoverParameters->EngineType == TEngineType::DieselEngine ) ) { if( std::abs( MoverParameters->enrot ) > 0.0 ) { // engine vibration shakevector.x += ( std::sin( MoverParameters->eAngle * 4.0 ) * Timedelta * EngineShake.scale ) // fade in with rpm above threshold * clamp( ( MoverParameters->enrot - EngineShake.fadein_offset ) * EngineShake.fadein_factor, 0.0, 1.0 ) // fade out with rpm above threshold * interpolate( 1.0, 0.0, clamp( ( MoverParameters->enrot - EngineShake.fadeout_offset ) * EngineShake.fadeout_factor, 0.0, 1.0 ) ); } } if( ( HuntingShake.fadein_begin > 0.f ) && ( true == MoverParameters->TruckHunting ) ) { // hunting oscillation HuntingAngle = clamp_circular( HuntingAngle + 4.0 * HuntingShake.frequency * Timedelta * MoverParameters->Vel, 360.0 ); auto const huntingamount = interpolate( 0.0, 1.0, clamp( ( MoverParameters->Vel - HuntingShake.fadein_begin ) / ( HuntingShake.fadein_end - HuntingShake.fadein_begin ), 0.0, 1.0 ) ); shakevector.x += ( std::sin( glm::radians( HuntingAngle ) ) * Timedelta * HuntingShake.scale ) * huntingamount; IsHunting = ( huntingamount > 0.025 ); } auto const iVel { std::min( GetVelocity(), 150.0 ) }; if( iVel > 0.5 ) { // acceleration-driven base shake shakevector += Math3D::vector3( -MoverParameters->AccN * Timedelta * 5.0, // highlight side sway -MoverParameters->AccVert * Timedelta, -MoverParameters->AccSVBased * Timedelta * 1.25 ); // accent acceleration/deceleration } auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) }; if( LocalRandom( iVel ) > 25.0 ) { // extra shake at increased velocity shake += ShakeSpring.ComputateForces( Math3D::vector3( ( LocalRandom( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.x, ( LocalRandom( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.y, ( LocalRandom( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.z ) // * (( 200 - DynamicObject->MyTrack->iQualityFlag ) * 0.0075 ) // scale to 75-150% based on track quality * 1.25, ShakeState.offset ); } shake *= 0.85; ShakeState.velocity -= ( shake + ShakeState.velocity * 100 ) * ( BaseShake.jolt_scale.x + BaseShake.jolt_scale.y + BaseShake.jolt_scale.z ) / ( 200 ); // McZapkie: ShakeState.offset += ShakeState.velocity * Timedelta; if( std::abs( ShakeState.offset.y ) > std::abs( BaseShake.jolt_limit ) ) { ShakeState.velocity.y = -ShakeState.velocity.y; } } else { // hamowanie rzucania przy spadku FPS ShakeState.offset -= ShakeState.offset * std::min( Timedelta, 1.0 ); // po tym chyba potrafią zostać jakieś ułamki, które powodują zjazd } } std::pair TDynamicObject::shake_angles() const { return { std::atan( ShakeState.velocity.x * BaseShake.angle_scale.x ), std::atan( ShakeState.velocity.z * BaseShake.angle_scale.z ) }; } void TDynamicObject::powertrain_sounds::position( glm::vec3 const Location ) { auto const nullvector { glm::vec3() }; std::vector enginesounds = { &inverter, &motor_relay, &dsbWejscie_na_bezoporow, &motor_parallel, &motor_shuntfield, &rsWentylator, &engine, &engine_ignition, &engine_shutdown, &engine_revving, &engine_turbo, &oil_pump, &fuel_pump, &water_pump, &water_heater, &radiator_fan, &radiator_fan_aux, &transmission, &rsEngageSlippery }; for( auto sound : enginesounds ) { if( sound->offset() == nullvector ) { sound->offset( Location ); } } } void TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, double const Deltatime ) { if( Vehicle.Power == 0 ) { return; } double frequency { 1.0 }; double volume { 0.0 }; // oil pump if( true == Vehicle.OilPump.is_active ) { oil_pump .pitch( oil_pump.m_frequencyoffset + oil_pump.m_frequencyfactor * 1.f ) .gain( oil_pump.m_amplitudeoffset + oil_pump.m_amplitudefactor * 1.f ) .play( sound_flags::exclusive | sound_flags::looping ); } else { oil_pump.stop(); } // fuel pump if( true == Vehicle.FuelPump.is_active ) { fuel_pump .pitch( fuel_pump.m_frequencyoffset + fuel_pump.m_frequencyfactor * 1.f ) .gain( fuel_pump.m_amplitudeoffset + fuel_pump.m_amplitudefactor * 1.f ) .play( sound_flags::exclusive | sound_flags::looping ); } else { fuel_pump.stop(); } // water pump if( true == Vehicle.WaterPump.is_active ) { water_pump .pitch( water_pump.m_frequencyoffset + water_pump.m_frequencyfactor * 1.f ) .gain( water_pump.m_amplitudeoffset + water_pump.m_amplitudefactor * 1.f ) .play( sound_flags::exclusive | sound_flags::looping ); } else { water_pump.stop(); } // water heater if( true == Vehicle.WaterHeater.is_active ) { water_heater .pitch( water_heater.m_frequencyoffset + water_heater.m_frequencyfactor * 1.f ) .gain( water_heater.m_amplitudeoffset + water_heater.m_amplitudefactor * 1.f ) .play( sound_flags::exclusive | sound_flags::looping ); } else { water_heater.stop(); } // engine sounds // ignition if( engine_state_last != Vehicle.Mains ) { if( true == Vehicle.Mains ) { // TODO: separate engine and main circuit // engine activation engine_shutdown.stop(); engine_ignition .pitch( engine_ignition.m_frequencyoffset + engine_ignition.m_frequencyfactor * 1.f ) .gain( engine_ignition.m_amplitudeoffset + engine_ignition.m_amplitudefactor * 1.f ) .play( sound_flags::exclusive ); // main circuit activation linebreaker_close .pitch( linebreaker_close.m_frequencyoffset + linebreaker_close.m_frequencyfactor * 1.f ) .gain( linebreaker_close.m_amplitudeoffset + linebreaker_close.m_amplitudefactor * 1.f ) .play(); } else { // engine deactivation engine_ignition.stop(); engine_shutdown.pitch( engine_shutdown.m_frequencyoffset + engine_shutdown.m_frequencyfactor * 1.f ) .gain( engine_shutdown.m_amplitudeoffset + engine_shutdown.m_amplitudefactor * 1.f ) .play( sound_flags::exclusive ); // main circuit deactivation linebreaker_open .pitch( linebreaker_open.m_frequencyoffset + linebreaker_open.m_frequencyfactor * 1.f ) .gain( linebreaker_open.m_amplitudeoffset + linebreaker_open.m_amplitudefactor * 1.f ) .play(); } engine_state_last = Vehicle.Mains; } // main engine sound if( true == Vehicle.Mains ) { if( ( std::abs( Vehicle.enrot ) > 0.01 ) // McZapkie-280503: zeby dla dumb dzialal silnik na jalowych obrotach || ( Vehicle.EngineType == TEngineType::Dumb ) ) { // frequency calculation auto const normalizer { ( true == engine.is_combined() ? // for combined engine sound we calculate sound point in rpm, to make .mmd files setup easier // NOTE: we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range 60.f * 0.01f : // for legacy single-piece sounds the standard frequency calculation is left untouched 1.f ) }; frequency = engine.m_frequencyoffset + engine.m_frequencyfactor * std::abs( Vehicle.enrot ) * normalizer; if( Vehicle.EngineType == TEngineType::Dumb ) { frequency -= 0.2 * Vehicle.EnginePower / ( 1 + Vehicle.Power * 1000 ); } // base volume calculation switch( Vehicle.EngineType ) { // TODO: check calculated values case TEngineType::DieselElectric: { volume = engine.m_amplitudeoffset + engine.m_amplitudefactor * ( 0.25 * ( Vehicle.EnginePower / Vehicle.Power ) + 0.75 * ( Vehicle.enrot * 60 ) / ( Vehicle.DElist[ Vehicle.MainCtrlPosNo ].RPM ) ); break; } case TEngineType::DieselEngine: { if( Vehicle.enrot > 0.0 ) { volume = ( Vehicle.EnginePower > 0 ? engine.m_amplitudeoffset + engine.m_amplitudefactor * Vehicle.dizel_fill : engine.m_amplitudeoffset + engine.m_amplitudefactor * std::fabs( Vehicle.enrot / Vehicle.dizel_nmax ) ); } break; } default: { volume = engine.m_amplitudeoffset + engine.m_amplitudefactor * ( Vehicle.EnginePower + std::fabs( Vehicle.enrot ) * 60.0 ); break; } } if( engine_volume >= 0.05 ) { auto enginerevvolume { 0.f }; if( ( Vehicle.EngineType == TEngineType::DieselElectric ) || ( Vehicle.EngineType == TEngineType::DieselEngine ) ) { // diesel engine revolutions increase; it can potentially decrease volume of base engine sound if( engine_revs_last != -1.f ) { // calculate potential recent increase of engine revolutions auto const revolutionsperminute { Vehicle.enrot * 60 }; auto const revolutionsdifference { revolutionsperminute - engine_revs_last }; auto const idlerevolutionsthreshold { 1.01 * ( Vehicle.EngineType == TEngineType::DieselElectric ? Vehicle.DElist[ 0 ].RPM : Vehicle.dizel_nmin * 60 ) }; engine_revs_change = std::max( 0.0, engine_revs_change - 2.5 * Deltatime ); if( ( revolutionsperminute > idlerevolutionsthreshold ) && ( revolutionsdifference > 1.0 * Deltatime ) ) { engine_revs_change = clamp( engine_revs_change + 5.0 * Deltatime, 0.0, 1.25 ); } enginerevvolume = 0.8 * engine_revs_change; } engine_revs_last = Vehicle.enrot * 60; auto const enginerevfrequency { ( true == engine_revving.is_combined() ? // if the sound contains multiple samples we reuse rpm-based calculation from the engine frequency : engine_revving.m_frequencyoffset + 1.f * engine_revving.m_frequencyfactor ) }; if( enginerevvolume > 0.02 ) { engine_revving .pitch( enginerevfrequency ) .gain( enginerevvolume ) .play( sound_flags::exclusive ); } else { engine_revving.stop(); } } // diesel engines // multi-part revving sound pieces replace base engine sound, single revving simply gets mixed with the base auto const enginevolume { ( ( ( enginerevvolume > 0.02 ) && ( true == engine_revving.is_combined() ) ) ? std::max( 0.0, engine_volume - enginerevvolume ) : engine_volume ) }; engine .pitch( frequency ) .gain( enginevolume ) .play( sound_flags::exclusive | sound_flags::looping ); } // enginevolume > 0.05 } else { engine.stop(); } } engine_volume = interpolate( engine_volume, volume, 0.25 ); if( engine_volume < 0.05 ) { engine.stop(); } // youBy - przenioslem, bo diesel tez moze miec turbo if( Vehicle.TurboTest > 0 ) { // udawanie turbo: auto const pitch_diesel { Vehicle.EngineType == TEngineType::DieselEngine ? Vehicle.enrot / Vehicle.dizel_nmax : 1 }; auto const goalpitch { std::max( 0.025, ( engine_volume * pitch_diesel + engine_turbo.m_frequencyoffset ) * engine_turbo.m_frequencyfactor ) }; auto const goalvolume { ( ( ( Vehicle.MainCtrlPos >= Vehicle.TurboTest ) && ( Vehicle.enrot > 0.1 ) ) ? std::max( 0.0, ( engine_turbo_pitch + engine_turbo.m_amplitudeoffset ) * engine_turbo.m_amplitudefactor ) : 0.0 ) }; auto const currentvolume { engine_turbo.gain() }; auto const changerate { 0.4 * Deltatime }; engine_turbo_pitch = ( engine_turbo_pitch > goalpitch ? std::max( goalpitch, engine_turbo_pitch - changerate * 0.5 ) : std::min( goalpitch, engine_turbo_pitch + changerate ) ); volume = ( currentvolume > goalvolume ? std::max( goalvolume, currentvolume - changerate ) : std::min( goalvolume, currentvolume + changerate ) ); engine_turbo .pitch( 0.4 + engine_turbo_pitch * 0.4 ) .gain( volume ); if( volume > 0.05 ) { engine_turbo.play( sound_flags::exclusive | sound_flags::looping ); } else { engine_turbo.stop(); } } if( Vehicle.dizel_engage > 0.1 ) { if( std::abs( Vehicle.dizel_engagedeltaomega ) > 0.2 ) { frequency = rsEngageSlippery.m_frequencyoffset + rsEngageSlippery.m_frequencyfactor * std::fabs( Vehicle.dizel_engagedeltaomega ); volume = rsEngageSlippery.m_amplitudeoffset + rsEngageSlippery.m_amplitudefactor * ( Vehicle.dizel_engage ); } else { frequency = 1.f; // rsEngageSlippery.FA+0.7*rsEngageSlippery.FM*(fabs(mvControlled->enrot)+mvControlled->nmax); volume = ( Vehicle.dizel_engage > 0.2 ? rsEngageSlippery.m_amplitudeoffset + 0.2 * rsEngageSlippery.m_amplitudefactor * ( Vehicle.enrot / Vehicle.nmax ) : 0.f ); } rsEngageSlippery .pitch( frequency ) .gain( volume ) .play( sound_flags::exclusive | sound_flags::looping ); } else { rsEngageSlippery.stop(); } // motor sounds volume = 0.0; if( false == motors.empty() ) { if( std::abs( Vehicle.nrot ) > 0.01 ) { auto const &motor { motors.front() }; // frequency calculation auto normalizer { 1.f }; if( true == motor.is_combined() ) { // for combined motor sound we calculate sound point in rpm, to make .mmd files setup easier // NOTE: we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range normalizer = 60.f * 0.01f; } auto const motorrevolutions { std::abs( Vehicle.nrot ) * Vehicle.Transmision.Ratio }; frequency = motor.m_frequencyoffset + motor.m_frequencyfactor * motorrevolutions * normalizer; // base volume calculation switch( Vehicle.EngineType ) { case TEngineType::ElectricInductionMotor: { volume = motor.m_amplitudeoffset + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 2 ); break; } case TEngineType::ElectricSeriesMotor: { volume = motor.m_amplitudeoffset + motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 60.0 ); break; } default: { volume = motor.m_amplitudeoffset + motor.m_amplitudefactor * motorrevolutions * 60.0; break; } } if( Vehicle.EngineType == TEngineType::ElectricSeriesMotor ) { // volume variation if( ( volume < 1.0 ) && ( Vehicle.EnginePower < 100 ) ) { auto const volumevariation { LocalRandom( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) }; if( volumevariation < 2 ) { volume += volumevariation / 200; } } if( ( Vehicle.DynamicBrakeFlag ) && ( Vehicle.EnginePower > 0.1 ) ) { // Szociu - 29012012 - jeżeli uruchomiony jest hamulec elektrodynamiczny, odtwarzany jest dźwięk silnika volume += 0.8; } } // scale motor volume based on whether they're active motor_momentum = clamp( motor_momentum - 1.0 * Deltatime // smooth out decay + std::abs( Vehicle.Mm ) / 60.0 * Deltatime, 0.0, 1.25 ); volume *= std::max( 0.25f, motor_momentum ); motor_volume = interpolate( motor_volume, volume, 0.25 ); if( motor_volume >= 0.05 ) { // apply calculated parameters to all motor instances for( auto &motor : motors ) { motor .pitch( frequency ) .gain( motor_volume ) .play( sound_flags::exclusive | sound_flags::looping ); } } else { // stop all motor instances for( auto &motor : motors ) { motor.stop(); } } } else { // stop all motor instances motor_volume = 0.0; for( auto &motor : motors ) { motor.stop(); } } } // motor blowers if( false == motorblowers.empty() ) { for( auto &blowersound : motorblowers ) { // match the motor blower and the sound source based on whether they're located in the front or the back of the vehicle auto const &blower { Vehicle.MotorBlowers[ ( blowersound.offset().z > 0 ? end::front : end::rear ) ] }; if( blower.revolutions > 1 ) { blowersound .pitch( true == blowersound.is_combined() ? blower.revolutions * 0.01f : blowersound.m_frequencyoffset + blowersound.m_frequencyfactor * blower.revolutions ) .gain( blowersound.m_amplitudeoffset + blowersound.m_amplitudefactor * blower.revolutions ) .play( sound_flags::exclusive | sound_flags::looping ); } else { blowersound.stop(); } } } // inverter sounds if( Vehicle.EngineType == TEngineType::ElectricInductionMotor ) { if( Vehicle.InverterFrequency > 0.1 ) { volume = inverter.m_amplitudeoffset + inverter.m_amplitudefactor * std::sqrt( std::abs( Vehicle.eimv_pr) ); inverter .pitch( inverter.m_frequencyoffset + inverter.m_frequencyfactor * Vehicle.InverterFrequency ) .gain( volume ) .play( sound_flags::exclusive | sound_flags::looping ); } else { inverter.stop(); } } // ventillator sounds if( Vehicle.RventRot > 0.1 ) { rsWentylator .pitch( rsWentylator.m_frequencyoffset + rsWentylator.m_frequencyfactor * Vehicle.RventRot ) .gain( rsWentylator.m_amplitudeoffset + rsWentylator.m_amplitudefactor * Vehicle.RventRot ) .play( sound_flags::exclusive | sound_flags::looping ); } else { // ...otherwise shut down the sound rsWentylator.stop(); } // radiator fan sounds if( ( Vehicle.EngineType == TEngineType::DieselEngine ) || ( Vehicle.EngineType == TEngineType::DieselElectric ) ) { if( Vehicle.dizel_heat.rpmw > 0.1 ) { // NOTE: fan speed tends to max out at ~100 rpm; by default we try to get pitch range of 0.5-1.5 and volume range of 0.5-1.0 radiator_fan .pitch( 0.5 + radiator_fan.m_frequencyoffset + radiator_fan.m_frequencyfactor * Vehicle.dizel_heat.rpmw * 0.01 ) .gain( 0.5 + radiator_fan.m_amplitudeoffset + 0.5 * radiator_fan.m_amplitudefactor * Vehicle.dizel_heat.rpmw * 0.01 ) .play( sound_flags::exclusive | sound_flags::looping ); } else { // ...otherwise shut down the sound radiator_fan.stop(); } if( Vehicle.dizel_heat.rpmw2 > 0.1 ) { // NOTE: fan speed tends to max out at ~100 rpm; by default we try to get pitch range of 0.5-1.5 and volume range of 0.5-1.0 radiator_fan_aux .pitch( 0.5 + radiator_fan_aux.m_frequencyoffset + radiator_fan_aux.m_frequencyfactor * Vehicle.dizel_heat.rpmw2 * 0.01 ) .gain( 0.5 + radiator_fan_aux.m_amplitudeoffset + 0.5 * radiator_fan_aux.m_amplitudefactor * Vehicle.dizel_heat.rpmw2 * 0.01 ) .play( sound_flags::exclusive | sound_flags::looping ); } else { // ...otherwise shut down the sound radiator_fan_aux.stop(); } } // relay sounds auto const soundflags { Vehicle.SoundFlag }; if( TestFlag( soundflags, sound::relay ) ) { // przekaznik - gdy bezpiecznik, automatyczny rozruch itp if( true == TestFlag( soundflags, sound::shuntfield ) ) { // shunt field motor_shuntfield .pitch( true == motor_shuntfield.is_combined() ? Vehicle.ScndCtrlActualPos * 0.01f : motor_shuntfield.m_frequencyoffset + motor_shuntfield.m_frequencyfactor * 1.f ) .gain( motor_shuntfield.m_amplitudeoffset + ( true == TestFlag( soundflags, sound::loud ) ? 1.0f : 0.8f ) * motor_shuntfield.m_amplitudefactor ) .play(); } else if( true == TestFlag( soundflags, sound::parallel ) ) { // parallel mode if( TestFlag( soundflags, sound::loud ) ) { dsbWejscie_na_bezoporow.play(); } else { motor_parallel.play(); } } else { // series mode motor_relay .pitch( true == motor_relay.is_combined() ? Vehicle.MainCtrlActualPos * 0.01f : motor_relay.m_frequencyoffset + motor_relay.m_frequencyfactor * 1.f ) .gain( motor_relay.m_amplitudeoffset + ( true == TestFlag( soundflags, sound::loud ) ? 1.0f : 0.8f ) * motor_relay.m_amplitudefactor ) .play(); } } if( Vehicle.Vel > 0.1 ) { transmission .pitch( transmission.m_frequencyoffset + transmission.m_frequencyfactor * Vehicle.Vel ) .gain( transmission.m_amplitudeoffset + transmission.m_amplitudefactor * Vehicle.Vel ) .play( sound_flags::exclusive | sound_flags::looping ); } else { transmission.stop(); } } // 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 == TPowerSource::CurrentCollector ) { update_traction( vehicle ); } vehicle->MoverParameters->ComputeConstans(); vehicle->update_neighbours(); } 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 ); } for( auto *vehicle : m_items ) { vehicle->FastUpdate( Deltatime ); } } } for( auto *vehicle : m_items ) { vehicle->UpdateForce( Deltatime ); } auto const totaltime { Deltatime * Iterationcount }; // całkowity czas for( auto *vehicle : m_items ) { // Ra 2015-01: tylko tu przelicza sieć trakcyjną vehicle->Update( Deltatime, totaltime ); } // jeśli jest coś do usunięcia z listy, to trzeba na końcu erase_disabled(); } // 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 == Vehicle->MoverParameters->Pantographs[ pantographindex ].is_active ) { // 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 ) { // sanity check. shouldn't happen in theory, but did happen in practice if( pantograph->hvPowerWire == nullptr ) { break; } // 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 // TODO: investigate this routine with reardriver/negative speed, does it picks the right wire? 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 ); } // maintenance; removes from tracks consists with vehicles marked as disabled bool vehicle_table::erase_disabled() { if( false == TDynamicObject::bDynamicRemove ) { return false; } // go through the list and retrieve vehicles scheduled for removal... type_sequence disabledvehicles; for( auto *vehicle : m_items ) { if( false == vehicle->bEnabled ) { disabledvehicles.emplace_back( vehicle ); } } // ...now propagate removal flag through affected consists... for( auto *vehicle : disabledvehicles ) { TDynamicObject *coupledvehicle { vehicle }; while( ( coupledvehicle = coupledvehicle->Next() ) != nullptr ) { coupledvehicle->bEnabled = false; } // (try to) run propagation in both directions, it's simpler than branching based on direction etc coupledvehicle = vehicle; while( ( coupledvehicle = coupledvehicle->Prev() ) != nullptr ) { coupledvehicle->bEnabled = false; } } // ...then actually remove all disabled vehicles... auto vehicleiter = std::begin( m_items ); while( vehicleiter != std::end( m_items ) ) { auto *vehicle { *vehicleiter }; if( true == vehicle->bEnabled ) { ++vehicleiter; } else { if( ( vehicle->MyTrack != nullptr ) && ( true == vehicle->MyTrack->RemoveDynamicObject( vehicle ) ) ) { vehicle->MyTrack = nullptr; } if( ( simulation::Train != nullptr ) && ( simulation::Train->Dynamic() == vehicle ) ) { // clear potential train binding // TBD, TODO: kill vehicle sounds SafeDelete( simulation::Train ); } simulation::Trains.purge(vehicle->name()); // remove potential entries in the light array simulation::Lights.remove( vehicle ); /* // finally get rid of the vehicle and its record themselves // BUG: deleting the vehicle leaves dangling pointers in event->Activator and potentially elsewhere // TBD, TODO: either mark 'dead' vehicles with additional flag, or delete the references as well SafeDelete( vehicle ); vehicleiter = m_items.erase( vehicleiter ); */ ++vehicleiter; // NOTE: instead of the erase in the disabled section } } // ...and call it a day TDynamicObject::bDynamicRemove = false; return true; }