diff --git a/AnimModel.cpp b/AnimModel.cpp index 33fcc0ae..745df963 100644 --- a/AnimModel.cpp +++ b/AnimModel.cpp @@ -244,24 +244,10 @@ void TAnimContainer::UpdateModel() { } if (fRotateSpeed != 0.0) { - - /* - double dif= fDesiredAngle-fAngle; - double s= fRotateSpeed*sign(dif)*Timer::GetDeltaTime(); - if ((abs(s)-abs(dif))>0) - fAngle= fDesiredAngle; - else - fAngle+= s; - - while (fAngle>360) fAngle-= 360; - while (fAngle<-360) fAngle+= 360; - pSubModel->SetRotate(vRotateAxis,fAngle); - */ - bool anim = false; auto dif = vDesiredAngles - vRotateAngles; double s; - s = fRotateSpeed * sign(dif.x) * Timer::GetDeltaTime(); + s = std::abs( fRotateSpeed ) * sign(dif.x) * Timer::GetDeltaTime(); if (fabs(s) >= fabs(dif.x)) vRotateAngles.x = vDesiredAngles.x; else @@ -269,7 +255,7 @@ void TAnimContainer::UpdateModel() { vRotateAngles.x += s; anim = true; } - s = fRotateSpeed * sign(dif.y) * Timer::GetDeltaTime(); + s = std::abs( fRotateSpeed ) * sign(dif.y) * Timer::GetDeltaTime(); if (fabs(s) >= fabs(dif.y)) vRotateAngles.y = vDesiredAngles.y; else @@ -277,7 +263,7 @@ void TAnimContainer::UpdateModel() { vRotateAngles.y += s; anim = true; } - s = fRotateSpeed * sign(dif.z) * Timer::GetDeltaTime(); + s = std::abs( fRotateSpeed ) * sign(dif.z) * Timer::GetDeltaTime(); if (fabs(s) >= fabs(dif.z)) vRotateAngles.z = vDesiredAngles.z; else @@ -285,22 +271,27 @@ void TAnimContainer::UpdateModel() { vRotateAngles.z += s; anim = true; } - while (vRotateAngles.x >= 360) - vRotateAngles.x -= 360; - while (vRotateAngles.x <= -360) - vRotateAngles.x += 360; - while (vRotateAngles.y >= 360) - vRotateAngles.y -= 360; - while (vRotateAngles.y <= -360) - vRotateAngles.y += 360; - while (vRotateAngles.z >= 360) - vRotateAngles.z -= 360; - while (vRotateAngles.z <= -360) - vRotateAngles.z += 360; - if (vRotateAngles.x == 0.0) - if (vRotateAngles.y == 0.0) - if (vRotateAngles.z == 0.0) - iAnim &= ~1; // kąty są zerowe + // HACK: negative speed allows to work around legacy behaviour, where desired angle > 360 meant permanent rotation + if( fRotateSpeed > 0.0 ) { + while( vRotateAngles.x >= 360 ) + vRotateAngles.x -= 360; + while( vRotateAngles.x <= -360 ) + vRotateAngles.x += 360; + while( vRotateAngles.y >= 360 ) + vRotateAngles.y -= 360; + while( vRotateAngles.y <= -360 ) + vRotateAngles.y += 360; + while( vRotateAngles.z >= 360 ) + vRotateAngles.z -= 360; + while( vRotateAngles.z <= -360 ) + vRotateAngles.z += 360; + } + + if( ( vRotateAngles.x == 0.0 ) + && ( vRotateAngles.y == 0.0 ) + && ( vRotateAngles.z == 0.0 ) ) { + iAnim &= ~1; // kąty są zerowe + } if (!anim) { // nie potrzeba przeliczać już fRotateSpeed = 0.0; diff --git a/Button.cpp b/Button.cpp index dfb36123..f440678b 100644 --- a/Button.cpp +++ b/Button.cpp @@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/. #include "Button.h" #include "parser.h" #include "Model3d.h" +#include "DynObj.h" #include "Console.h" #include "Logs.h" #include "renderer.h" @@ -25,17 +26,19 @@ void TButton::Clear(int i) Update(); // kasowanie bitu Feedback, o ile jakiś ustawiony }; -void TButton::Init( std::string const &asName, TModel3d *pModel, bool bNewOn ) { +bool TButton::Init( std::string const &asName, TModel3d const *pModel, bool bNewOn ) { - if( pModel == nullptr ) { return; } + if( pModel == nullptr ) { return false; } pModelOn = pModel->GetFromName( asName + "_on" ); pModelOff = pModel->GetFromName( asName + "_off" ); m_state = bNewOn; Update(); + + return( ( pModelOn != nullptr ) || ( pModelOff != nullptr ) ); }; -void TButton::Load( cParser &Parser, TDynamicObject const *Owner, TModel3d *pModel1, TModel3d *pModel2 ) { +void TButton::Load( cParser &Parser, TDynamicObject const *Owner ) { std::string submodelname; @@ -57,21 +60,17 @@ void TButton::Load( cParser &Parser, TDynamicObject const *Owner, TModel3d *pMod m_soundfxincrease.owner( Owner ); m_soundfxdecrease.owner( Owner ); - if( pModel1 ) { - // poszukiwanie submodeli w modelu - Init( submodelname, pModel1, false ); + std::array sources { Owner->mdKabina, Owner->mdLowPolyInt, Owner->mdModel }; + for( auto const *source : sources ) { + if( true == Init( submodelname, source, false ) ) { + // got what we wanted, bail out + break; + } } - if( ( pModelOn == nullptr ) - && ( pModelOff == nullptr ) - && ( pModel2 != nullptr ) ) { - // poszukiwanie submodeli w modelu - Init( submodelname, pModel2, false ); - } - if( ( pModelOn == nullptr ) && ( pModelOff == nullptr ) ) { // if we failed to locate even one state submodel, cry - ErrorLog( "Bad model: failed to locate sub-model \"" + submodelname + "\" in 3d model \"" + ( pModel1 != nullptr ? pModel1->NameGet() : pModel2 != nullptr ? pModel2->NameGet() : "NULL" ) + "\"", logtype::model ); + ErrorLog( "Bad model: failed to locate sub-model \"" + submodelname + "\" in 3d model(s) of \"" + Owner->name() + "\"", logtype::model ); } // pass submodel location to defined sounds diff --git a/Button.h b/Button.h index c0399b8e..3340e37e 100644 --- a/Button.h +++ b/Button.h @@ -31,8 +31,8 @@ public: return ( ( pModelOn != nullptr ) || ( pModelOff != nullptr ) ); } void Update(); - void Init( std::string const &asName, TModel3d *pModel, bool bNewOn = false ); - void Load( cParser &Parser, TDynamicObject const *Owner, TModel3d *pModel1, TModel3d *pModel2 = nullptr ); + bool Init( std::string const &asName, TModel3d const *pModel, bool bNewOn = false ); + void Load( cParser &Parser, TDynamicObject const *Owner ); void AssignBool(bool const *bValue); // returns offset of submodel associated with the button from the model centre glm::vec3 model_offset() const; diff --git a/Classes.h b/Classes.h index ae514622..ecbfd1b4 100644 --- a/Classes.h +++ b/Classes.h @@ -44,6 +44,7 @@ struct light_array; namespace scene { struct node_data; class basic_node; +using group_handle = std::size_t; } namespace Mtable diff --git a/Driver.cpp b/Driver.cpp index 3ddd365c..590cbee1 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -152,6 +152,7 @@ const double EasyAcceleration = 0.85; //[m/ss] const double HardAcceleration = 9.81; const double PrepareTime = 2.0; //[s] przebłyski świadomości przy odpalaniu bool WriteLogFlag = false; +double const deltalog = 0.05; // przyrost czasu std::string StopReasonTable[] = { // przyczyny zatrzymania ruchu AI @@ -833,7 +834,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN double v; // prędkość double d; // droga double d_to_next_sem = 10000.0; //ustaiwamy na pewno dalej niż widzi AI - bool isatpassengerstop { false }; // true if the consist is within acceptable range of w4 post + IsAtPassengerStop = false; TCommandType go = TCommandType::cm_Unknown; eSignNext = NULL; // te flagi są ustawiane tutaj, w razie potrzeby @@ -921,7 +922,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN sSpeedTable[i].iFlags = 0; } } - isatpassengerstop = ( + IsAtPassengerStop = ( ( sSpeedTable[ i ].fDist <= passengerstopmaxdistance ) // Ra 2F1I: odległość plus długość pociągu musi być mniejsza od długości // peronu, chyba że pociąg jest dłuższy, to wtedy minimalna. @@ -941,7 +942,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN if( mvOccupied->Vel > 0.3 ) { // jeśli jedzie (nie trzeba czekać, aż się drgania wytłumią - drzwi zamykane od 1.0) to będzie zatrzymanie sSpeedTable[ i ].fVelNext = 0; - } else if( true == isatpassengerstop ) { + } else if( true == IsAtPassengerStop ) { // jeśli się zatrzymał przy W4, albo stał w momencie zobaczenia W4 if( !AIControllFlag ) { // w razie przełączenia na AI ma nie podciągać do W4, gdy użytkownik zatrzymał za daleko @@ -964,8 +965,8 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // perform loading/unloading auto const platformside = static_cast( std::floor( std::abs( sSpeedTable[ i ].evEvent->input_value( 2 ) ) ) ) % 10; - auto const exchangetime = std::max( 5.0, simulation::Station.update_load( pVehicles[ 0 ], *TrainParams, platformside ) ); - WaitingSet( std::max( -fStopTime, exchangetime ) ); // na końcu rozkładu się ustawia 60s i tu by było skrócenie + auto const exchangetime = simulation::Station.update_load( pVehicles[ 0 ], *TrainParams, platformside ); + WaitingSet( exchangetime ); if( TrainParams->CheckTrainLatency() < 0.0 ) { // odnotowano spóźnienie @@ -1007,6 +1008,21 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN continue; } } + else { + // sitting at passenger stop + if( fStopTime < 0 ) { + // verify progress of load exchange + auto exchangetime { 0.f }; + auto *vehicle { pVehicles[ 0 ] }; + while( vehicle != nullptr ) { + exchangetime = std::max( exchangetime, vehicle->LoadExchangeTime() ); + vehicle = vehicle->Next(); + } + if( exchangetime > 0 ) { + WaitingSet( exchangetime ); + } + } + } if (OrderCurrentGet() & Shunt) { OrderNext(Obey_train); // uruchomić jazdę pociągową @@ -1017,7 +1033,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // jeśli są dalsze stacje, czekamy do godziny odjazdu if (TrainParams->IsTimeToGo(simulation::Time.data().wHour, simulation::Time.data().wMinute)) { // z dalszą akcją czekamy do godziny odjazdu - isatpassengerstop = false; + IsAtPassengerStop = false; // przy jakim dystansie (stanie licznika) ma przesunąć na następny postój fLastStopExpDist = mvOccupied->DistCounter + 0.050 + 0.001 * fLength; TrainParams->StationIndexInc(); // przejście do następnej @@ -1358,7 +1374,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // 2014-02: jeśli stoi, a ma do przejechania kawałek, to niech jedzie if( ( mvOccupied->Vel < 0.01 ) && ( true == TestFlag( sSpeedTable[ i ].iFlags, ( spEnabled | spEvent | spPassengerStopPoint ) ) ) - && ( false == isatpassengerstop ) ) { + && ( false == IsAtPassengerStop ) ) { // ma podjechać bliżej - czy na pewno w tym miejscu taki warunek? a = ( ( d > passengerstopmaxdistance ) || ( ( iDrivigFlags & moveStopCloser ) != 0 ) ? fAcc : @@ -1439,7 +1455,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN } //analiza spisanych z tabelki ograniczeń i nadpisanie aktualnego - if( ( true == isatpassengerstop ) && ( mvOccupied->Vel < 0.01 ) ) { + if( ( true == IsAtPassengerStop ) && ( mvOccupied->Vel < 0.01 ) ) { // if stopped at a valid passenger stop, hold there fVelDes = 0.0; } @@ -1607,6 +1623,11 @@ TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche, mvOccupied->TrainType == dt_EZT ? -0.55 : mvOccupied->TrainType == dt_DMU ? -0.45 : -0.2 ); + // HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors + if( ( mvOccupied->TrainType == dt_EZT ) + && ( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) ) { + fAccThreshold += 0.10; + } } // TrainParams=NewTrainParams; // if (TrainParams) @@ -1655,11 +1676,6 @@ void TController::CloseLog() if (WriteLogFlag) { LogFile.close(); - // if WriteLogFlag) - // CloseFile(AILogFile); - /* append(AIlogFile); - writeln(AILogFile,ElapsedTime5:2,": QUIT"); - close(AILogFile); */ } }; @@ -1722,11 +1738,8 @@ void TController::Activation() mvOccupied->DirectionForward(); // kierunek na 0 while (mvOccupied->ActiveDir > 0) mvOccupied->DirectionBackward(); - if (TestFlag(d->MoverParameters->Couplers[iDirectionOrder < 0 ? 1 : 0].CouplingFlag, - ctrain_controll)) - { - mvControlling->MainSwitch( - false); // dezaktywacja czuwaka, jeśli przejście do innego członu + if (TestFlag(d->MoverParameters->Couplers[iDirectionOrder < 0 ? 1 : 0].CouplingFlag, ctrain_controll)) { + mvControlling->MainSwitch( false); // dezaktywacja czuwaka, jeśli przejście do innego członu mvOccupied->DecLocalBrakeLevel(LocalBrakePosNo); // zwolnienie hamulca w opuszczanym pojeździe // mvOccupied->BrakeLevelSet((mvOccupied->BrakeHandle==FVel6)?4:-2); //odcięcie na // zaworze maszynisty, FVel6 po drugiej stronie nie luzuje @@ -1736,8 +1749,7 @@ void TController::Activation() mvOccupied->ActiveCab = mvOccupied->CabNo; // użytkownik moze zmienić ActiveCab wychodząc mvOccupied->CabDeactivisation(); // tak jest w Train.cpp // przejście AI na drugą stronę EN57, ET41 itp. - while (TestFlag(d->MoverParameters->Couplers[iDirection < 0 ? 1 : 0].CouplingFlag, - ctrain_controll)) + while (TestFlag(d->MoverParameters->Couplers[iDirection < 0 ? 1 : 0].CouplingFlag, ctrain_controll)) { // jeśli pojazd z przodu jest ukrotniony, to przechodzimy do niego d = iDirection * d->DirectionGet() < 0 ? d->Next() : d->Prev(); // przechodzimy do następnego członu @@ -1801,8 +1813,7 @@ void TController::AutoRewident() // · masa (jako suma) -> jest w (fMass) while (d) { // klasyfikacja pojazdów wg BrakeDelays i mocy (licznik) - if (d->MoverParameters->Power < - 1) // - lokomotywa - Power>1 - ale może być nieczynna na końcu... + if (d->MoverParameters->Power < 1) // - lokomotywa - Power>1 - ale może być nieczynna na końcu... if (TestFlag(d->MoverParameters->BrakeDelays, bdelay_R)) ++r; // - wagon pospieszny - jest R else if (TestFlag(d->MoverParameters->BrakeDelays, bdelay_G)) @@ -1936,7 +1947,13 @@ void TController::AutoRewident() 0.25 ); if( mvOccupied->TrainType == dt_EZT ) { - fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] ); + if( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) { + // HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors + fNominalAccThreshold = std::max( -0.60, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] ); + } + else { + fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] ); + } fBrakeReaction = 0.25; } else if( mvOccupied->TrainType == dt_DMU ) { @@ -2055,18 +2072,24 @@ bool TController::CheckVehicles(TOrders user) p = pVehicles[0]; while (p) { + // HACK: wagony muszą mieć baterię załączoną do otwarcia drzwi... + if( ( p != pVehicle ) + && ( ( p->MoverParameters->Couplers[ side::front ].CouplingFlag & ( coupling::control | coupling::permanent ) ) == 0 ) + && ( ( p->MoverParameters->Couplers[ side::rear ].CouplingFlag & ( coupling::control | coupling::permanent ) ) == 0 ) ) { + // NOTE: don't set battery in the occupied vehicle, let the user/ai do it explicitly + p->MoverParameters->BatterySwitch( true ); + } + if (p->asDestination == "none") p->DestinationSet(TrainParams->Relation2, TrainParams->TrainName); // relacja docelowa, jeśli nie było if (AIControllFlag) // jeśli prowadzi komputer p->RaLightsSet(0, 0); // gasimy światła if (p->MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) - { // jeśli pojazd posiada pantograf, to przydzielamy mu maskę, którą będzie informował o - // jeździe bezprądowej + { // jeśli pojazd posiada pantograf, to przydzielamy mu maskę, którą będzie informował o jeździe bezprądowej p->iOverheadMask = pantmask; pantmask = pantmask << 1; // przesunięcie bitów, max. 32 pojazdy z pantografami w składzie } - d = p->DirectionSet(d ? 1 : -1); // zwraca położenie następnego (1=zgodny,0=odwrócony - - // względem czoła składu) + d = p->DirectionSet(d ? 1 : -1); // zwraca położenie następnego (1=zgodny,0=odwrócony - względem czoła składu) p->ctOwner = this; // dominator oznacza swoje terytorium p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła) } @@ -2086,9 +2109,6 @@ bool TController::CheckVehicles(TOrders user) Lights( frontlights, rearlights ); -#if LOGPRESS == 0 - AutoRewident(); // nastawianie hamulca do jazdy pociągowej -#endif } else if (OrderCurrentGet() & (Shunt | Connect)) { @@ -2121,46 +2141,59 @@ bool TController::CheckVehicles(TOrders user) Lights( 0, light::headlight_right ); } } + // nastawianie hamulca do jazdy pociągowej + if( OrderCurrentGet() & ( Obey_train | Shunt ) ) { + AutoRewident(); + } } - else // Ra 2014-02: lepiej tu niż w pętli obsługującej komendy, bo tam się zmieni informacja - // o składzie - switch (user) // gdy człowiek i gdy nastąpiło połącznie albo rozłączenie - { - case Change_direction: - while (OrderCurrentGet() & (Change_direction)) - JumpToNextOrder(); // zmianę kierunku też można olać, ale zmienić kierunek - // skanowania! + else { // gdy człowiek i gdy nastąpiło połącznie albo rozłączenie + // Ra 2014-02: lepiej tu niż w pętli obsługującej komendy, bo tam się zmieni informacja o składzie + switch (user) { + case Change_direction: { + while (OrderCurrentGet() & (Change_direction)) { + // zmianę kierunku też można olać, ale zmienić kierunek skanowania! + JumpToNextOrder(); + } break; - case Connect: - while (OrderCurrentGet() & (Change_direction)) - JumpToNextOrder(); // zmianę kierunku też można olać, ale zmienić kierunek - // skanowania! - if (OrderCurrentGet() & (Connect)) - { // jeśli miało być łączenie, zakładamy, że jest dobrze (sprawdzić?) + } + case Connect: { + while (OrderCurrentGet() & (Change_direction)) { + // zmianę kierunku też można olać, ale zmienić kierunek skanowania! + JumpToNextOrder(); + } + if (OrderCurrentGet() & (Connect)) { + // jeśli miało być łączenie, zakładamy, że jest dobrze (sprawdzić?) iCoupler = 0; // koniec z doczepianiem iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania JumpToNextOrder(); // wykonanie następnej komendy - if (OrderCurrentGet() & (Change_direction)) - JumpToNextOrder(); // zmianę kierunku też można olać, ale zmienić kierunek - // skanowania! + if (OrderCurrentGet() & (Change_direction)) { + // zmianę kierunku też można olać, ale zmienić kierunek skanowania! + JumpToNextOrder(); + } + } break; - case Disconnect: - while (OrderCurrentGet() & (Change_direction)) - JumpToNextOrder(); // zmianę kierunku też można olać, ale zmienić kierunek - // skanowania! - if (OrderCurrentGet() & (Disconnect)) - { // wypadało by sprawdzić, czy odczepiono wagony w odpowiednim miejscu - // (iVehicleCount) - JumpToNextOrder(); // wykonanie następnej komendy - if (OrderCurrentGet() & (Change_direction)) - JumpToNextOrder(); // zmianę kierunku też można olać, ale zmienić kierunek - // skanowania! - } - break; - default: - break; } + case Disconnect: { + while (OrderCurrentGet() & (Change_direction)) { + // zmianę kierunku też można olać, ale zmienić kierunek skanowania! + JumpToNextOrder(); + } + if (OrderCurrentGet() & (Disconnect)) { + // wypadało by sprawdzić, czy odczepiono wagony w odpowiednim miejscu (iVehicleCount) + JumpToNextOrder(); // wykonanie następnej komendy + if (OrderCurrentGet() & (Change_direction)) { + // zmianę kierunku też można olać, ale zmienić kierunek skanowania! + JumpToNextOrder(); + } + } + break; + } + default: { + break; + } + } // switch + } // Ra 2014-09: tymczasowo prymitywne ustawienie warunku pod kątem SN61 if( ( mvOccupied->TrainType == dt_EZT ) || ( mvOccupied->TrainType == dt_DMU ) @@ -2405,7 +2438,7 @@ bool TController::PrepareEngine() ; // zerowanie napędu mvControlling->ConvOvldFlag = false; // reset nadmiarowego } - else if (false == mvControlling->Mains) { + else if (false == IsLineBreakerClosed) { while (DecSpeed(true)) ; // zerowanie napędu if( mvOccupied->TrainType == dt_SN61 ) { @@ -2418,24 +2451,6 @@ bool TController::PrepareEngine() || ( std::max( mvControlling->GetTrainsetVoltage(), std::abs( mvControlling->RunningTraction.TractionVoltage ) ) > mvControlling->EnginePowerSource.CollectorParameters.MinV ) ) { mvControlling->MainSwitch( true ); } -/* - if (mvControlling->EngineType == DieselEngine) { - // Ra 2014-06: dla SN61 trzeba wrzucić pierwszą pozycję - nie wiem, czy tutaj... - // kiedyś działało... - if (!mvControlling->MainCtrlPos) { - if( mvControlling->RList[ 0 ].R == 0.0 ) { - // gdy na pozycji 0 dawka paliwa jest zerowa, to zgaśnie dlatego trzeba zwiększyć pozycję - mvControlling->IncMainCtrl( 1 ); - } - if( ( !mvControlling->ScndCtrlPos ) // jeśli bieg nie został ustawiony - && ( !mvControlling->MotorParam[ 0 ].AutoSwitch ) // gdy biegi ręczne - && ( mvControlling->MotorParam[ 0 ].mIsat == 0.0 ) ) { // bl,mIsat,fi,mfi - // pierwszy bieg - mvControlling->IncScndCtrl( 1 ); - } - } - } -*/ } else { OK = ( OrderDirectionChange( iDirection, mvOccupied ) == -1 ); @@ -2837,7 +2852,7 @@ bool TController::IncSpeed() auto const sufficienttractionforce { std::abs( mvControlling->Ft ) > ( IsHeavyCargoTrain ? 125 : 100 ) * 1000.0 }; auto const seriesmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn == 1 ) }; auto const parallelmodefieldshunting { ( mvControlling->ScndCtrlPos > 0 ) && ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) }; - auto const useseriesmodevoltage { 0.80 * mvControlling->EnginePowerSource.CollectorParameters.MaxV }; + auto const useseriesmodevoltage { mvControlling->EnginePowerSource.CollectorParameters.MaxV * ( IsHeavyCargoTrain ? 0.70 : 0.80 ) }; auto const useseriesmode = ( ( mvControlling->Imax > mvControlling->ImaxLo ) || ( fVoltage < useseriesmodevoltage ) @@ -3181,8 +3196,8 @@ void TController::SpeedCntrl(double DesiredSpeed) else if (mvControlling->ScndCtrlPosNo > 1) { int DesiredPos = 1 + mvControlling->ScndCtrlPosNo * ((DesiredSpeed - 1.0) / mvControlling->Vmax); - while (mvControlling->ScndCtrlPos > DesiredPos) mvControlling->DecScndCtrl(1); - while (mvControlling->ScndCtrlPos < DesiredPos) mvControlling->IncScndCtrl(1); + while( ( mvControlling->ScndCtrlPos > DesiredPos ) && ( true == mvControlling->DecScndCtrl( 1 ) ) ) { ; } // all work is done in the condition loop + while( ( mvControlling->ScndCtrlPos < DesiredPos ) && ( true == mvControlling->IncScndCtrl( 1 ) ) ) { ; } // all work is done in the condition loop } }; @@ -3195,8 +3210,6 @@ void TController::Doors( bool const Open, int const Side ) { // tu będzie jeszcze długość peronu zaokrąglona do 10m (20m bezpieczniej, bo nie modyfikuje bitu 1) auto *vehicle = pVehicles[0]; // pojazd na czole składu while( vehicle != nullptr ) { - // wagony muszą mieć baterię załączoną do otwarcia drzwi... - vehicle->MoverParameters->BatterySwitch( true ); // otwieranie drzwi w pojazdach - flaga zezwolenia była by lepsza if( vehicle->MoverParameters->DoorOpenCtrl != control_t::passenger ) { // if the door are controlled by the driver, we let the user operate them... @@ -3806,36 +3819,58 @@ TController::UpdateSituation(double dt) { TDynamicObject *p = pVehicles[0]; // pojazd na czole składu while (p) { // sprawdzenie odhamowania wszystkich połączonych pojazdów + auto *vehicle { p->MoverParameters }; if (Ready) { // bo jak coś nie odhamowane, to dalej nie ma co sprawdzać - if (p->MoverParameters->BrakePress >= 0.4) // wg UIC określone sztywno na 0.04 + if (vehicle->BrakePress >= 0.4) // wg UIC określone sztywno na 0.04 { Ready = false; // nie gotowy // Ra: odluźnianie przeładowanych lokomotyw, ciągniętych na zimno - prowizorka... if (AIControllFlag) // skład jak dotąd był wyluzowany { if( ( mvOccupied->BrakeCtrlPos == 0 ) // jest pozycja jazdy - && ( ( p->MoverParameters->Hamulec->GetBrakeStatus() & b_dmg ) == 0 ) // brake isn't broken - && ( p->MoverParameters->PipePress - 5.0 > -0.1 ) // jeśli ciśnienie jak dla jazdy - && ( p->MoverParameters->Hamulec->GetCRP() > p->MoverParameters->PipePress + 0.12 ) ) { // za dużo w zbiorniku + && ( ( vehicle->Hamulec->GetBrakeStatus() & b_dmg ) == 0 ) // brake isn't broken + && ( vehicle->PipePress - 5.0 > -0.1 ) // jeśli ciśnienie jak dla jazdy + && ( vehicle->Hamulec->GetCRP() > vehicle->PipePress + 0.12 ) ) { // za dużo w zbiorniku // indywidualne luzowanko - p->MoverParameters->BrakeReleaser( 1 ); + vehicle->BrakeReleaser( 1 ); } - if (p->MoverParameters->Power > 0.01) // jeśli ma silnik - if (p->MoverParameters->FuseFlag) // wywalony nadmiarowy - Need_TryAgain = true; // reset jak przy wywaleniu nadmiarowego } } } - if (fReady < p->MoverParameters->BrakePress) - fReady = p->MoverParameters->BrakePress; // szukanie najbardziej zahamowanego + if (fReady < vehicle->BrakePress) + fReady = vehicle->BrakePress; // szukanie najbardziej zahamowanego if( ( dy = p->VectorFront().y ) != 0.0 ) { // istotne tylko dla pojazdów na pochyleniu // ciężar razy składowa styczna grawitacji - fAccGravity -= p->MoverParameters->TotalMassxg * dy * ( p->DirectionGet() == iDirection ? 1 : -1 ); + fAccGravity -= vehicle->TotalMassxg * dy * ( p->DirectionGet() == iDirection ? 1 : -1 ); + } + if( ( vehicle->Power > 0.01 ) // jeśli ma silnik + && ( vehicle->FuseFlag ) ) { // wywalony nadmiarowy + Need_TryAgain = true; // reset jak przy wywaleniu nadmiarowego } p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła) } + + // test state of main switch in all powered vehicles under control + IsLineBreakerClosed = ( mvOccupied->Power > 0.01 ? mvOccupied->Mains : true ); + p = pVehicle; + while( ( true == IsLineBreakerClosed ) + && ( ( p = p->PrevC( coupling::control) ) != nullptr ) ) { + auto const *vehicle { p->MoverParameters }; + if( vehicle->Power > 0.01 ) { + IsLineBreakerClosed = ( IsLineBreakerClosed && vehicle->Mains ); + } + } + p = pVehicle; + while( ( true == IsLineBreakerClosed ) + && ( ( p = p->NextC( coupling::control ) ) != nullptr ) ) { + auto const *vehicle { p->MoverParameters }; + if( vehicle->Power > 0.01 ) { + IsLineBreakerClosed = ( IsLineBreakerClosed && vehicle->Mains ); + } + } + if( iDirection ) { // siłę generują pojazdy na pochyleniu ale działa ona całość składu, więc a=F/m fAccGravity *= iDirection; @@ -4066,6 +4101,12 @@ TController::UpdateSituation(double dt) { // dla nastawienia G koniecznie należy wydłużyć drogę na czas reakcji fBrakeDist += 2 * mvOccupied->Vel; } + if( ( mvOccupied->Vel > 0.05 ) + && ( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) + && ( ( mvControlling->TrainType & dt_EZT ) != 0 ) ) { + // HACK: make the induction motor powered EMUs start braking slightly earlier + fBrakeDist += 10.0; + } /* // take into account effect of gravity (but to stay on safe side of calculations, only downhill) if( fAccGravity > 0.025 ) { @@ -4722,8 +4763,8 @@ TController::UpdateSituation(double dt) { fMinProximityDist : // cars can bunch up tighter fMaxProximityDist ) ); // other vehicle types less so */ - double k = coupler->Connected->Vel; // prędkość pojazdu z przodu (zakładając, - // że jedzie w tę samą stronę!!!) + // prędkość pojazdu z przodu (zakładając, że jedzie w tę samą stronę!!!) + double k = coupler->Connected->Vel; if( k - vel < 5 ) { // porównanie modułów prędkości [km/h] // zatroszczyć się trzeba, jeśli tamten nie jedzie znacząco szybciej @@ -4731,6 +4772,26 @@ TController::UpdateSituation(double dt) { ActualProximityDist, vehicle->fTrackBlock ); + if( ActualProximityDist <= ( + ( mvOccupied->CategoryFlag & 2 ) ? + 100.0 : // cars + 250.0 ) ) { // others + // regardless of driving mode at close distance take precaution measures: + // match the other vehicle's speed or slow down if the other vehicle is stopped + VelDesired = + min_speed( + VelDesired, + std::max( + k, + ( mvOccupied->CategoryFlag & 2 ) ? + 40.0 : // cars + 20.0 ) ); // others + if( vel > VelDesired + fVelPlus ) { + // if going too fast force some prompt braking + AccPreferred = std::min( -0.65, AccPreferred ); + } + } + double const distance = vehicle->fTrackBlock - fMaxProximityDist - ( fBrakeDist * 1.15 ); // odległość bezpieczna zależy od prędkości if( distance < 0.0 ) { // jeśli odległość jest zbyt mała @@ -4780,7 +4841,7 @@ TController::UpdateSituation(double dt) { } } ReactionTime = ( - vel != 0.0 ? + mvOccupied->Vel > 0.01 ? 0.1 : // orientuj się, bo jest goraco 2.0 ); // we're already standing still, so take it easy } @@ -5198,10 +5259,10 @@ TController::UpdateSituation(double dt) { return; // ...and don't touch any other controls } - if (mvControlling->ConvOvldFlag || - !mvControlling->Mains) // WS może wywalić z powodu błędu w drutach - { // wywalił bezpiecznik nadmiarowy przetwornicy - PrepareEngine(); // próba ponownego załączenia + if( ( true == mvControlling->ConvOvldFlag ) // wywalił bezpiecznik nadmiarowy przetwornicy + || ( false == IsLineBreakerClosed ) ) { // WS może wywalić z powodu błędu w drutach + // próba ponownego załączenia + PrepareEngine(); } // włączanie bezpiecznika if ((mvControlling->EngineType == TEngineType::ElectricSeriesMotor) || @@ -5763,7 +5824,7 @@ void TController::OrdersInit(double fVel) // Ale mozna by je zapodac ze scenerii }; -std::string TController::StopReasonText() +std::string TController::StopReasonText() const { // informacja tekstowa o przyczynie zatrzymania if (eStopReason != 7) // zawalidroga będzie inaczej return StopReasonTable[eStopReason]; diff --git a/Driver.h b/Driver.h index c0f50fbf..9140a899 100644 --- a/Driver.h +++ b/Driver.h @@ -129,12 +129,7 @@ class TSpeedPos double fDist{ 0.0 }; // aktualna odległość (ujemna gdy minięte) double fVelNext{ -1.0 }; // prędkość obowiązująca od tego miejsca double fSectionVelocityDist{ 0.0 }; // długość ograniczenia prędkości - // double fAcc; int iFlags{ spNone }; // flagi typu wpisu do tabelki - // 1=istotny,2=tor,4=odwrotnie,8-zwrotnica (może się zmienić),16-stan - // zwrotnicy,32-minięty,64=koniec,128=łuk - // 0x100=event,0x200=manewrowa,0x400=przystanek,0x800=SBL,0x1000=wysłana komenda,0x2000=W5 - // 0x4000=semafor,0x10000=zatkanie bool bMoved{ false }; // czy przesunięty (dotyczy punktu zatrzymania w peronie) Math3D::vector3 vPos; // współrzędne XYZ do liczenia odległości struct @@ -174,88 +169,76 @@ static const int BrakeAccTableSize = 20; //---------------------------------------------------------------------------- class TController { + // TBD: few authorized inspectors, or bunch of public getters? + friend class TTrain; + friend class drivingaid_panel; + friend class timetable_panel; + friend class debug_panel; + friend class whois_event; - private: // obsługa tabelki prędkości (musi mieć możliwość odhaczania stacji w rozkładzie) - int iLast{ 0 }; // ostatnia wypełniona pozycja w tabeli sSpeedTable; - double fLastVel = 0.0; // prędkość na poprzednio sprawdzonym torze - TTrack *tLast = nullptr; // ostatni analizowany tor - basic_event *eSignSkip = nullptr; // można pominąć ten SBL po zatrzymaniu - std::size_t SemNextIndex{ std::size_t(-1) }; - std::size_t SemNextStopIndex{ std::size_t( -1 ) }; - double dMoveLen = 0.0; // odległość przejechana od ostatniego sprawdzenia tabelki - // parametry aktualnego składu - double fLength = 0.0; // długość składu (do wyciągania z ograniczeń) - double fMass = 0.0; // całkowita masa do liczenia stycznej składowej grawitacji public: - double fAccGravity = 0.0; // przyspieszenie składowej stycznej grawitacji - basic_event *eSignNext = nullptr; // sygnał zmieniający prędkość, do pokazania na [F2] - std::string asNextStop; // nazwa następnego punktu zatrzymania wg rozkładu - int iStationStart = 0; // numer pierwszej stacji pokazywanej na podglądzie rozkładu - // parametry sterowania pojazdem (stan, hamowanie) - private: - double fShuntVelocity = 40.0; // maksymalna prędkość manewrowania, zależy m.in. od składu // domyślna prędkość manewrowa - int iVehicles = 0; // ilość pojazdów w składzie - int iEngineActive = 0; // ABu: Czy silnik byl juz zalaczony; Ra: postęp w załączaniu - bool Psyche = false; - int iDrivigFlags = // flagi bitowe ruchu - moveStopPoint | // podjedź do W4 możliwie blisko - moveStopHere | // nie podjeżdżaj do semafora, jeśli droga nie jest wolna - moveStartHorn; // podaj sygnał po podaniu wolnej drogi - double fDriverBraking = 0.0; // po pomnożeniu przez v^2 [km/h] daje ~drogę hamowania [m] - double fDriverDist = 0.0; // dopuszczalna odległość podjechania do przeszkody - double fVelMax = -1.0; // maksymalna prędkość składu (sprawdzany każdy pojazd) - public: - double fBrakeDist = 0.0; // przybliżona droga hamowania - double BrakeAccFactor() const; - double fBrakeReaction = 1.0; //opóźnienie zadziałania hamulca - czas w s / (km/h) - double fNominalAccThreshold = 0.0; // nominalny próg opóźnienia dla zadziałania hamulca - double fAccThreshold = 0.0; // aktualny próg opóźnienia dla zadziałania hamulca - double AbsAccS_pub = 0.0; // próg opóźnienia dla zadziałania hamulca - // dla fBrake_aX: - // indeks [0] - wartości odpowiednie dla aktualnej prędkości - // a potem jest 20 wartości dla różnych prędkości zmieniających się co 5 % Vmax pojazdu obsadzonego - double fBrake_a0[BrakeAccTableSize+1] = { 0.0 }; // opóźnienia hamowania przy ustawieniu zaworu maszynisty w pozycji 1.0 - double fBrake_a1[BrakeAccTableSize+1] = { 0.0 }; // przyrost opóźnienia hamowania po przestawieniu zaworu maszynisty o 0,25 pozycji - double BrakingInitialLevel{ 1.0 }; - double BrakingLevelIncrease{ 0.25 }; - bool IsCargoTrain{ false }; - bool IsHeavyCargoTrain{ false }; - double fLastStopExpDist = -1.0; // odległość wygasania ostateniego przystanku - double ReactionTime = 0.0; // czas reakcji Ra: czego i na co? świadomości AI - double fBrakeTime = 0.0; // wpisana wartość jest zmniejszana do 0, gdy ujemna należy zmienić nastawę hamulca - double BrakeChargingCooldown {}; // prevents the ai from trying to charge the train brake too frequently - double fReady = 0.0; // poziom odhamowania wagonów - bool Ready = false; // ABu: stan gotowosci do odjazdu - sprawdzenie odhamowania wagonow -private: - double LastUpdatedTime = 0.0; // czas od ostatniego logu - double ElapsedTime = 0.0; // czas od poczatku logu - double deltalog = 0.05; // przyrost czasu - double LastReactionTime = 0.0; - double fActionTime = 0.0; // czas używany przy regulacji prędkości i zamykaniu drzwi - double m_radiocontroltime{ 0.0 }; // timer used to control speed of radio operations - TAction eAction { TAction::actUnknown }; // aktualny stan - public: + TController( bool AI, TDynamicObject *NewControll, bool InitPsyche, bool primary = true ); + ~TController(); + +// ai operations logic +// methods +public: + void UpdateSituation(double dt); // uruchamiac przynajmniej raz na sekundę + void MoveTo(TDynamicObject *to); + void TakeControl(bool yes); + inline + bool Primary() const { + return ( ( iDrivigFlags & movePrimary ) != 0 ); }; + inline + TMoverParameters const *Controlling() const { + return mvControlling; } + void DirectionInitial(); + inline + int Direction() const { + return iDirection; } inline TAction GetAction() { return eAction; } +private: + void Activation(); // umieszczenie obsady w odpowiednim członie + void ControllingSet(); // znajduje człon do sterowania + void SetDriverPsyche(); + bool IncBrake(); + bool DecBrake(); + bool IncSpeed(); + bool DecSpeed(bool force = false); + void SpeedSet(); + void SpeedCntrl(double DesiredSpeed); + double ESMVelocity(bool Main); + bool UpdateHeating(); + // uaktualnia informacje o prędkości + void SetVelocity(double NewVel, double NewVelNext, TStopReason r = stopNone); + int CheckDirection(); + void WaitingSet(double Seconds); + void DirectionForward(bool forward); + int OrderDirectionChange(int newdir, TMoverParameters *Vehicle); + void Lights(int head, int rear); + std::string StopReasonText() const; + double BrakeAccFactor() const; + // modifies brake distance for low target speeds, to ease braking rate in such situations + float + braking_distance_multiplier( float const Targetvelocity ) const; + inline + int DrivigFlags() const { + return iDrivigFlags; }; +// members +public: bool AIControllFlag = false; // rzeczywisty/wirtualny maszynista -/* - int iRouteWanted = 3; // oczekiwany kierunek jazdy (0-stop,1-lewo,2-prawo,3-prosto) np. odpala migacz lub czeka na stan zwrotnicy -*/ - private: + int iOverheadZero = 0; // suma bitowa jezdy bezprądowej, bity ustawiane przez pojazdy z podniesionymi pantografami + int iOverheadDown = 0; // suma bitowa opuszczenia pantografów, bity ustawiane przez pojazdy z podniesionymi pantografami +private: + bool Psyche = false; + int HelperState = 0; //stan pomocnika maszynisty TDynamicObject *pVehicle = nullptr; // pojazd w którym siedzi sterujący - TDynamicObject *pVehicles[2]; // skrajne pojazdy w składzie (niekoniecznie bezpośrednio sterowane) TMoverParameters *mvControlling = nullptr; // jakim pojazdem steruje (może silnikowym w EZT) TMoverParameters *mvOccupied = nullptr; // jakim pojazdem hamuje - Mtable::TTrainParameters *TrainParams = nullptr; // rozkład jazdy zawsze jest, nawet jeśli pusty - int iRadioChannel = 1; // numer aktualnego kanału radiowego - int iGuardRadio = 0; // numer kanału radiowego kierownika (0, gdy nie używa radia) - sound_source tsGuardSignal { sound_placement::internal }; + std::string VehicleName; std::array m_lighthints { -1 }; // suggested light patterns - public: - int HelperState = 0; //stan pomocnika maszynisty double AccPreferred = 0.0; // preferowane przyspieszenie (wg psychiki kierującego, zmniejszana przy wykryciu kolizji) double AccDesired = AccPreferred; // przyspieszenie, jakie ma utrzymywać (<0:nie przyspieszaj,<-0.1:hamuj) double VelDesired = 0.0; // predkość, z jaką ma jechać, wynikająca z analizy tableki; <=VelSignal @@ -269,18 +252,8 @@ private: double VelRoad = -1.0; // aktualna prędkość drogowa (ze znaku W27) (PutValues albo komendą) // prędkość drogowa bez ograniczenia double VelNext = 120.0; // prędkość, jaka ma być po przejechaniu długości ProximityDist double VelRestricted = -1.0; // speed of travel after passing a permissive signal at stop - private: double FirstSemaphorDist = 10000.0; // odległość do pierwszego znalezionego semafora - public: double ActualProximityDist = 1.0; // odległość brana pod uwagę przy wyliczaniu prędkości i przyspieszenia - private: - Math3D::vector3 vCommandLocation; // polozenie wskaznika, sygnalizatora lub innego obiektu do ktorego - // odnosi sie komenda - TOrders OrderList[maxorders]; // lista rozkazów - int OrderPos = 0, - OrderTop = 0; // rozkaz aktualny oraz wolne miejsce do wstawiania nowych - std::ofstream LogFile; // zapis parametrow fizycznych - std::ofstream AILogFile; // log AI int iDirection = 0; // kierunek jazdy względem sprzęgów pojazdu, w którym siedzi AI (1=przód,-1=tył) int iDirectionOrder = 0; //żadany kierunek jazdy (służy do zmiany kierunku) int iVehicleCount = -2; // wartość neutralna // ilość pojazdów do odłączenia albo zabrania ze składu (-1=wszystkie) @@ -288,146 +261,187 @@ private: int iDriverFailCount = 0; // licznik błędów AI bool Need_TryAgain = false; // true, jeśli druga pozycja w elektryku nie załapała bool Need_BrakeRelease = true; - - public: + bool IsAtPassengerStop{ false }; // true if the consist is within acceptable range of w4 post double fMinProximityDist = 30.0; // stawanie między 30 a 60 m przed przeszkodą // minimalna oległość do przeszkody, jaką należy zachować double fOverhead1 = 3000.0; // informacja o napięciu w sieci trakcyjnej (0=brak drutu, zatrzymaj!) double fOverhead2 = -1.0; // informacja o sposobie jazdy (-1=normalnie, 0=bez prądu, >0=z opuszczonym i ograniczeniem prędkości) - int iOverheadZero = 0; // suma bitowa jezdy bezprądowej, bity ustawiane przez pojazdy z podniesionymi pantografami - int iOverheadDown = 0; // suma bitowa opuszczenia pantografów, bity ustawiane przez pojazdy z podniesionymi pantografami double fVoltage = 0.0; // uśrednione napięcie sieci: przy spadku poniżej wartości minimalnej opóźnić rozruch o losowy czas - private: double fMaxProximityDist = 50.0; // stawanie między 30 a 60 m przed przeszkodą // akceptowalna odległość stanięcia przed przeszkodą TStopReason eStopReason = stopSleep; // powód zatrzymania przy ustawieniu zerowej prędkości // na początku śpi - std::string VehicleName; double fVelPlus = 0.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania double fVelMinus = 0.0; // margines obniżenia prędkości, powodujący załączenie napędu double fWarningDuration = 0.0; // ile czasu jeszcze trąbić double WaitingTime = 0.0; // zliczany czas oczekiwania do samoistnego ruszenia double WaitingExpireTime = 31.0; // tyle ma czekać, zanim się ruszy // maksymlany czas oczekiwania do samoistnego ruszenia double IdleTime {}; // keeps track of time spent at a stop - public: double fStopTime = 0.0; // czas postoju przed dalszą jazdą (np. na przystanku) + double fShuntVelocity = 40.0; // maksymalna prędkość manewrowania, zależy m.in. od składu // domyślna prędkość manewrowa + int iDrivigFlags = // flagi bitowe ruchu + moveStopPoint | // podjedź do W4 możliwie blisko + moveStopHere | // nie podjeżdżaj do semafora, jeśli droga nie jest wolna + moveStartHorn; // podaj sygnał po podaniu wolnej drogi + double fDriverBraking = 0.0; // po pomnożeniu przez v^2 [km/h] daje ~drogę hamowania [m] + double fDriverDist = 0.0; // dopuszczalna odległość podjechania do przeszkody + double fVelMax = -1.0; // maksymalna prędkość składu (sprawdzany każdy pojazd) + double fBrakeDist = 0.0; // przybliżona droga hamowania + double fBrakeReaction = 1.0; //opóźnienie zadziałania hamulca - czas w s / (km/h) + double fNominalAccThreshold = 0.0; // nominalny próg opóźnienia dla zadziałania hamulca + double fAccThreshold = 0.0; // aktualny próg opóźnienia dla zadziałania hamulca + double AbsAccS_pub = 0.0; // próg opóźnienia dla zadziałania hamulca + // dla fBrake_aX: + // indeks [0] - wartości odpowiednie dla aktualnej prędkości + // a potem jest 20 wartości dla różnych prędkości zmieniających się co 5 % Vmax pojazdu obsadzonego + double fBrake_a0[BrakeAccTableSize+1] = { 0.0 }; // opóźnienia hamowania przy ustawieniu zaworu maszynisty w pozycji 1.0 + double fBrake_a1[BrakeAccTableSize+1] = { 0.0 }; // przyrost opóźnienia hamowania po przestawieniu zaworu maszynisty o 0,25 pozycji + double BrakingInitialLevel{ 1.0 }; + double BrakingLevelIncrease{ 0.25 }; + double ReactionTime = 0.0; // czas reakcji Ra: czego i na co? świadomości AI + double fBrakeTime = 0.0; // wpisana wartość jest zmniejszana do 0, gdy ujemna należy zmienić nastawę hamulca + double BrakeChargingCooldown {}; // prevents the ai from trying to charge the train brake too frequently + double LastReactionTime = 0.0; + double fActionTime = 0.0; // czas używany przy regulacji prędkości i zamykaniu drzwi + double m_radiocontroltime{ 0.0 }; // timer used to control speed of radio operations + TAction eAction { TAction::actUnknown }; // aktualny stan - private: //---//---//---//---// koniec zmiennych, poniżej metody //---//---//---//---// - void SetDriverPsyche(); - bool PrepareEngine(); - bool ReleaseEngine(); - bool IncBrake(); - bool DecBrake(); - bool IncSpeed(); - bool DecSpeed(bool force = false); - void SpeedSet(); - void SpeedCntrl(double DesiredSpeed); - void Doors(bool const Open, int const Side = 0); - // returns true if any vehicle in the consist has an open door - bool doors_open() const; - void RecognizeCommand(); // odczytuje komende przekazana lokomotywie - void Activation(); // umieszczenie obsady w odpowiednim członie - void ControllingSet(); // znajduje człon do sterowania - void AutoRewident(); // ustawia hamulce w składzie - double ESMVelocity(bool Main); - public: +// orders +// methods +public: void PutCommand(std::string NewCommand, double NewValue1, double NewValue2, const TLocation &NewLocation, TStopReason reason = stopComm); bool PutCommand( std::string NewCommand, double NewValue1, double NewValue2, glm::dvec3 const *NewLocation, TStopReason reason = stopComm ); - void UpdateSituation(double dt); // uruchamiac przynajmniej raz na sekundę - bool UpdateHeating(); - // procedury dotyczace rozkazow dla maszynisty - // uaktualnia informacje o prędkości - void SetVelocity(double NewVel, double NewVelNext, TStopReason r = stopNone); - public: +private: + void RecognizeCommand(); // odczytuje komende przekazana lokomotywie void JumpToNextOrder(); void JumpToFirstOrder(); void OrderPush(TOrders NewOrder); void OrderNext(TOrders NewOrder); inline TOrders OrderCurrentGet(); inline TOrders OrderNextGet(); - bool CheckVehicles(TOrders user = Wait_for_orders); - int CheckDirection(); - - private: - void CloseLog(); void OrderCheck(); - - public: void OrdersInit(double fVel); void OrdersClear(); void OrdersDump(); - TController( bool AI, TDynamicObject *NewControll, bool InitPsyche, bool primary = true ); std::string OrderCurrent() const; - void WaitingSet(double Seconds); - - private: std::string Order2Str(TOrders Order) const; - void DirectionForward(bool forward); - int OrderDirectionChange(int newdir, TMoverParameters *Vehicle); - void Lights(int head, int rear); +// members + Math3D::vector3 vCommandLocation; // polozenie wskaznika, sygnalizatora lub innego obiektu do ktorego odnosi sie komenda // NOTE: not used + TOrders OrderList[ maxorders ]; // lista rozkazów + int OrderPos = 0, + OrderTop = 0; // rozkaz aktualny oraz wolne miejsce do wstawiania nowych + +// scantable +// methods +public: + int CrossRoute(TTrack *tr); + inline void MoveDistanceAdd( double distance ) { + dMoveLen += distance * iDirection; } //jak jedzie do tyłu to trzeba uwzględniać, że distance jest ujemna +private: // Ra: metody obsługujące skanowanie toru - std::vector CheckTrackEvent(TTrack *Track, double const fDirection ) const; + std::vector CheckTrackEvent( TTrack *Track, double const fDirection ) const; bool TableAddNew(); - bool TableNotFound(basic_event const *Event) const; - void TableTraceRoute(double fDistance, TDynamicObject *pVehicle); - void TableCheck(double fDistance); - TCommandType TableUpdate(double &fVelDes, double &fDist, double &fNext, double &fAcc); - // modifies brake distance for low target speeds, to ease braking rate in such situations - float - braking_distance_multiplier( float const Targetvelocity ) const; + bool TableNotFound( basic_event const *Event ) const; + void TableTraceRoute( double fDistance, TDynamicObject *pVehicle ); + void TableCheck( double fDistance ); + TCommandType TableUpdate( double &fVelDes, double &fDist, double &fNext, double &fAcc ); + // returns most recently calculated distance to potential obstacle ahead + double TrackBlock() const; void TablePurger(); void TableSort(); inline double MoveDistanceGet() const { - return dMoveLen; } + return dMoveLen; + } inline void MoveDistanceReset() { - dMoveLen = 0.0; } - public: - inline void MoveDistanceAdd(double distance) { - dMoveLen += distance * iDirection; //jak jedzie do tyłu to trzeba uwzględniać, że distance jest ujemna + dMoveLen = 0.0; } std::size_t TableSize() const { return sSpeedTable.size(); } void TableClear(); int TableDirection() { return iTableDirection; } - - private: // Ra: stare funkcje skanujące, używane do szukania sygnalizatora z tyłu - bool BackwardTrackBusy(TTrack *Track); - basic_event *CheckTrackEventBackward(double fDirection, TTrack *Track); - TTrack *BackwardTraceRoute(double &fDistance, double &fDirection, TTrack *Track, basic_event *&Event); + // Ra: stare funkcje skanujące, używane do szukania sygnalizatora z tyłu + bool BackwardTrackBusy( TTrack *Track ); + basic_event *CheckTrackEventBackward( double fDirection, TTrack *Track ); + TTrack *BackwardTraceRoute( double &fDistance, double &fDirection, TTrack *Track, basic_event *&Event ); void SetProximityVelocity( double dist, double vel, glm::dvec3 const *pos ); TCommandType BackwardScan(); - - public: - void PhysicsLog(); - std::string StopReasonText(); - ~TController(); - void TakeControl(bool yes); - Mtable::TTrainParameters const * TrainTimetable() const; - std::string TrainName() const; - std::string Relation() const; - int StationCount() const; - int StationIndex() const; - bool IsStop() const; - std::string NextStop() const; - inline - bool Primary() const { - return ( ( iDrivigFlags & movePrimary ) != 0 ); }; - inline - int DrivigFlags() const { - return iDrivigFlags; }; - // returns most recently calculated distance to potential obstacle ahead - double - TrackBlock() const; - void MoveTo(TDynamicObject *to); - void DirectionInitial(); std::string TableText(std::size_t const Index) const; - int CrossRoute(TTrack *tr); /* void RouteSwitch(int d); */ - std::string OwnerName() const; - TMoverParameters const *Controlling() const { - return mvControlling; } - int Direction() const { - return iDirection; } +// members + int iLast{ 0 }; // ostatnia wypełniona pozycja w tabeli sSpeedTable; + double fLastVel = 0.0; // prędkość na poprzednio sprawdzonym torze + TTrack *tLast = nullptr; // ostatni analizowany tor + basic_event *eSignSkip = nullptr; // można pominąć ten SBL po zatrzymaniu + std::size_t SemNextIndex{ std::size_t(-1) }; + std::size_t SemNextStopIndex{ std::size_t( -1 ) }; + double dMoveLen = 0.0; // odległość przejechana od ostatniego sprawdzenia tabelki + basic_event *eSignNext = nullptr; // sygnał zmieniający prędkość, do pokazania na [F2] + +// timetable +// methods +public: + std::string TrainName() const; +private: + std::string Relation() const; + Mtable::TTrainParameters const * TrainTimetable() const; + int StationIndex() const; + int StationCount() const; + bool IsStop() const; + std::string NextStop() const; +// members + Mtable::TTrainParameters *TrainParams = nullptr; // rozkład jazdy zawsze jest, nawet jeśli pusty + std::string asNextStop; // nazwa następnego punktu zatrzymania wg rozkładu + int iStationStart = 0; // numer pierwszej stacji pokazywanej na podglądzie rozkładu + double fLastStopExpDist = -1.0; // odległość wygasania ostateniego przystanku + int iRadioChannel = 1; // numer aktualnego kanału radiowego + int iGuardRadio = 0; // numer kanału radiowego kierownika (0, gdy nie używa radia) + sound_source tsGuardSignal { sound_placement::internal }; + +// consist +// methods +public: +private: + bool CheckVehicles(TOrders user = Wait_for_orders); + bool PrepareEngine(); + bool ReleaseEngine(); + void Doors(bool const Open, int const Side = 0); + // returns true if any vehicle in the consist has an open door + bool doors_open() const; + void AutoRewident(); // ustawia hamulce w składzie +// members + double fLength = 0.0; // długość składu (do wyciągania z ograniczeń) + double fMass = 0.0; // całkowita masa do liczenia stycznej składowej grawitacji + double fAccGravity = 0.0; // przyspieszenie składowej stycznej grawitacji + int iVehicles = 0; // ilość pojazdów w składzie + int iEngineActive = 0; // ABu: Czy silnik byl juz zalaczony; Ra: postęp w załączaniu + bool IsCargoTrain{ false }; + bool IsHeavyCargoTrain{ false }; + bool IsLineBreakerClosed{ false }; // state of line breaker in all powered vehicles under control + double fReady = 0.0; // poziom odhamowania wagonów + bool Ready = false; // ABu: stan gotowosci do odjazdu - sprawdzenie odhamowania wagonow + TDynamicObject *pVehicles[ 2 ]; // skrajne pojazdy w składzie (niekoniecznie bezpośrednio sterowane) + +// logs +// methods + void PhysicsLog(); + void CloseLog(); +// members + std::ofstream LogFile; // zapis parametrow fizycznych + double LastUpdatedTime = 0.0; // czas od ostatniego logu + double ElapsedTime = 0.0; // czas od poczatku logu + +// getters +public: TDynamicObject const *Vehicle() const { return pVehicle; } TDynamicObject *Vehicle( side const Side ) const { return pVehicles[ Side ]; } +private: + std::string OwnerName() const; + +// leftovers +/* + int iRouteWanted = 3; // oczekiwany kierunek jazdy (0-stop,1-lewo,2-prawo,3-prosto) np. odpala migacz lub czeka na stan zwrotnicy +*/ + }; diff --git a/DynObj.cpp b/DynObj.cpp index e1ebd18d..b8dc7be7 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -619,8 +619,12 @@ void TDynamicObject::toggle_lights() { if( true == SectionLightsActive ) { - // switch all lights off + // switch all lights off... for( auto §ion : Sections ) { + // ... but skip cab sections, their lighting ignores battery state + auto const sectionname { section.compartment->pName }; + if( sectionname.find( "cab" ) == 0 ) { continue; } + section.light_level = 0.0f; } SectionLightsActive = false; @@ -647,16 +651,14 @@ TDynamicObject::toggle_lights() { } void -TDynamicObject::set_cab_lights( float const Level ) { - - if( Level == InteriorLightLevel ) { return; } - - InteriorLightLevel = Level; +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; } @@ -984,7 +986,8 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) btnOn = true; } - if( ( Mechanik != nullptr ) + 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->GetAction() != TAction::actSleep ) || ( MoverParameters->Battery ) ) ) { // rysowanie figurki mechanika @@ -1039,10 +1042,19 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) // else btHeadSignals23.TurnOff(); } // interior light levels + auto sectionlightcolor { glm::vec4( 1.f ) }; for( auto const §ion : Sections ) { - section.compartment->SetLightLevel( section.light_level, true ); + /* + sectionlightcolor = glm::vec4( InteriorLight, section.light_level ); + */ + sectionlightcolor = glm::vec4( + ( ( ( section.light_level == 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 + section.light_level ); + section.compartment->SetLightLevel( sectionlightcolor, true ); if( section.load != nullptr ) { - section.load->SetLightLevel( section.light_level, true ); + section.load->SetLightLevel( sectionlightcolor, true ); } } // load chunks visibility @@ -1061,7 +1073,17 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) 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 ************************************* @@ -1355,8 +1377,6 @@ TDynamicObject::couple( int const Side ) { Side, 2, MoverParameters->Couplers[ Side ].Connected, coupling::coupler ) ) { - // tmp->MoverParameters->Couplers[CouplNr].Render=true; //podłączony sprzęg będzie widoczny - m_couplersounds[ Side ].dsbCouplerAttach.play(); // one coupling type per key press return; } @@ -1374,9 +1394,6 @@ TDynamicObject::couple( int const Side ) { Side, 2, MoverParameters->Couplers[ Side ].Connected, ( MoverParameters->Couplers[ Side ].CouplingFlag | coupling::brakehose ) ) ) { - // TODO: dedicated sound for connecting cable-type connections - m_couplersounds[ Side ].dsbCouplerDetach.play(); - SetPneumatic( Side != 0, true ); if( Side == side::front ) { PrevConnected->SetPneumatic( Side != 0, true ); @@ -1398,9 +1415,6 @@ TDynamicObject::couple( int const Side ) { Side, 2, MoverParameters->Couplers[ Side ].Connected, ( MoverParameters->Couplers[ Side ].CouplingFlag | coupling::mainhose ) ) ) { - // TODO: dedicated sound for connecting cable-type connections - m_couplersounds[ Side ].dsbCouplerDetach.play(); - SetPneumatic( Side != 0, false ); if( Side == side::front ) { PrevConnected->SetPneumatic( Side != 0, false ); @@ -1422,8 +1436,6 @@ TDynamicObject::couple( int const Side ) { Side, 2, MoverParameters->Couplers[ Side ].Connected, ( MoverParameters->Couplers[ Side ].CouplingFlag | coupling::control ) ) ) { - // TODO: dedicated sound for connecting cable-type connections - m_couplersounds[ Side ].dsbCouplerAttach.play(); // one coupling type per key press return; } @@ -1438,8 +1450,6 @@ TDynamicObject::couple( int const Side ) { Side, 2, MoverParameters->Couplers[ Side ].Connected, ( MoverParameters->Couplers[ Side ].CouplingFlag | coupling::gangway ) ) ) { - // TODO: dedicated gangway sound - m_couplersounds[ Side ].dsbCouplerAttach.play(); // one coupling type per key press return; } @@ -1454,9 +1464,6 @@ TDynamicObject::couple( int const Side ) { Side, 2, MoverParameters->Couplers[ Side ].Connected, ( MoverParameters->Couplers[ Side ].CouplingFlag | coupling::heating ) ) ) { - - // TODO: dedicated 'click' sound for connecting cable-type connections - m_couplersounds[ Side ].dsbCouplerDetach.play(); // one coupling type per key press return; } @@ -1475,11 +1482,6 @@ TDynamicObject::uncouple( int const Side ) { } // jeżeli sprzęg niezablokowany, jest co odczepić i się da auto const couplingflag { Dettach( Side ) }; - if( couplingflag == coupling::faux ) { - // dźwięk odczepiania - m_couplersounds[ Side ].dsbCouplerAttach.play(); - m_couplersounds[ Side ].dsbCouplerDetach.play(); - } return couplingflag; } @@ -2183,9 +2185,18 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" // 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( "cab1" ) ) != nullptr ) { Sections.push_back( { submodel, nullptr, 0.0f } ); } - if( ( submodel = mdLowPolyInt->GetFromName( "cab2" ) ) != nullptr ) { Sections.push_back( { submodel, nullptr, 0.0f } ); } - if( ( submodel = mdLowPolyInt->GetFromName( "cab0" ) ) != nullptr ) { Sections.push_back( { submodel, nullptr, 0.0f } ); } + if( ( submodel = mdLowPolyInt->GetFromName( "cab0" ) ) != nullptr ) { + Sections.push_back( { submodel, nullptr, 0.0f } ); + LowPolyIntCabs[ 0 ] = submodel; + } + if( ( submodel = mdLowPolyInt->GetFromName( "cab1" ) ) != nullptr ) { + Sections.push_back( { submodel, nullptr, 0.0f } ); + LowPolyIntCabs[ 1 ] = submodel; + } + if( ( submodel = mdLowPolyInt->GetFromName( "cab2" ) ) != nullptr ) { + Sections.push_back( { submodel, nullptr, 0.0f } ); + LowPolyIntCabs[ 2 ] = submodel; + } // passenger car compartments std::vector nameprefixes = { "corridor", "korytarz", "compartment", "przedzial" }; for( auto const &nameprefix : nameprefixes ) { @@ -2536,9 +2547,9 @@ void TDynamicObject::AttachPrev(TDynamicObject *Object, int iType) loc.Z=Object->vPosition.y; Object->MoverParameters->Loc=loc; //ustawienie dodawanego pojazdu */ - MoverParameters->Attach(iDirection, Object->iDirection ^ 1, Object->MoverParameters, iType, true); + MoverParameters->Attach(iDirection, Object->iDirection ^ 1, Object->MoverParameters, iType, true, false); MoverParameters->Couplers[iDirection].Render = false; - Object->MoverParameters->Attach(Object->iDirection ^ 1, iDirection, MoverParameters, iType, true); + Object->MoverParameters->Attach(Object->iDirection ^ 1, iDirection, MoverParameters, iType, true, false); Object->MoverParameters->Couplers[Object->iDirection ^ 1].Render = true; // rysowanie sprzęgu w dołączanym if (iDirection) { //łączenie standardowe @@ -2607,13 +2618,39 @@ void TDynamicObject::LoadExchange( int const Disembark, int const Embark, int co } m_exchange.unload_count += Disembark; m_exchange.load_count += Embark; - m_exchange.speed_factor = ( - Platform == 3 ? - 2.0 : - 1.0 ); + 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->DoorLeftOpened ? 1.f : 0.f ); + } + if( m_exchange.platforms & prawe ) { + exchangespeedfactor += ( MoverParameters->DoorRightOpened ? 1.f : 0.f ); + } + + return exchangespeedfactor; +} + // update state of load exchange operation void TDynamicObject::update_exchange( double const Deltatime ) { @@ -2631,7 +2668,7 @@ void TDynamicObject::update_exchange( double const Deltatime ) { && ( m_exchange.time >= 1.0 ) ) { m_exchange.time -= 1.0; - auto const exchangesize = std::min( m_exchange.unload_count, MoverParameters->UnLoadSpeed * m_exchange.speed_factor ); + auto const exchangesize = std::min( m_exchange.unload_count, MoverParameters->UnLoadSpeed * LoadExchangeSpeed() ); m_exchange.unload_count -= exchangesize; MoverParameters->LoadStatus = 1; MoverParameters->LoadAmount = std::max( 0.f, MoverParameters->LoadAmount - exchangesize ); @@ -2645,7 +2682,7 @@ void TDynamicObject::update_exchange( double const Deltatime ) { && ( m_exchange.time >= 1.0 ) ) { m_exchange.time -= 1.0; - auto const exchangesize = std::min( m_exchange.load_count, MoverParameters->LoadSpeed * m_exchange.speed_factor ); + auto const exchangesize = std::min( m_exchange.load_count, MoverParameters->LoadSpeed * LoadExchangeSpeed() ); m_exchange.load_count -= exchangesize; MoverParameters->LoadStatus = 2; MoverParameters->LoadAmount = std::min( MoverParameters->MaxLoad, MoverParameters->LoadAmount + exchangesize ); // std::max not strictly needed but, eh @@ -4464,6 +4501,11 @@ void TDynamicObject::RenderSounds() { 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 ) ) { @@ -4488,6 +4530,7 @@ void TDynamicObject::RenderSounds() { .play( sound_flags::exclusive ); } } + if( true == TestFlag( coupler.sounds, sound::couplerstretch ) ) { // sprzegi sie rozciagaja if( true == TestFlag( coupler.sounds, sound::loud ) ) { @@ -4513,8 +4556,22 @@ void TDynamicObject::RenderSounds() { } } - coupler.sounds = 0; + // 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(); + } + ++couplerindex; + coupler.sounds = 0; } MoverParameters->SoundFlag = 0; @@ -5916,6 +5973,10 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co >> HuntingShake.fadein_end; } + else if( token == "jointcabs:" ) { + parser.getTokens(); + parser >> JointCabs; + } } while( token != "" ); diff --git a/DynObj.h b/DynObj.h index 239ce2f1..c2c103f0 100644 --- a/DynObj.h +++ b/DynObj.h @@ -201,6 +201,8 @@ public: TModel3d *mdLoad; // model zmiennego ładunku TModel3d *mdKabina; // model kabiny dla użytkownika; McZapkie-030303: to z train.h TModel3d *mdLowPolyInt; // ABu 010305: wnetrze lowpoly + std::array LowPolyIntCabs {}; // pointers to low fidelity version of individual driver cabs + bool JointCabs{ false }; // flag for vehicles with multiple virtual 'cabs' sharing location and 3d model(s) float fShade; // zacienienie: 0:normalnie, -1:w ciemności, +1:dodatkowe światło (brak koloru?) float LoadOffset { 0.f }; glm::vec3 InteriorLight { 0.9f * 255.f / 255.f, 0.9f * 216.f / 255.f, 0.9f * 176.f / 255.f }; // tungsten light. TODO: allow definition of light type? @@ -290,7 +292,7 @@ private: struct exchange_data { float unload_count { 0.f }; // amount to unload float load_count { 0.f }; // amount to load - float speed_factor { 1.f }; // operation speed modifier + int platforms { 0 }; // platforms which may take part in the exchange float time { 0.f }; // time spent on the operation }; @@ -530,6 +532,10 @@ private: bool UpdateForce(double dt, double dt1, bool FullVer); // initiates load change by specified amounts, with a platform on specified side void LoadExchange( int const Disembark, int const Embark, int const Platform ); + // calculates time needed to complete current load change + float LoadExchangeTime() const; + // calculates current load exchange factor, where 1 = nominal rate, higher = faster + float LoadExchangeSpeed() const; // TODO: make private when cleaning up void LoadUpdate(); void update_load_sections(); void update_load_visibility(); @@ -619,7 +625,7 @@ private: void Damage(char flag); void RaLightsSet(int head, int rear); int LightList( side const Side ) const { return iInventory[ Side ]; } - void set_cab_lights( float const Level ); + void set_cab_lights( int const Cab, float const Level ); TDynamicObject * FirstFind(int &coupler_nr, int cf = 1); float GetEPP(); // wyliczanie sredniego cisnienia w PG int DirectionSet(int d); // ustawienie kierunku w składzie diff --git a/Event.cpp b/Event.cpp index 70e36a33..b1bd3429 100644 --- a/Event.cpp +++ b/Event.cpp @@ -1019,7 +1019,7 @@ multi_event::init() { auto const conditiontchecksmemcell { m_conditions.flags & ( flags::text | flags::value_1 | flags::value_2 ) }; // not all multi-events have memory cell checks, for the ones which don't we can keep quiet about it - init_targets( simulation::Memory, "memory cell", ( false == conditiontchecksmemcell ) ); + init_targets( simulation::Memory, "memory cell", conditiontchecksmemcell ); if( m_ignored ) { // legacy compatibility behaviour, instead of disabling the event we disable the memory cell comparison test m_conditions.flags &= ~( flags::text | flags::value_1 | flags::value_2 ); diff --git a/Gauge.cpp b/Gauge.cpp index c9c09490..79218ce8 100644 --- a/Gauge.cpp +++ b/Gauge.cpp @@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/. #include "Gauge.h" #include "parser.h" #include "Model3d.h" +#include "DynObj.h" #include "Timer.h" #include "Logs.h" #include "renderer.h" @@ -76,7 +77,7 @@ void TGauge::Init(TSubModel *Submodel, TGaugeAnimation Type, float Scale, float } }; -bool TGauge::Load( cParser &Parser, TDynamicObject const *Owner, TModel3d *md1, TModel3d *md2, double mul ) { +void TGauge::Load( cParser &Parser, TDynamicObject const *Owner, double const mul ) { std::string submodelname, gaugetypename; float scale, endscale, endvalue, offset, friction; @@ -138,13 +139,17 @@ bool TGauge::Load( cParser &Parser, TDynamicObject const *Owner, TModel3d *md1, if( interpolatescale ) { endscale *= mul; } - TSubModel *submodel = md1->GetFromName( submodelname ); - if (submodel) // jeśli nie znaleziony - md2 = nullptr; // informacja, że znaleziony - else if (md2) // a jest podany drugi model (np. zewnętrzny) - submodel = md2->GetFromName(submodelname); // to może tam będzie, co za różnica gdzie + TSubModel *submodel { nullptr }; + std::array sources { Owner->mdKabina, Owner->mdLowPolyInt }; + for( auto const *source : sources ) { + if( ( source != nullptr ) + && ( submodel = source->GetFromName( submodelname ) ) != nullptr ) { + // got what we wanted, bail out + break; + } + } if( submodel == nullptr ) { - ErrorLog( "Bad model: failed to locate sub-model \"" + submodelname + "\" in 3d model \"" + md1->NameGet() + "\"", logtype::model ); + ErrorLog( "Bad model: failed to locate sub-model \"" + submodelname + "\" in 3d model(s) of \"" + Owner->name() + "\"", logtype::model ); } std::map gaugetypes { @@ -163,7 +168,7 @@ bool TGauge::Load( cParser &Parser, TDynamicObject const *Owner, TModel3d *md1, Init( submodel, type, scale, offset, friction, 0, endvalue, endscale, interpolatescale ); - return md2 != nullptr; // true, gdy podany model zewnętrzny, a w kabinie nie było +// return md2 != nullptr; // true, gdy podany model zewnętrzny, a w kabinie nie było }; bool diff --git a/Gauge.h b/Gauge.h index 82de24af..e812831b 100644 --- a/Gauge.h +++ b/Gauge.h @@ -38,7 +38,7 @@ public: void Clear() { *this = TGauge(); } void Init(TSubModel *Submodel, TGaugeAnimation Type, float Scale = 1, float Offset = 0, float Friction = 0, float Value = 0, float const Endvalue = -1.0, float const Endscale = -1.0, bool const Interpolate = false ); - bool Load(cParser &Parser, TDynamicObject const *Owner, TModel3d *md1, TModel3d *md2 = nullptr, double mul = 1.0); + void Load(cParser &Parser, TDynamicObject const *Owner, double const mul = 1.0); void UpdateValue( float fNewDesired ); void UpdateValue( float fNewDesired, sound_source &Fallbacksound ); void PutValue(float fNewDesired); diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 53824bca..28021cee 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -209,13 +209,20 @@ static int const s_CAtest = 128; /*dzwieki*/ enum sound { none, - loud = 0x1, - couplerstretch = 0x2, - bufferclash = 0x4, - relay = 0x10, - parallel = 0x20, - shuntfield = 0x40, - pneumatic = 0x80 + loud = 1 << 0, + couplerstretch = 1 << 1, + bufferclash = 1 << 2, + relay = 1 << 3, + parallel = 1 << 4, + shuntfield = 1 << 5, + pneumatic = 1 << 6, + detachall = 1 << 7, + attachcoupler = 1 << 8, + attachbrakehose = 1 << 9, + attachmainhose = 1 << 10, + attachcontrol = 1 << 11, + attachgangway = 1 << 12, + attachheating = 1 << 13 }; //szczególne typy pojazdów (inna obsługa) dla zmiennej TrainType @@ -356,7 +363,7 @@ enum class TBrakeSystem { Individual, Pneumatic, ElectroPneumatic }; /*podtypy hamulcow zespolonych*/ enum class TBrakeSubSystem { ss_None, ss_W, ss_K, ss_KK, ss_Hik, ss_ESt, ss_KE, ss_LSt, ss_MT, ss_Dako }; enum class TBrakeValve { NoValve, W, W_Lu_VI, W_Lu_L, W_Lu_XR, K, Kg, Kp, Kss, Kkg, Kkp, Kks, Hikg1, Hikss, Hikp1, KE, SW, EStED, NESt3, ESt3, LSt, ESt4, ESt3AL2, EP1, EP2, M483, CV1_L_TR, CV1, CV1_R, Other }; -enum class TBrakeHandle { NoHandle, West, FV4a, M394, M254, FVel1, FVel6, D2, Knorr, FD1, BS2, testH, St113, MHZ_P, MHZ_T, MHZ_EN57, MHZ_K5P }; +enum class TBrakeHandle { NoHandle, West, FV4a, M394, M254, FVel1, FVel6, D2, Knorr, FD1, BS2, testH, St113, MHZ_P, MHZ_T, MHZ_EN57, MHZ_K5P, MHZ_K8P }; /*typy hamulcow indywidualnych*/ enum class TLocalBrake { NoBrake, ManualBrake, PneumaticBrake, HydraulicBrake }; /*dla osob/towar: opoznienie hamowania/odhamowania*/ @@ -1245,7 +1252,7 @@ public: double Distance(const TLocation &Loc1, const TLocation &Loc2, const TDimension &Dim1, const TDimension &Dim2); /* double Distance(const vector3 &Loc1, const vector3 &Loc2, const vector3 &Dim1, const vector3 &Dim2); */ //bool AttachA(int ConnectNo, int ConnectToNr, TMoverParameters *ConnectTo, int CouplingType, bool Forced = false); - bool Attach(int ConnectNo, int ConnectToNr, TMoverParameters *ConnectTo, int CouplingType, bool Forced = false); + bool Attach(int ConnectNo, int ConnectToNr, TMoverParameters *ConnectTo, int CouplingType, bool Forced = false, bool Audible = true); int DettachStatus(int ConnectNo); bool Dettach(int ConnectNo); void SetCoupleDist(); @@ -1363,6 +1370,7 @@ public: bool MotorBlowersSwitch( bool State, side const Side, range_t const Notify = range_t::consist ); // traction motor fan state toggle bool MotorBlowersSwitchOff( bool State, side const Side, range_t const Notify = range_t::consist ); // traction motor fan state toggle bool MainSwitch( bool const State, range_t const Notify = range_t::consist );/*! wylacznik glowny*/ + void MainSwitch_( bool const State ); bool ConverterSwitch( bool State, range_t const Notify = range_t::consist );/*! wl/wyl przetwornicy*/ bool CompressorSwitch( bool State, range_t const Notify = range_t::consist );/*! wl/wyl sprezarki*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 443eb2ef..cf80e601 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -406,7 +406,7 @@ double TMoverParameters::CouplerDist(int Coupler) return Couplers[ Coupler ].CoupleDist; }; -bool TMoverParameters::Attach(int ConnectNo, int ConnectToNr, TMoverParameters *ConnectTo, int CouplingType, bool Forced) +bool TMoverParameters::Attach(int ConnectNo, int ConnectToNr, TMoverParameters *ConnectTo, int CouplingType, bool Forced, bool Audible) { //łączenie do swojego sprzęgu (ConnectNo) pojazdu (ConnectTo) stroną (ConnectToNr) // Ra: zwykle wykonywane dwukrotnie, dla każdego pojazdu oddzielnie // Ra: trzeba by odróżnić wymóg dociśnięcia od uszkodzenia sprzęgu przy podczepianiu AI do @@ -433,12 +433,32 @@ bool TMoverParameters::Attach(int ConnectNo, int ConnectToNr, TMoverParameters * coupler.Render = true; // tego rysować othercoupler.Render = false; // a tego nie }; + auto const couplingchange { CouplingType ^ coupler.CouplingFlag }; coupler.CouplingFlag = CouplingType; // ustawienie typu sprzęgu // if (CouplingType!=ctrain_virtual) //Ra: wirtualnego nie łączymy zwrotnie! //{//jeśli łączenie sprzęgiem niewirtualnym, ustawiamy połączenie zwrotne othercoupler.CouplingFlag = CouplingType; othercoupler.Connected = this; othercoupler.CoupleDist = coupler.CoupleDist; + + if( ( true == Audible ) && ( couplingchange != 0 ) ) { + // set sound event flag + int soundflag{ sound::none }; + std::vector> const soundmappings = { + { coupling::coupler, sound::attachcoupler }, + { coupling::brakehose, sound::attachbrakehose }, + { coupling::mainhose, sound::attachmainhose }, + { coupling::control, sound::attachcontrol}, + { coupling::gangway, sound::attachgangway}, + { coupling::heating, sound::attachheating} }; + for( auto const &soundmapping : soundmappings ) { + if( ( couplingchange & soundmapping.first ) != 0 ) { + soundflag |= soundmapping.second; + } + } + SetFlag( coupler.sounds, soundflag ); + } + return true; //} // podłączenie nie udało się - jest wirtualne @@ -470,7 +490,7 @@ int TMoverParameters::DettachStatus(int ConnectNo) // if (CouplerType==Articulated) return false; //sprzęg nie do rozpięcia - może być tylko urwany // Couplers[ConnectNo].CoupleDist=Distance(Loc,Couplers[ConnectNo].Connected->Loc,Dim,Couplers[ConnectNo].Connected->Dim); CouplerDist(ConnectNo); - if (Couplers[ConnectNo].CouplerType == TCouplerType::Screw ? Couplers[ConnectNo].CoupleDist < 0.0 : true) + if (Couplers[ConnectNo].CouplerType == TCouplerType::Screw ? Couplers[ConnectNo].CoupleDist < 0.01 : true) return -Couplers[ConnectNo].CouplingFlag; // można rozłączać, jeśli dociśnięty return (Couplers[ConnectNo].CoupleDist > 0.2) ? -Couplers[ConnectNo].CouplingFlag : Couplers[ConnectNo].CouplingFlag; @@ -489,6 +509,10 @@ bool TMoverParameters::Dettach(int ConnectNo) Couplers[ConnectNo].Connected->Couplers[Couplers[ConnectNo].ConnectedNr].CouplingFlag = 0; // pozostaje sprzęg wirtualny Couplers[ConnectNo].CouplingFlag = 0; // pozostaje sprzęg wirtualny + + // set sound event flag + SetFlag( Couplers[ ConnectNo ].sounds, sound::detachall ); + return true; } else if (i > 0) @@ -539,7 +563,7 @@ bool TMoverParameters::DirectionForward() SendCtrlToNext("Direction", ActiveDir, CabNo); return true; } - else if ((ActiveDir == 1) && (MainCtrlPos == 0) && (TrainType == dt_EZT)) + else if ((ActiveDir == 1) && (MainCtrlPos == 0) && (TrainType == dt_EZT) && (EngineType != TEngineType::ElectricInductionMotor)) return MinCurrentSwitch(true); //"wysoki rozruch" EN57 return false; }; @@ -611,8 +635,9 @@ bool TMoverParameters::ChangeCab(int direction) { // if (ActiveCab+direction=0) then LastCab:=ActiveCab; ActiveCab = ActiveCab + direction; - if ((BrakeSystem == TBrakeSystem::Pneumatic) && (BrakeCtrlPosNo > 0)) - { + if( ( BrakeCtrlPosNo > 0 ) + && ( ( BrakeSystem == TBrakeSystem::Pneumatic ) + || ( BrakeSystem == TBrakeSystem::ElectroPneumatic ) ) ) { // if (BrakeHandle==FV4a) //!!!POBIERAĆ WARTOŚĆ Z KLASY ZAWORU!!! // BrakeLevelSet(-2); //BrakeCtrlPos=-2; // else if ((BrakeHandle==FVel6)||(BrakeHandle==St113)) @@ -2375,7 +2400,7 @@ bool TMoverParameters::EpFuseSwitch(bool State) bool TMoverParameters::DirectionBackward(void) { bool DB = false; - if ((ActiveDir == 1) && (MainCtrlPos == 0) && (TrainType == dt_EZT)) + if ((ActiveDir == 1) && (MainCtrlPos == 0) && (TrainType == dt_EZT) && (EngineType != TEngineType::ElectricInductionMotor)) if (MinCurrentSwitch(false)) { DB = true; // @@ -2715,66 +2740,76 @@ bool TMoverParameters::MotorBlowersSwitchOff( bool State, side const Side, range // Q: 20160713 // włączenie / wyłączenie obwodu głownego // ************************************************************************************************* -bool TMoverParameters::MainSwitch( bool const State, range_t const Notify ) -{ +bool TMoverParameters::MainSwitch( bool const State, range_t const Notify ) { + bool const initialstate { Mains || dizel_startup }; - if( ( Mains != State ) - && ( MainCtrlPosNo > 0 ) ) { + MainSwitch_( State ); - if( ( false == State ) - || ( ( ( ScndCtrlPos == 0 ) || ( EngineType == TEngineType::ElectricInductionMotor ) ) - && ( ( ConvOvldFlag == false ) || ( TrainType == dt_EZT ) ) - && ( true == NoVoltRelay ) - && ( true == OvervoltageRelay ) - && ( LastSwitchingTime > CtrlDelay ) - && ( false == TestFlag( DamageFlag, dtrain_out ) ) - && ( false == TestFlag( EngDmgFlag, 1 ) ) ) ) { - - if( true == State ) { - // switch on - if( ( EngineType == TEngineType::DieselEngine ) - || ( EngineType == TEngineType::DieselElectric ) ) { - // launch diesel engine startup procedure - dizel_startup = true; - } - else { - Mains = true; - } - } - else { - Mains = false; - // potentially knock out the pumps if their switch doesn't force them on - WaterPump.is_active &= WaterPump.is_enabled; - FuelPump.is_active &= FuelPump.is_enabled; - } - - if( ( TrainType == dt_EZT ) - && ( false == State ) ) { - - ConvOvldFlag = true; - } - - if( Mains != initialstate ) { - LastSwitchingTime = 0; - } - - if( Notify != range_t::local ) { - // pass the command to other vehicles - SendCtrlToNext( - "MainSwitch", - ( State ? 1 : 0 ), - CabNo, - ( Notify == range_t::unit ? - coupling::control | coupling::permanent : - coupling::control ) ); - } - } + if( Notify != range_t::local ) { + // pass the command to other vehicles + // TBD: pass the requested state, or the actual state? + SendCtrlToNext( + "MainSwitch", + ( State ? 1 : 0 ), + CabNo, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); } return ( ( Mains || dizel_startup ) != initialstate ); } +void TMoverParameters::MainSwitch_( bool const State ) { + + if( ( Mains == State ) + || ( MainCtrlPosNo == 0 ) ) { + // nothing to do + return; + } + + bool const initialstate { Mains || dizel_startup }; + + if( ( false == State ) + || ( ( ( ScndCtrlPos == 0 ) || ( EngineType == TEngineType::ElectricInductionMotor ) ) + && ( ( ConvOvldFlag == false ) || ( TrainType == dt_EZT ) ) + && ( true == NoVoltRelay ) + && ( true == OvervoltageRelay ) + && ( LastSwitchingTime > CtrlDelay ) + && ( false == TestFlag( DamageFlag, dtrain_out ) ) + && ( false == TestFlag( EngDmgFlag, 1 ) ) ) ) { + + if( true == State ) { + // switch on + if( ( EngineType == TEngineType::DieselEngine ) + || ( EngineType == TEngineType::DieselElectric ) ) { + // launch diesel engine startup procedure + dizel_startup = true; + } + else { + Mains = true; + } + } + else { + Mains = false; + // potentially knock out the pumps if their switch doesn't force them on + WaterPump.is_active &= WaterPump.is_enabled; + FuelPump.is_active &= FuelPump.is_enabled; + } + + if( ( TrainType == dt_EZT ) + && ( false == State ) ) { + + ConvOvldFlag = true; + } + + if( Mains != initialstate ) { + LastSwitchingTime = 0; + } + } +} + // ************************************************************************************************* // Q: 20160713 // włączenie / wyłączenie przetwornicy @@ -3531,9 +3566,8 @@ void TMoverParameters::UpdatePipePressure(double dt) dpMainValve = 0; - if ((BrakeCtrlPosNo > 1) /*&& (ActiveCab != 0)*/) - // with BrakePressureTable[BrakeCtrlPos] do - { + if( BrakeCtrlPosNo > 1 ) { + if ((EngineType != TEngineType::ElectricInductionMotor)) dpLocalValve = LocHandle->GetPF(std::max(LocalBrakePosA, LocalBrakePosAEIM), Hamulec->GetBCP(), ScndPipePress, dt, 0); else @@ -3549,13 +3583,21 @@ void TMoverParameters::UpdatePipePressure(double dt) temp = ScndPipePress; } Handle->SetReductor(BrakeCtrlPos2); + + if( ( ( BrakeOpModes & bom_PS ) == 0 ) + || ( ( ActiveCab != 0 ) + && ( BrakeOpModeFlag != bom_PS ) ) ) { - if ((BrakeOpModeFlag != bom_PS)) - if ((BrakeOpModeFlag < bom_EP) || ((Handle->GetPos(bh_EB) - 0.5) < BrakeCtrlPosR) || - (BrakeHandle != TBrakeHandle::MHZ_EN57)) - dpMainValve = Handle->GetPF(BrakeCtrlPosR, PipePress, temp, dt, EqvtPipePress); - else - dpMainValve = Handle->GetPF(0, PipePress, temp, dt, EqvtPipePress); + if( ( BrakeOpModeFlag < bom_EP ) + || ( ( Handle->GetPos( bh_EB ) - 0.5 ) < BrakeCtrlPosR ) + || ( ( BrakeHandle != TBrakeHandle::MHZ_EN57 ) + && ( BrakeHandle != TBrakeHandle::MHZ_K8P ) ) ) { + dpMainValve = Handle->GetPF( BrakeCtrlPosR, PipePress, temp, dt, EqvtPipePress ); + } + else { + dpMainValve = Handle->GetPF( 0, PipePress, temp, dt, EqvtPipePress ); + } + } if (dpMainValve < 0) // && (PipePressureVal > 0.01) //50 if (Compressor > ScndPipePress) @@ -4405,7 +4447,8 @@ double TMoverParameters::CouplerForce(int CouplerN, double dt) // sprzeganie wagonow z samoczynnymi sprzegami} // CouplingFlag:=ctrain_coupler+ctrain_pneumatic+ctrain_controll+ctrain_passenger+ctrain_scndpneumatic; // EN57 - Couplers[ CouplerN ].CouplingFlag = coupling::coupler | coupling::brakehose | coupling::mainhose | coupling::control; + Couplers[ CouplerN ].CouplingFlag = coupling::coupler /*| coupling::brakehose | coupling::mainhose | coupling::control*/; + SetFlag( Couplers[ CouplerN ].sounds, sound::attachcoupler ); } } } @@ -4602,14 +4645,6 @@ double TMoverParameters::TractionForce( double dt ) { switch( EngineType ) { case TEngineType::ElectricSeriesMotor: { -/* - if ((Mains)) // nie wchodzić w funkcję bez potrzeby - if ( (std::max(GetTrainsetVoltage(), std::abs(Voltage)) < EnginePowerSource.CollectorParameters.MinV) || - (std::max(GetTrainsetVoltage(), std::abs(Voltage)) * EnginePowerSource.CollectorParameters.OVP > - EnginePowerSource.CollectorParameters.MaxV)) - if( MainSwitch( false, ( TrainType == dt_EZT ? range_t::unit : range_t::local ) ) ) // TODO: check whether we need to send this EMU-wide - EventFlag = true; // wywalanie szybkiego z powodu niewłaściwego napięcia -*/ // update the state of voltage relays auto const voltage { std::max( GetTrainsetVoltage(), std::abs( RunningTraction.TractionVoltage ) ) }; NoVoltRelay = ( voltage >= EnginePowerSource.CollectorParameters.MinV ); @@ -4621,6 +4656,18 @@ double TMoverParameters::TractionForce( double dt ) { break; } + case TEngineType::ElectricInductionMotor: { + // TODO: check if we can use instead the code for electricseriesmotor + if( ( Mains ) ) { + // nie wchodzić w funkcję bez potrzeby + if( ( std::max( GetTrainsetVoltage(), std::abs( RunningTraction.TractionVoltage ) ) < EnginePowerSource.CollectorParameters.MinV ) + || ( std::max( GetTrainsetVoltage(), std::abs( RunningTraction.TractionVoltage ) ) > EnginePowerSource.CollectorParameters.MaxV + 200 ) ) { + MainSwitch( false, ( TrainType == dt_EZT ? range_t::unit : range_t::local ) ); // TODO: check whether we need to send this EMU-wide + } + } + break; + } + case TEngineType::DieselElectric: { // TODO: move this to the auto relay check when the electric engine code paths are unified StLinFlag = MotorConnectorsCheck(); @@ -4857,9 +4904,13 @@ double TMoverParameters::TractionForce( double dt ) { EnginePower = Voltage * Im / 1000.0; /* - // NOTE: this part is experimentally disabled, as it generated early traction force drop for undetermined purpose - if ((tmpV > 2) && (EnginePower < tmp)) - Ft = Ft * EnginePower / tmp; + // power curve drop + // NOTE: disabled for the time being due to side-effects + if( ( tmpV > 1 ) && ( EnginePower < tmp ) ) { + Ft = interpolate( + Ft, EnginePower / tmp, + clamp( tmpV - 1.0, 0.0, 1.0 ) ); + } */ } @@ -5033,13 +5084,6 @@ double TMoverParameters::TractionForce( double dt ) { case TEngineType::ElectricInductionMotor: { - if( ( Mains ) ) { - // nie wchodzić w funkcję bez potrzeby - if( ( std::max( std::abs( Voltage ), GetTrainsetVoltage() ) < EnginePowerSource.CollectorParameters.MinV ) - || ( std::max( std::abs( Voltage ), GetTrainsetVoltage() ) > EnginePowerSource.CollectorParameters.MaxV + 200 ) ) { - MainSwitch( false, ( TrainType == dt_EZT ? range_t::unit : range_t::local ) ); // TODO: check whether we need to send this EMU-wide - } - } if( true == Mains ) { //tempomat if (ScndCtrlPosNo > 1) @@ -5502,8 +5546,9 @@ bool TMoverParameters::MaxCurrentSwitch(bool State) bool TMoverParameters::MinCurrentSwitch(bool State) { bool MCS = false; - if (((EngineType == TEngineType::ElectricSeriesMotor) && (IminHi > IminLo)) || (TrainType == dt_EZT)) - { + if( ( ( EngineType == TEngineType::ElectricSeriesMotor ) && ( IminHi > IminLo ) ) + || ( ( TrainType == dt_EZT ) && ( EngineType != TEngineType::ElectricInductionMotor ) ) ) { + if (State && (Imin == IminLo)) { Imin = IminHi; @@ -8194,6 +8239,7 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { { "D2", TBrakeHandle::D2 }, { "MHZ_EN57", TBrakeHandle::MHZ_EN57 }, { "MHZ_K5P", TBrakeHandle::MHZ_K5P }, + { "MHZ_K8P", TBrakeHandle::MHZ_K8P }, { "M394", TBrakeHandle::M394 }, { "Knorr", TBrakeHandle::Knorr }, { "Westinghouse", TBrakeHandle::West }, @@ -8984,6 +9030,7 @@ bool TMoverParameters::CheckLocomotiveParameters(bool ReadyFlag, int Dir) Handle = std::make_shared(); break; case TBrakeHandle::MHZ_EN57: + case TBrakeHandle::MHZ_K8P: Handle = std::make_shared(); break; case TBrakeHandle::FVel6: @@ -9430,22 +9477,7 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C } else if (Command == "MainSwitch") { - if (CValue1 == 1) { - - if( ( EngineType == TEngineType::DieselEngine ) - || ( EngineType == TEngineType::DieselElectric ) ) { - dizel_startup = true; - } - else { - Mains = true; - } - } - else { - Mains = false; - // potentially knock out the pumps if their switch doesn't force them on - WaterPump.is_active &= WaterPump.is_enabled; - FuelPump.is_active &= FuelPump.is_enabled; - } + MainSwitch_( CValue1 > 0.0 ); OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } else if (Command == "Direction") diff --git a/Model3d.cpp b/Model3d.cpp index 872a35f5..a9328b05 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -110,9 +110,12 @@ TSubModel::SetVisibilityLevel( float const Level, bool const Includechildren, bo // sets light level (alpha component of illumination color) to specified value void -TSubModel::SetLightLevel( float const Level, bool const Includechildren, bool const Includesiblings ) { - - f4Emision.a = Level; +TSubModel::SetLightLevel( glm::vec4 const &Level, bool const Includechildren, bool const Includesiblings ) { + /* + f4Emision = Level; + */ + f4Diffuse = { Level.r, Level.g, Level.b, f4Diffuse.a }; + f4Emision.a = Level.a; if( true == Includesiblings ) { auto sibling { this }; while( ( sibling = sibling->Next ) != nullptr ) { @@ -171,10 +174,26 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic /* iVboPtr = Pos; // pozycja w VBO */ - if (!parser.expectToken("type:")) - ErrorLog("Bad model: expected submodel type definition not found while loading model \"" + Model->NameGet() + "\"" ); + auto token { parser.getToken() }; + if( token != "type:" ) { + std::string errormessage { + "Bad model: expected submodel type definition not found while loading model \"" + Model->NameGet() + "\"" + + "\ncurrent model data stream content: \"" }; + auto count { 10 }; + while( ( true == parser.getTokens() ) + && ( false == ( token = parser.peek() ).empty() ) + && ( token != "parent:" ) ) { + // skip data until next submodel, dump first few tokens in the error message + if( --count > 0 ) { + errormessage += token + " "; + } + } + errormessage += "(...)\""; + ErrorLog( errormessage ); + return 0; + } { - std::string type = parser.getToken(); + auto const type { parser.getToken() }; if (type == "mesh") eType = GL_TRIANGLES; // submodel - trójkaty else if (type == "point") @@ -187,7 +206,6 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic eType = TP_STARS; // wiele punktów świetlnych }; parser.ignoreToken(); - std::string token; parser.getTokens(1, false); // nazwa submodelu bez zmieny na małe parser >> token; Name(token); @@ -797,6 +815,8 @@ uint32_t TSubModel::FlagsCheck() // samo pomijanie glBindTexture() nie poprawi wydajności // ale można sprawdzić, czy można w ogóle pominąć kod do tekstur (sprawdzanie // replaceskin) + m_rotation_init_done = true; + uint32_t i = 0; if (Child) { // Child jest renderowany po danym submodelu @@ -1261,16 +1281,10 @@ TSubModel::offset( float const Geometrytestoffsetthreshold ) const { } } - if( true == TestFlag( iFlags, 0x0200 ) ) { - // flip coordinates for t3d file which wasn't yet initialized - if( ( false == simulation::is_ready ) - || ( false == Vertices.empty() ) ) { - // NOTE, HACK: results require flipping if the model wasn't yet initialized, so we're using crude method to detect possible cases - // TODO: sort out this mess, either unify offset lookups to take place before (or after) initialization, - // or provide way to determine on submodel level whether the initialization took place - offset = { -offset.x, offset.z, offset.y }; - } - } + if (!m_rotation_init_done) + // NOTE, HACK: results require flipping if the model wasn't yet initialized, + // TODO: sort out this mess, maybe try unify offset lookups to take place before (or after) initialization, + offset = { -offset.x, offset.z, offset.y }; return offset; } @@ -1518,6 +1532,9 @@ void TSubModel::deserialize(std::istream &s) fCosFalloffAngle = sn_utils::ld_float32(s); fCosHotspotAngle = sn_utils::ld_float32(s); fCosViewAngle = sn_utils::ld_float32(s); + + // necessary rotations were already done during t3d->e3d conversion + m_rotation_init_done = true; } void TModel3d::deserialize(std::istream &s, size_t size, bool dynamic) diff --git a/Model3d.h b/Model3d.h index d82b6062..00994c8e 100644 --- a/Model3d.h +++ b/Model3d.h @@ -121,6 +121,8 @@ private: float fCosHotspotAngle { 0.3f }; // cosinus kąta stożka pod którym widać aureolę i zwiększone natężenie światła float fCosViewAngle { 0.0f }; // cos kata pod jakim sie teraz patrzy + bool m_rotation_init_done = false; + TSubModel *Next { nullptr }; TSubModel *Child { nullptr }; public: // temporary access, clean this up during refactoring @@ -201,7 +203,7 @@ public: // sets visibility level (alpha component) to specified value void SetVisibilityLevel( float const Level, bool const Includechildren = false, bool const Includesiblings = false ); // sets light level (alpha component of illumination color) to specified value - void SetLightLevel( float const Level, bool const Includechildren = false, bool const Includesiblings = false ); + void SetLightLevel( glm::vec4 const &Level, bool const Includechildren = false, bool const Includesiblings = false ); inline float3 Translation1Get() { return fMatrix ? *(fMatrix->TranslationGet()) + v_TransVector : v_TransVector; } inline float3 Translation2Get() { diff --git a/PyInt.cpp b/PyInt.cpp index 88f151be..725a91ed 100644 --- a/PyInt.cpp +++ b/PyInt.cpp @@ -142,13 +142,11 @@ auto python_taskqueue::init() -> bool { stringioclassname != nullptr ? PyObject_CallObject( stringioclassname, nullptr ) : nullptr ); - m_error = { ( + m_stderr = { ( stringioobject == nullptr ? nullptr : PySys_SetObject( "stderr", stringioobject ) != 0 ? nullptr : stringioobject ) }; - if( m_error == nullptr ) { goto release_and_exit; } - if( false == run_file( "abstractscreenrenderer" ) ) { goto release_and_exit; } // release the lock, save the state for future use @@ -392,13 +390,13 @@ python_taskqueue::error() { if( PyErr_Occurred() == nullptr ) { return; } - if( m_error != nullptr ) { + if( m_stderr != nullptr ) { // std err pythona jest buforowane PyErr_Print(); - auto *errortext { PyObject_CallMethod( m_error, "getvalue", nullptr ) }; + auto *errortext { PyObject_CallMethod( m_stderr, "getvalue", nullptr ) }; ErrorLog( PyString_AsString( errortext ) ); // czyscimy bufor na kolejne bledy - PyObject_CallMethod( m_error, "truncate", "i", 0 ); + PyObject_CallMethod( m_stderr, "truncate", "i", 0 ); } else { // nie dziala buffor pythona @@ -420,7 +418,7 @@ python_taskqueue::error() { } auto *tracebacktext { PyObject_Str( traceback ) }; if( tracebacktext != nullptr ) { - WriteLog( PyString_AsString( tracebacktext ) ); + ErrorLog( PyString_AsString( tracebacktext ) ); } else { WriteLog( "Python Interpreter: failed to retrieve the stack traceback" ); diff --git a/PyInt.h b/PyInt.h index 84f668ce..4dddcbbd 100644 --- a/PyInt.h +++ b/PyInt.h @@ -116,7 +116,7 @@ private: void error(); // members PyObject *m_main { nullptr }; - PyObject *m_error { nullptr }; + PyObject *m_stderr { nullptr }; PyThreadState *m_mainthread{ nullptr }; worker_array m_workers; threading::condition_variable m_condition; // wakes up the workers diff --git a/Segment.cpp b/Segment.cpp index 58a9801f..1b9a2edf 100644 --- a/Segment.cpp +++ b/Segment.cpp @@ -372,7 +372,7 @@ Math3D::vector3 TSegment::FastGetPoint(double const t) const interpolate( Point1, Point2, t ) ); } -bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Origin, const gfx::vertex_array &ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale, int iSkip, int iEnd, std::pair fOffsetX, glm::vec3 **p, bool bRender) +bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Origin, const gfx::vertex_array &ShapePoints, bool const Transition, double fTextureLength, double Texturescale, int iSkip, int iEnd, std::pair fOffsetX, glm::vec3 **p, bool bRender) { // generowanie trójkątów dla odcinka trajektorii ruchu // standardowo tworzy triangle_strip dla prostego albo ich zestaw dla łuku // po modyfikacji - dla ujemnego (iNumShapePoints) w dodatkowych polach tabeli podany jest przekrój końcowy @@ -382,8 +382,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori glm::vec3 pos1, pos2, dir, parallel1, parallel2, pt, norm; float s, step, fOffset, tv1, tv2, t, fEnd; - bool const trapez = iNumShapePoints < 0; // sygnalizacja trapezowatości - iNumShapePoints = std::abs( iNumShapePoints ); + auto const iNumShapePoints = Transition ? ShapePoints.size() / 2 : ShapePoints.size(); float const texturelength = fTextureLength * Texturescale; float const texturescale = Texturescale; @@ -448,7 +447,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori parallel2 = glm::normalize( parallel2 ); // TODO: refactor the loop, there's no need to calculate starting points for each segment when we can copy the end points of the previous one - if( trapez ) { + if( Transition ) { for( int j = 0; j < iNumShapePoints; ++j ) { pt = parallel1 * ( jmm1 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m1 * ( ShapePoints[ j + iNumShapePoints ].position.x - fOffsetX.second ) ) + pos1; pt.y += jmm1 * ShapePoints[ j ].position.y + m1 * ShapePoints[ j + iNumShapePoints ].position.y; diff --git a/Segment.h b/Segment.h index 06d0290a..9ea8020e 100644 --- a/Segment.h +++ b/Segment.h @@ -116,7 +116,7 @@ public: r2 = fRoll2; } bool - RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Origin, gfx::vertex_array const &ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, std::pair fOffsetX = {0.f, 0.f}, glm::vec3 **p = nullptr, bool bRender = true ); + RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Origin, gfx::vertex_array const &ShapePoints, bool const Transition, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, std::pair fOffsetX = {0.f, 0.f}, glm::vec3 **p = nullptr, bool bRender = true ); /* void Render(); diff --git a/Texture.cpp b/Texture.cpp index 0bc12910..962e92be 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -880,14 +880,12 @@ opengl_texture::create() { ::glBindTexture( target, id ); // analyze specified texture traits - bool wraps{ true }; - bool wrapt{ true }; for( auto const &trait : traits ) { switch( trait ) { - case 's': { wraps = false; break; } - case 't': { wrapt = false; break; } + case 's': { wrap_mode_s = GL_CLAMP_TO_EDGE; break; } + case 't': { wrap_mode_t = GL_CLAMP_TO_EDGE; break; } } } @@ -900,8 +898,8 @@ opengl_texture::create() { { glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap_mode_s); + glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap_mode_t); if (data_components == GL_DEPTH_COMPONENT) { glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); @@ -926,8 +924,8 @@ opengl_texture::create() { } else { - ::glTexParameteri(target, GL_TEXTURE_WRAP_S, (wraps == true ? GL_REPEAT : GL_CLAMP_TO_EDGE)); - ::glTexParameteri(target, GL_TEXTURE_WRAP_T, (wrapt == true ? GL_REPEAT : GL_CLAMP_TO_EDGE)); + ::glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap_mode_s); + ::glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap_mode_t); set_filtering(); // data_format and data_type specifies how image is laid out in memory @@ -1061,7 +1059,7 @@ opengl_texture::release() { return; } -void opengl_texture::alloc_rendertarget(GLint format, GLint components, int width, int height, int s) +void opengl_texture::alloc_rendertarget(GLint format, GLint components, int width, int height, int s, GLint wrap) { data_width = width; data_height = height; @@ -1069,6 +1067,8 @@ void opengl_texture::alloc_rendertarget(GLint format, GLint components, int widt data_components = components; data_mapcount = 1; is_rendertarget = true; + wrap_mode_s = wrap; + wrap_mode_t = wrap; samples = s; if (samples > 1) target = GL_TEXTURE_2D_MULTISAMPLE; diff --git a/Texture.h b/Texture.h index 42686489..7a8efe7a 100644 --- a/Texture.h +++ b/Texture.h @@ -43,7 +43,7 @@ struct opengl_texture { height() const { return data_height; } - void alloc_rendertarget(GLint format, GLint components, int width, int height, int samples = 1); + void alloc_rendertarget(GLint format, GLint components, int width, int height, int samples = 1, GLint wrap = GL_CLAMP_TO_BORDER); void set_components_hint(GLint hint); static void reset_unit_cache(); @@ -87,6 +87,8 @@ private: data_components{ 0 }; GLint data_type = GL_UNSIGNED_BYTE; + GLint wrap_mode_s = GL_REPEAT; + GLint wrap_mode_t = GL_REPEAT; /* std::atomic is_loaded{ false }; // indicates the texture data was loaded and can be processed std::atomic is_good{ false }; // indicates the texture data was retrieved without errors diff --git a/Track.cpp b/Track.cpp index dbf92244..f0e3ccb5 100644 --- a/Track.cpp +++ b/Track.cpp @@ -192,8 +192,7 @@ void TTrack::Init() bool TTrack::sort_by_material( TTrack const *Left, TTrack const *Right ) { - return ( ( Left->m_material1 < Right->m_material1 ) - && ( Left->m_material2 < Right->m_material2 ) ); + return std::tie( Left->m_material1, Left->m_material2 ) < std::tie( Right->m_material1, Right->m_material2 ); } TTrack * TTrack::Create400m(int what, double dx) @@ -586,7 +585,7 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) if (eType == tt_Table) // obrotnica ma doklejkę { // SwitchExtension=new TSwitchExtension(this,1); //dodatkowe zmienne dla obrotnicy - SwitchExtension->Segments[0]->Init(p1, p2, segsize); // kopia oryginalnego toru + SwitchExtension->Segments[0]->Init(p1, p2, segsize, r1, r2 ); // kopia oryginalnego toru } else if (iCategoryFlag & 2) if (m_material1 && fTexLength) @@ -1122,11 +1121,10 @@ void TTrack::RaAssign( TAnimModel *am, basic_event *done, basic_event *joined ) { SwitchExtension->pModel = am; SwitchExtension->evMinus = done; // event zakończenia animacji (zadanie nowej przedłuża) - SwitchExtension->evPlus = - joined; // event potwierdzenia połączenia (gdy nie znajdzie, to się nie połączy) - if (am) - if (am->GetContainer()) // może nie być? - am->GetContainer()->EventAssign(done); // zdarzenie zakończenia animacji + SwitchExtension->evPlus = joined; // event potwierdzenia połączenia (gdy nie znajdzie, to się nie połączy) + if( ( am != nullptr ) && ( am->GetContainer() ) ) {// może nie być? + am->GetContainer()->EventAssign( done ); // zdarzenie zakończenia animacji + } } }; @@ -1159,7 +1157,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { create_track_bed_profile( bpts1, trPrev, trNext ); auto const texturelength { texture_length( m_material2 ) }; gfx::vertex_array vertices; - Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -5 : 5, texturelength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, texturelength); if( ( Bank != 0 ) && ( true == Geometry2.empty() ) ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } @@ -1174,18 +1172,18 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { auto const texturelength { texture_length( m_material1 ) }; gfx::vertex_array vertices; if( ( Bank != 0 ) && ( true == Geometry1.empty() ) ) { - Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, texturelength ); + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // reuse the scratchpad - Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, texturelength ); + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if( ( Bank == 0 ) && ( false == Geometry1.empty() ) ) { // special variant, replace existing data for a turntable track - Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, texturelength ); + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength ); GfxRenderer.Replace( vertices, Geometry1[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); // reuse the scratchpad - Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, texturelength ); + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength ); GfxRenderer.Replace( vertices, Geometry1[ 1 ], GL_TRIANGLE_STRIP ); } } @@ -1196,8 +1194,12 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::vertex_array rpts3, rpts4; create_track_blade_profile( rpts3, rpts4 ); // TODO, TBD: change all track geometry to triangles, to allow packing data in less, larger buffers - auto const bladelength { static_cast( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) ) }; auto const nnumPts { track_rail_profile( m_profile1.second ).size() / 2 }; + auto const bladelength { static_cast( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) ) }; + // positive jointlength: the switch is typically used along the main track, negative: along the diverging track + // TODO: determine this from names of textures assigned to the tracks +// auto const jointlength { static_cast( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.15 ) ) }; + auto const jointlength { 0 }; // temporary until implementation of the above if (SwitchExtension->RightSwitch) { // nowa wersja z SPKS, ale odwrotnie lewa/prawa gfx::vertex_array vertices; @@ -1205,15 +1207,22 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { auto const texturelength { texture_length( m_material1 ) }; // left blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } ); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // fixed parts - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength ); + if( jointlength > 0 ) { + // part of the diverging rail touched by wheels of vehicle going straight + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, 0, jointlength ); + Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); + vertices.clear(); + } + // other rail, full length + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1221,15 +1230,16 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { auto const texturelength { texture_length( m_material2 ) }; // right blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } ); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // fixed parts - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength ); + // diverging rail, potentially minus part touched by wheels of vehicle going straight + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, jointlength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1241,15 +1251,23 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { auto const texturelength { texture_length( m_material1 ) }; // right blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } ); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // fixed parts - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength ); // prawa szyna za iglicą + // prawa szyna za iglicą + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength ); // lewa szyna normalna cała + if( jointlength > 0 ) { + // part of the diverging rail touched by wheels of vehicle going straight + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, 0, jointlength ); + Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); + vertices.clear(); + } + // other rail, full length + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1257,15 +1275,17 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { auto const texturelength { texture_length( m_material2 ) }; // left blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } ); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // fixed parts - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength ); // lewa szyna za iglicą + // lewa szyna za iglicą + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength ); // prawa szyna normalnie cała + // diverging rail, potentially minus part touched by wheels of vehicle going straight + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, jointlength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1297,7 +1317,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { { // tworzenie trójkątów nawierzchni szosy auto const texturelength { texture_length( m_material1 ) }; gfx::vertex_array vertices; - Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -2 : 2, texturelength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, texturelength); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if (m_material2) @@ -1308,31 +1328,17 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::vertex_array rpts1, rpts2; // współrzędne przekroju i mapowania dla prawej i lewej strony create_road_side_profile( rpts1, rpts2, bpts1 ); gfx::vertex_array vertices; - if( iTrapezoid ) // trapez albo przechyłki - { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony - // odcinka - if( ( fTexHeight1 >= 0.0 ) || ( slop != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts1, -3, texturelength ); // tylko jeśli jest z prawej - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - } - if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts2, -3, texturelength ); // tylko jeśli jest z lewej - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - } + if( ( fTexHeight1 >= 0.0 ) || ( slop != 0.0 ) ) { + // tylko jeśli jest z prawej + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, texturelength ); + Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); + vertices.clear(); } - else { // pobocza zwykłe, brak przechyłki - if( ( fTexHeight1 >= 0.0 ) || ( slop != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts1, 3, texturelength ); - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - } - if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts2, 3, texturelength ); - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - } + if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { + // tylko jeśli jest z lewej + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, texturelength ); + Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); + vertices.clear(); } } break; @@ -1406,22 +1412,22 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { if (SwitchExtension->iRoads == 4) { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, {}, &b, render ); + SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 3 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, {}, &b, render ); + SwitchExtension->Segments[ 3 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 4 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, {}, &b, render ); + SwitchExtension->Segments[ 4 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 5 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, {}, &b, render ); + SwitchExtension->Segments[ 5 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); @@ -1431,17 +1437,17 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { else { // punkt 3 pokrywa się z punktem 1, jak w zwrotnicy; połączenie 1->2 nie musi być prostoliniowe if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, {}, &b, render ); // z P2 do P4 + SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); // z P2 do P4 if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, {}, &b, render ); // z P4 do P3=P1 (odwrócony) + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); // z P4 do P3=P1 (odwrócony) if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, {}, &b, render ); // z P1 do P2 + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, true, texturelength, 1.0, 0, 0, {}, &b, render ); // z P1 do P2 if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); @@ -1516,7 +1522,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { if (m_material1) // jeśli podana była tekstura, generujemy trójkąty { // tworzenie trójkątów nawierzchni szosy gfx::vertex_array vertices; - Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -2 : 2, fTexLength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, fTexLength); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if (m_material2) @@ -1524,24 +1530,12 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::vertex_array rpts1, rpts2; // współrzędne przekroju i mapowania dla prawej i lewej strony create_road_side_profile( rpts1, rpts2, bpts1 ); gfx::vertex_array vertices; - if (iTrapezoid) // trapez albo przechyłki - { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - Segment->RenderLoft(vertices, m_origin, rpts1, -3, fTexLength); - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - Segment->RenderLoft(vertices, m_origin, rpts2, -3, fTexLength); - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - } - else - { // pobocza zwykłe, brak przechyłki - Segment->RenderLoft(vertices, m_origin, rpts1, 3, fTexLength); - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - Segment->RenderLoft(vertices, m_origin, rpts2, 3, fTexLength); - Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); - } + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid > 0, fTexLength ); + Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); + vertices.clear(); + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid > 0, fTexLength ); + Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); + vertices.clear(); } } } @@ -1816,8 +1810,8 @@ TTrack * TTrack::RaAnimate() auto const texturelength { texture_length( m_material1 ) }; // left blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } ); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { SwitchExtension->fOffset2, SwitchExtension->fOffset2 / 2 } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { SwitchExtension->fOffset2 / 2, 0.f } ); GfxRenderer.Replace( vertices, Geometry1[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } @@ -1825,8 +1819,8 @@ TTrack * TTrack::RaAnimate() auto const texturelength { texture_length( m_material2 ) }; // right blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } ); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -fMaxOffset + SwitchExtension->fOffset1, ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2 } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { ( -fMaxOffset + SwitchExtension->fOffset1 ) / 2, 0.f } ); GfxRenderer.Replace( vertices, Geometry2[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } @@ -1836,8 +1830,8 @@ TTrack * TTrack::RaAnimate() auto const texturelength { texture_length( m_material1 ) }; // right blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } ); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, true, texturelength, 1.0, 0, bladelength / 2, { -SwitchExtension->fOffset2, -SwitchExtension->fOffset2 / 2 } ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, false, texturelength, 1.0, bladelength / 2, bladelength, { -SwitchExtension->fOffset2 / 2, 0.f } ); GfxRenderer.Replace( vertices, Geometry1[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } @@ -1845,8 +1839,8 @@ TTrack * TTrack::RaAnimate() auto const texturelength { texture_length( m_material2 ) }; // left blade // composed from two parts: transition from blade to regular rail, and regular rail - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } ); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, true, texturelength, 1.0, 0, bladelength / 2, { fMaxOffset - SwitchExtension->fOffset1, ( fMaxOffset - SwitchExtension->fOffset1 ) / 2 } ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, false, texturelength, 1.0, bladelength / 2, bladelength, { ( fMaxOffset - SwitchExtension->fOffset1 ) / 2, 0.f } ); GfxRenderer.Replace( vertices, Geometry2[ 0 ], GL_TRIANGLE_STRIP ); vertices.clear(); } @@ -1863,22 +1857,23 @@ TTrack * TTrack::RaAnimate() SwitchExtension->pModel ? SwitchExtension->pModel->GetContainer() : // pobranie głównego submodelu nullptr ); - if (ac) - if ((ac->AngleGet() != SwitchExtension->fOffset) || - !(ac->TransGet() == - SwitchExtension->vTrans)) // czy przemieściło się od ostatniego sprawdzania - { - double hlen = 0.5 * SwitchExtension->Segments[0]->GetLength(); // połowa - // długości - SwitchExtension->fOffset = ac->AngleGet(); // pobranie kąta z submodelu - double sina = -hlen * std::sin(glm::radians(SwitchExtension->fOffset)), - cosa = -hlen * std::cos(glm::radians(SwitchExtension->fOffset)); + if( ac ) { + if( ( ac->AngleGet() != SwitchExtension->fOffset ) + || !( ac->TransGet() == SwitchExtension->vTrans ) ) { // czy przemieściło się od ostatniego sprawdzania + + double hlen = 0.5 * SwitchExtension->Segments[ 0 ]->GetLength(); // połowa długości + SwitchExtension->fOffset = + SwitchExtension->pModel->Angles().y // take into account orientation of the model + + ac->AngleGet(); // pobranie kąta z submodelu + double + sina = -hlen * std::sin( glm::radians( SwitchExtension->fOffset ) ), + cosa = -hlen * std::cos( glm::radians( SwitchExtension->fOffset ) ); SwitchExtension->vTrans = ac->TransGet(); - auto middle = - location() + - SwitchExtension->vTrans; // SwitchExtension->Segments[0]->FastGetPoint(0.5); - Segment->Init(middle + Math3D::vector3(sina, 0.0, cosa), - middle - Math3D::vector3(sina, 0.0, cosa), 10.0); // nowy odcinek + auto middle = location() + SwitchExtension->vTrans; // SwitchExtension->Segments[0]->FastGetPoint(0.5); + Segment->Init( + middle + Math3D::vector3( sina, 0.0, cosa ), + middle - Math3D::vector3( sina, 0.0, cosa ), + 10.0 ); // nowy odcinek for( auto dynamic : Dynamics ) { // minimalny ruch, aby przeliczyć pozycję dynamic->Move( 0.000001 ); @@ -1886,6 +1881,7 @@ TTrack * TTrack::RaAnimate() // NOTE: passing empty handle is a bit of a hack here. could be refactored into something more elegant create_geometry( {} ); } // animacja trwa nadal + } } else m = false; // koniec animacji albo w ogóle nie połączone z modelem @@ -3080,10 +3076,10 @@ TTrack::create_switch_trackbed( gfx::vertex_array &Output ) { gfx::vertex_array trackbedvertices1, trackbedvertices2; // main trackbed create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 0 ], SwitchExtension->pNexts[ 0 ] ); - SwitchExtension->Segments[ 0 ]->RenderLoft( trackbedvertices1, m_origin, trackbedprofile, -5, texturelength ); + SwitchExtension->Segments[ 0 ]->RenderLoft( trackbedvertices1, m_origin, trackbedprofile, true, texturelength ); // side trackbed create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 1 ], SwitchExtension->pNexts[ 1 ] ); - SwitchExtension->Segments[ 1 ]->RenderLoft( trackbedvertices2, m_origin, trackbedprofile, -5, texturelength ); + SwitchExtension->Segments[ 1 ]->RenderLoft( trackbedvertices2, m_origin, trackbedprofile, true, texturelength ); // ...then combine them into a single geometry sequence auto const segmentsize { 10 }; auto const segmentcount { trackbedvertices1.size() / segmentsize }; @@ -3163,8 +3159,9 @@ TTrack::copy_adjacent_trackbed_material( TTrack const *Exclude ) { break; } case tt_Switch: { - // only check the neighbour on the joint side + // only check the neighbours of the main track adjacents.emplace_back( SwitchExtension->pPrevs[ 0 ] ); + adjacents.emplace_back( SwitchExtension->pNexts[ 0 ] ); break; } default: { diff --git a/Train.cpp b/Train.cpp index 58897a49..3392a4a4 100644 --- a/Train.cpp +++ b/Train.cpp @@ -30,13 +30,13 @@ http://mozilla.org/MPL/2.0/. #include "Console.h" #include "application.h" #include "renderer.h" - +/* namespace input { extern user_command command; } - +*/ void control_mapper::insert( TGauge const &Gauge, std::string const &Label ) { @@ -67,9 +67,10 @@ void TCab::Load(cParser &Parser) std::string token; Parser.getTokens(); Parser >> token; - if (token == "cablight") + if (token == "cablight:") { Parser.getTokens( 9, false ); +/* Parser >> dimm.r >> dimm.g @@ -80,6 +81,7 @@ void TCab::Load(cParser &Parser) >> intlitlow.r >> intlitlow.g >> intlitlow.b; +*/ Parser.getTokens(); Parser >> token; } CabPos1.x = std::stod( token ); @@ -374,10 +376,6 @@ TTrain::TTrain() { fBlinkTimer = 0; fHaslerTimer = 0; DynamicSet(NULL); // ustawia wszystkie mv* - iCabLightFlag = 0; - // hunter-091012 - bCabLight = false; - bCabLightDim = false; //----- pMechSittingPosition = Math3D::vector3(0, 0, 0); // ABu: 180404 fTachoTimer = 0.0; // włączenie skoków wskazań prędkościomierza @@ -554,13 +552,18 @@ PyObject *TTrain::GetTrainState() { PyDict_SetItemString( dict, "velnext", PyGetFloat( driver->VelNext ) ); PyDict_SetItemString( dict, "actualproximitydist", PyGetFloat( driver->ActualProximityDist ) ); // train data + auto const *timetable{ driver->TrainTimetable() }; + PyDict_SetItemString( dict, "trainnumber", PyGetString( driver->TrainName().c_str() ) ); + PyDict_SetItemString( dict, "train_brakingmassratio", PyGetFloat( timetable->BrakeRatio ) ); + PyDict_SetItemString( dict, "train_enginetype", PyGetString( timetable->LocSeries.c_str() ) ); + PyDict_SetItemString( dict, "train_engineload", PyGetFloat( timetable->LocLoad ) ); + PyDict_SetItemString( dict, "train_stationindex", PyGetInt( driver->StationIndex() ) ); auto const stationcount { driver->StationCount() }; PyDict_SetItemString( dict, "train_stationcount", PyGetInt( stationcount ) ); if( stationcount > 0 ) { // timetable stations data, if there's any - auto const *timetable { driver->TrainTimetable() }; for( auto stationidx = 1; stationidx <= stationcount; ++stationidx ) { auto const stationlabel { "train_station" + std::to_string( stationidx ) + "_" }; auto const &timetableline { timetable->TimeTable[ stationidx ] }; @@ -574,7 +577,9 @@ PyObject *TTrain::GetTrainState() { PyDict_SetItemString( dict, ( stationlabel + "dm" ).c_str(), PyGetInt( timetableline.Dm ) ); } } + PyDict_SetItemString( dict, "train_atpassengerstop", PyGetBool( driver->IsAtPassengerStop ) ); // world state data + PyDict_SetItemString( dict, "scenario", PyGetString( Global.SceneryFile.c_str() ) ); PyDict_SetItemString( dict, "hours", PyGetInt( simulation::Time.data().wHour ) ); PyDict_SetItemString( dict, "minutes", PyGetInt( simulation::Time.data().wMinute ) ); PyDict_SetItemString( dict, "seconds", PyGetInt( simulation::Time.second() ) ); @@ -752,11 +757,20 @@ void TTrain::OnCommand_aidriverdisable( TTrain *Train, command_data const &Comma } } +auto const EU07_CONTROLLER_BASERETURNDELAY { 0.5f }; +auto const EU07_CONTROLLER_KEYBOARDETURNDELAY { 1.5f }; + void TTrain::OnCommand_mastercontrollerincrease( TTrain *Train, command_data const &Command ) { if( Command.action != GLFW_RELEASE ) { // on press or hold Train->mvControlled->IncMainCtrl( 1 ); + Train->m_mastercontrollerinuse = true; + } + else if (Command.action == GLFW_RELEASE) { + // release + Train->m_mastercontrollerinuse = false; + Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_KEYBOARDETURNDELAY + EU07_CONTROLLER_BASERETURNDELAY; } } @@ -773,6 +787,12 @@ void TTrain::OnCommand_mastercontrollerdecrease( TTrain *Train, command_data con if( Command.action != GLFW_RELEASE ) { // on press or hold Train->mvControlled->DecMainCtrl( 1 ); + Train->m_mastercontrollerinuse = true; + } + else if (Command.action == GLFW_RELEASE) { + // release + Train->m_mastercontrollerinuse = false; + Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_KEYBOARDETURNDELAY + EU07_CONTROLLER_BASERETURNDELAY; } } @@ -789,6 +809,12 @@ void TTrain::OnCommand_mastercontrollerset( TTrain *Train, command_data const &C if( Command.action != GLFW_RELEASE ) { // on press or hold Train->set_master_controller( Command.param1 ); + Train->m_mastercontrollerinuse = true; + } + else { + // release + Train->m_mastercontrollerinuse = false; + Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_BASERETURNDELAY; // NOTE: keyboard return delay is omitted for other input sources } } @@ -891,6 +917,9 @@ void TTrain::OnCommand_secondcontrollerdecreasefast( TTrain *Train, command_data } void TTrain::OnCommand_secondcontrollerset( TTrain *Train, command_data const &Command ) { + if (Command.action == GLFW_RELEASE) + return; + auto const targetposition { std::min( Command.param1, Train->mvControlled->ScndCtrlPosNo ) }; while( ( targetposition < Train->mvControlled->GetVirtualScndPos() ) && ( true == Train->mvControlled->DecScndCtrl( 1 ) ) ) { @@ -952,10 +981,13 @@ void TTrain::OnCommand_independentbrakedecreasefast( TTrain *Train, command_data void TTrain::OnCommand_independentbrakeset( TTrain *Train, command_data const &Command ) { - Train->mvControlled->LocalBrakePosA = ( - clamp( - Command.param1, - 0.0, 1.0 ) ); + if( Command.action != GLFW_RELEASE ) { + + Train->mvControlled->LocalBrakePosA = ( + clamp( + Command.param1, + 0.0, 1.0 ) ); + } /* Train->mvControlled->LocalBrakePos = ( std::round( @@ -1023,8 +1055,7 @@ void TTrain::OnCommand_trainbrakedecrease( TTrain *Train, command_data const &Co Train->mvOccupied->BrakeLevelAdd( -Global.brake_speed * Command.time_delta * Train->mvOccupied->BrakeCtrlPosNo ); else if (Command.action == GLFW_PRESS && Train->mvOccupied->BrakeHandle != TBrakeHandle::FV4a) Train->set_train_brake( Train->mvOccupied->fBrakeCtrlPos - Global.fBrakeStep ); - - if (Command.action == GLFW_RELEASE) { + else if (Command.action == GLFW_RELEASE) { // release if( ( Train->mvOccupied->BrakeCtrlPos == -1 ) && ( Train->mvOccupied->BrakeHandle == TBrakeHandle::FVel6 ) @@ -1038,13 +1069,25 @@ void TTrain::OnCommand_trainbrakedecrease( TTrain *Train, command_data const &Co void TTrain::OnCommand_trainbrakeset( TTrain *Train, command_data const &Command ) { - Train->mvOccupied->BrakeLevelSet( - interpolate( - Train->mvOccupied->Handle->GetPos( bh_MIN ), - Train->mvOccupied->Handle->GetPos( bh_MAX ), - clamp( - Command.param1, - 0.0, 1.0 ) ) ); + if( Command.action != GLFW_RELEASE ) { + // press or hold + Train->mvOccupied->BrakeLevelSet( + interpolate( + Train->mvOccupied->Handle->GetPos( bh_MIN ), + Train->mvOccupied->Handle->GetPos( bh_MAX ), + clamp( + Command.param1, + 0.0, 1.0 ) ) ); + } else { + // release + if( ( Train->mvOccupied->BrakeCtrlPos == -1 ) + && ( Train->mvOccupied->BrakeHandle == TBrakeHandle::FVel6 ) + && ( Train->DynamicObject->Controller != AIdriver ) + && ( Global.iFeedbackMode < 3 ) ) { + // Odskakiwanie hamulce EP + Train->set_train_brake( 0 ); + } + } } void TTrain::OnCommand_trainbrakecharging( TTrain *Train, command_data const &Command ) { @@ -1326,13 +1369,11 @@ void TTrain::OnCommand_trainbrakeoperationmodeincrease(TTrain *Train, command_da if( ( ( Train->mvOccupied->BrakeOpModeFlag << 1 ) & Train->mvOccupied->BrakeOpModes ) != 0 ) { // next mode Train->mvOccupied->BrakeOpModeFlag <<= 1; - // audio feedback - Train->dsbPneumaticSwitch.play(); // visual feedback Train->ggBrakeOperationModeCtrl.UpdateValue( Train->mvOccupied->BrakeOpModeFlag > 0 ? std::log2( Train->mvOccupied->BrakeOpModeFlag ) : - 0 ); + 0 ); // audio fallback } } } @@ -1344,8 +1385,6 @@ void TTrain::OnCommand_trainbrakeoperationmodedecrease(TTrain *Train, command_da if( ( ( Train->mvOccupied->BrakeOpModeFlag >> 1 ) & Train->mvOccupied->BrakeOpModes ) != 0 ) { // previous mode Train->mvOccupied->BrakeOpModeFlag >>= 1; - // audio feedback - Train->dsbPneumaticSwitch.play(); // visual feedback Train->ggBrakeOperationModeCtrl.UpdateValue( Train->mvOccupied->BrakeOpModeFlag > 0 ? @@ -3678,7 +3717,7 @@ void TTrain::OnCommand_redmarkerstoggle( TTrain *Train, command_data const &Comm int const CouplNr { clamp( vehicle->DirectionGet() - * ( LengthSquared3( vehicle->HeadPosition() - Global.pCamera.Pos ) > LengthSquared3( vehicle->RearPosition() - Global.pCamera.Pos ) ? + * ( Math3D::LengthSquared3( vehicle->HeadPosition() - Global.pCamera.Pos ) > Math3D::LengthSquared3( vehicle->RearPosition() - Global.pCamera.Pos ) ? 1 : -1 ), 0, 1 ) }; // z [-1,1] zrobić [0,1] @@ -3704,7 +3743,7 @@ void TTrain::OnCommand_endsignalstoggle( TTrain *Train, command_data const &Comm int const CouplNr { clamp( vehicle->DirectionGet() - * ( LengthSquared3( vehicle->HeadPosition() - Global.pCamera.Pos ) > LengthSquared3( vehicle->RearPosition() - Global.pCamera.Pos ) ? + * ( Math3D::LengthSquared3( vehicle->HeadPosition() - Global.pCamera.Pos ) > Math3D::LengthSquared3( vehicle->RearPosition() - Global.pCamera.Pos ) ? 1 : -1 ), 0, 1 ) }; // z [-1,1] zrobić [0,1] @@ -3773,7 +3812,7 @@ void TTrain::OnCommand_interiorlighttoggle( TTrain *Train, command_data const &C if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - if( false == Train->bCabLight ) { + if( false == Train->Cabine[Train->iCabn].bLight ) { // turn on OnCommand_interiorlightenable( Train, Command ); } @@ -3795,11 +3834,18 @@ void TTrain::OnCommand_interiorlightenable( TTrain *Train, command_data const &C } // visual feedback Train->ggCabLightButton.UpdateValue( 1.0, Train->dsbSwitch ); - - if( true == Train->bCabLight ) { return; } // already enabled - - Train->bCabLight = true; Train->btCabLight.Turn( true ); + // store lighting switch states + if( false == Train->DynamicObject->JointCabs ) { + // vehicles with separate cabs get separate lighting switch states + Train->Cabine[ Train->iCabn ].bLight = true; + } + else { + // joint virtual cabs share lighting switch states + for( auto &cab : Train->Cabine ) { + cab.bLight = true; + } + } } } @@ -3814,11 +3860,18 @@ void TTrain::OnCommand_interiorlightdisable( TTrain *Train, command_data const & } // visual feedback Train->ggCabLightButton.UpdateValue( 0.0, Train->dsbSwitch ); - - if( false == Train->bCabLight ) { return; } // already disabled - - Train->bCabLight = false; Train->btCabLight.Turn( false ); + // store lighting switch states + if( false == Train->DynamicObject->JointCabs ) { + // vehicles with separate cabs get separate lighting switch states + Train->Cabine[ Train->iCabn ].bLight = false; + } + else { + // joint virtual cabs share lighting switch states + for( auto &cab : Train->Cabine ) { + cab.bLight = false; + } + } } } @@ -3826,7 +3879,7 @@ void TTrain::OnCommand_interiorlightdimtoggle( TTrain *Train, command_data const if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - if( false == Train->bCabLightDim ) { + if( false == Train->Cabine[ Train->iCabn ].bLightDim ) { // turn on OnCommand_interiorlightdimenable( Train, Command ); } @@ -3848,10 +3901,17 @@ void TTrain::OnCommand_interiorlightdimenable( TTrain *Train, command_data const } // visual feedback Train->ggCabLightDimButton.UpdateValue( 1.0, Train->dsbSwitch ); - - if( true == Train->bCabLightDim ) { return; } // already enabled - - Train->bCabLightDim = true; + // store lighting switch states + if( false == Train->DynamicObject->JointCabs ) { + // vehicles with separate cabs get separate lighting switch states + Train->Cabine[ Train->iCabn ].bLightDim = true; + } + else { + // joint virtual cabs share lighting switch states + for( auto &cab : Train->Cabine ) { + cab.bLightDim = true; + } + } } } @@ -3866,10 +3926,17 @@ void TTrain::OnCommand_interiorlightdimdisable( TTrain *Train, command_data cons } // visual feedback Train->ggCabLightDimButton.UpdateValue( 0.0, Train->dsbSwitch ); - - if( false == Train->bCabLightDim ) { return; } // already disabled - - Train->bCabLightDim = false; + // store lighting switch states + if( false == Train->DynamicObject->JointCabs ) { + // vehicles with separate cabs get separate lighting switch states + Train->Cabine[ Train->iCabn ].bLightDim = false; + } + else { + // joint virtual cabs share lighting switch states + for( auto &cab : Train->Cabine ) { + cab.bLightDim = false; + } + } } } @@ -4867,16 +4934,23 @@ bool TTrain::Update( double const Deltatime ) || ( mvOccupied->TrainType == dt_EP05 ) ) { // dla ET40 i EU05 automatyczne cofanie nastawnika - i tak nie będzie to działać dobrze... // TODO: use deltatime to stabilize speed +/* if( false == ( ( input::command == user_command::mastercontrollerset ) || ( input::command == user_command::mastercontrollerincrease ) || ( input::command == user_command::mastercontrollerdecrease ) ) ) { - if( mvOccupied->MainCtrlPos > mvOccupied->MainCtrlActualPos ) { - mvOccupied->DecMainCtrl( 1 ); - } - else if( mvOccupied->MainCtrlPos < mvOccupied->MainCtrlActualPos ) { - // Ra 15-01: a to nie miało być tylko cofanie? - mvOccupied->IncMainCtrl( 1 ); +*/ + if( false == m_mastercontrollerinuse ) { + m_mastercontrollerreturndelay -= Deltatime; + if( m_mastercontrollerreturndelay < 0.f ) { + m_mastercontrollerreturndelay = EU07_CONTROLLER_BASERETURNDELAY; + if( mvOccupied->MainCtrlPos > mvOccupied->MainCtrlActualPos ) { + mvOccupied->DecMainCtrl( 1 ); + } + else if( mvOccupied->MainCtrlPos < mvOccupied->MainCtrlActualPos ) { + // Ra 15-01: a to nie miało być tylko cofanie? + mvOccupied->IncMainCtrl( 1 ); + } } } } @@ -5068,17 +5142,6 @@ bool TTrain::Update( double const Deltatime ) Console::ValueSet(6, fTachoVelocity); } #endif - // hunter-091012: swiatlo - if (bCabLight == true) - { - if (bCabLightDim == true) - iCabLightFlag = 1; - else - iCabLightFlag = 2; - } - else - iCabLightFlag = 0; - //------------------ // hunter-261211: nadmiarowy przetwornicy i ogrzewania // Ra 15-01: to musi stąd wylecieć - zależności nie mogą być w kabinie @@ -5418,8 +5481,8 @@ bool TTrain::Update( double const Deltatime ) btLampkaRadioStop.Turn( mvOccupied->Radio && mvOccupied->RadioStopFlag ); btLampkaHamulecReczny.Turn(mvOccupied->ManualBrakePos > 0); // NBMX wrzesien 2003 - drzwi oraz sygnał odjazdu - btLampkaDoorLeft.Turn(mvOccupied->DoorLeftOpened); - btLampkaDoorRight.Turn(mvOccupied->DoorRightOpened); + btLampkaDoorLeft.Turn( DynamicObject->dDoorMoveL > 0.0 );// mvOccupied->DoorLeftOpened); + btLampkaDoorRight.Turn( DynamicObject->dDoorMoveR > 0.0 ); //mvOccupied ->DoorRightOpened); btLampkaBlokadaDrzwi.Turn(mvOccupied->DoorBlockedFlag()); btLampkaDoorLockOff.Turn( false == mvOccupied->DoorLockEnabled ); btLampkaDepartureSignal.Turn( mvControlled->DepartureSignal ); @@ -5728,7 +5791,12 @@ bool TTrain::Update( double const Deltatime ) InstrumentLightType == 0 ? mvControlled->Battery || mvControlled->ConverterFlag : InstrumentLightType == 1 ? mvControlled->Mains : InstrumentLightType == 2 ? mvControlled->ConverterFlag : + InstrumentLightType == 3 ? mvControlled->Battery || mvControlled->ConverterFlag : false ) }; + if( InstrumentLightType == 3 ) { + // TODO: link the light state with the state of the master key + InstrumentLightActive = true; + } btInstrumentLight.Turn( InstrumentLightActive && lightpower ); btDashboardLight.Turn( DashboardLightActive && lightpower ); btTimetableLight.Turn( TimetableLightActive && lightpower ); @@ -5786,7 +5854,9 @@ bool TTrain::Update( double const Deltatime ) ggHornLowButton.Update(); ggHornHighButton.Update(); ggWhistleButton.Update(); - ggHelperButton.UpdateValue(DynamicObject->Mechanik->HelperState); + if( DynamicObject->Mechanik != nullptr ) { + ggHelperButton.UpdateValue( DynamicObject->Mechanik->HelperState ); + } ggHelperButton.Update(); for( auto &universal : ggUniversals ) { universal.Update(); @@ -5816,43 +5886,34 @@ bool TTrain::Update( double const Deltatime ) btHaslerCurrent.Turn(DynamicObject->MoverParameters->Im != 0.0); // prąd na silnikach // calculate current level of interior illumination - // TODO: organize it along with rest of train update in a more sensible arrangement - auto interiorlightlevel { 0.f }; - switch( iCabLightFlag ) // Ra: uzeleżnic od napięcia w obwodzie sterowania - { // hunter-091012: uzaleznienie jasnosci od przetwornicy - case 0: { - //światło wewnętrzne zgaszone - interiorlightlevel = 0.0f; - break; - } - case 1: { - //światło wewnętrzne przygaszone (255 216 176) - auto const converteractive { ( - ( mvOccupied->ConverterFlag ) - || ( ( ( mvOccupied->Couplers[ side::front ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::front ].Connected->ConverterFlag ) - || ( ( ( mvOccupied->Couplers[ side::rear ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::rear ].Connected->ConverterFlag ) ) }; + { + // TODO: organize it along with rest of train update in a more sensible arrangement + auto const converteractive{ ( + ( mvOccupied->ConverterFlag ) + || ( ( ( mvOccupied->Couplers[ side::front ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::front ].Connected->ConverterFlag ) + || ( ( ( mvOccupied->Couplers[ side::rear ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::rear ].Connected->ConverterFlag ) ) }; + // Ra: uzeleżnic od napięcia w obwodzie sterowania + // hunter-091012: uzaleznienie jasnosci od przetwornicy + int cabidx { 0 }; + for( auto &cab : Cabine ) { - interiorlightlevel = ( - converteractive ? - 0.4f : - 0.2f ); - break; - } - case 2: { - //światło wewnętrzne zapalone (255 216 176) - auto const converteractive { ( - ( mvOccupied->ConverterFlag ) - || ( ( ( mvOccupied->Couplers[ side::front ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::front ].Connected->ConverterFlag ) - || ( ( ( mvOccupied->Couplers[ side::rear ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::rear ].Connected->ConverterFlag ) ) }; + auto const cablightlevel = + ( ( cab.bLight == false ) ? 0.f : + ( cab.bLightDim == true ) ? 0.4f : + 1.f ) + * ( converteractive ? 1.f : 0.5f ); - interiorlightlevel = ( - converteractive ? - 1.0f : - 0.5f ); - break; + if( cab.LightLevel != cablightlevel ) { + cab.LightLevel = cablightlevel; + DynamicObject->set_cab_lights( cabidx, cab.LightLevel ); + } + if( cabidx == iCabn ) { + DynamicObject->InteriorLightLevel = cablightlevel; + } + + ++cabidx; } } - DynamicObject->set_cab_lights( interiorlightlevel ); // anti slip system activation, maintained while the control button is down if( mvOccupied->BrakeSystem != TBrakeSystem::ElectroPneumatic ) { @@ -5863,7 +5924,8 @@ bool TTrain::Update( double const Deltatime ) // NOTE: crude way to have the pantographs go back up if they're dropped due to insufficient pressure etc // TODO: rework it into something more elegant, when redoing the whole consist/unit/cab etc arrangement - if( DynamicObject->Mechanik != nullptr && !DynamicObject->Mechanik->AIControllFlag ) { + if( ( DynamicObject->Mechanik == nullptr ) + || ( false == DynamicObject->Mechanik->AIControllFlag ) ) { // don't mess with the ai driving, at least not while switches don't follow ai-set vehicle state if( ( mvControlled->Battery ) || ( mvControlled->ConverterFlag ) ) { @@ -6244,15 +6306,16 @@ bool TTrain::CabChange(int iDirection) else { // jeśli pojazd prowadzony ręcznie albo wcale (wagon) DynamicObject->MoverParameters->CabDeactivisation(); - if (DynamicObject->MoverParameters->ChangeCab(iDirection)) - if (InitializeCab(DynamicObject->MoverParameters->ActiveCab, - DynamicObject->asBaseDir + DynamicObject->MoverParameters->TypeName + - ".mmd")) - { // zmiana kabiny w ramach tego samego pojazdu + if( DynamicObject->MoverParameters->ChangeCab( iDirection ) ) { + if( InitializeCab( + DynamicObject->MoverParameters->ActiveCab, + DynamicObject->asBaseDir + DynamicObject->MoverParameters->TypeName + ".mmd" ) ) { + // zmiana kabiny w ramach tego samego pojazdu DynamicObject->MoverParameters->CabActivisation(); // załączenie rozrządu (wirtualne kabiny) DynamicObject->Mechanik->CheckVehicles( Change_direction ); return true; // udało się zmienić kabinę } + } // aktywizacja poprzedniej, bo jeszcze nie wiadomo, czy jakiś pojazd jest DynamicObject->MoverParameters->CabActivisation(); } @@ -6548,10 +6611,10 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) >> viewangle.y // yaw first, then pitch >> viewangle.x; pMechViewAngle = glm::radians( viewangle ); -/* - Global.pCamera.Pitch = pMechViewAngle.x; - Global.pCamera.Yaw = pMechViewAngle.y; -*/ + + Global.pCamera.Angle.x = pMechViewAngle.x; + Global.pCamera.Angle.y = pMechViewAngle.y; + parser.getTokens(); parser >> token; } @@ -6632,11 +6695,13 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) } clear_cab_controls(); } +/* if (nullptr == DynamicObject->mdKabina) { // don't bother with other parts until the cab is initialised continue; } +*/ else if (true == initialize_gauge(parser, token, cabindex)) { // matched the token, grab the next one @@ -6656,7 +6721,7 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) >> submodelname >> renderername; - auto const *submodel { DynamicObject->mdKabina->GetFromName( submodelname ) }; + auto const *submodel { ( DynamicObject->mdKabina ? DynamicObject->mdKabina->GetFromName( submodelname ) : nullptr ) }; if( submodel == nullptr ) { WriteLog( "Python Screen: submodel " + submodelname + " not found - Ignoring screen" ); continue; @@ -6688,8 +6753,10 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) { return false; } +/* if (DynamicObject->mdKabina) { +*/ // configure placement of sound emitters which aren't bound with any device model, and weren't placed manually // try first to bind sounds to location of possible devices if( dsbReverserKey.offset() == nullvector ) { @@ -6741,28 +6808,37 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) } } - DynamicObject->mdKabina->Init(); // obrócenie modelu oraz optymalizacja, również zapisanie binarnego - set_cab_controls(); + if( DynamicObject->mdKabina ) + DynamicObject->mdKabina->Init(); // obrócenie modelu oraz optymalizacja, również zapisanie binarnego + set_cab_controls( NewCabNo < 0 ? 2 : NewCabNo ); +/* return true; } return (token == "none"); +*/ + return true; } Math3D::vector3 TTrain::MirrorPosition(bool lewe) { // zwraca współrzędne widoku kamery z lusterka switch (iCabn) { - case 1: // przednia (1) - return DynamicObject->mMatrix * - Math3D::vector3(lewe ? Cabine[iCabn].CabPos2.x : Cabine[iCabn].CabPos1.x, - 1.5 + Cabine[iCabn].CabPos1.y, Cabine[iCabn].CabPos2.z); case 2: // tylna (-1) return DynamicObject->mMatrix * - Math3D::vector3(lewe ? Cabine[iCabn].CabPos1.x : Cabine[iCabn].CabPos2.x, - 1.5 + Cabine[iCabn].CabPos1.y, Cabine[iCabn].CabPos1.z); + Math3D::vector3( + mvOccupied->Dim.W * ( lewe ? -0.5 : 0.5 ) + 0.2 * ( lewe ? -1 : 1 ), + 1.5 + Cabine[iCabn].CabPos1.y, + Cabine[iCabn].CabPos1.z); + case 1: // przednia (1) + default: + return DynamicObject->mMatrix * + Math3D::vector3( + mvOccupied->Dim.W * ( lewe ? 0.5 : -0.5 ) + 0.2 * ( lewe ? 1 : -1 ), + 1.5 + Cabine[iCabn].CabPos1.y, + Cabine[iCabn].CabPos2.z); } - return DynamicObject->GetPosition(); // współrzędne środka pojazdu +// return DynamicObject->GetPosition(); // współrzędne środka pojazdu }; void TTrain::DynamicSet(TDynamicObject *d) @@ -7112,20 +7188,20 @@ void TTrain::clear_cab_controls() } // NOTE: we can get rid of this function once we have per-cab persistent state -void TTrain::set_cab_controls() { +void TTrain::set_cab_controls( int const Cab ) { // switches // battery if( true == mvOccupied->Battery ) { - ggBatteryButton.PutValue( 1.0 ); + ggBatteryButton.PutValue( 1.f ); } // motor connectors ggStLinOffButton.PutValue( ( mvControlled->StLinSwitchOff ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); // radio if( true == mvOccupied->Radio ) { - ggRadioButton.PutValue( 1.0 ); + ggRadioButton.PutValue( 1.f ); } ggRadioChannelSelector.PutValue( iRadioChannel - 1 ); // pantographs @@ -7133,96 +7209,96 @@ void TTrain::set_cab_controls() { if( ggPantFrontButton.SubModel ) { ggPantFrontButton.PutValue( ( mvControlled->PantFrontUp ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); } if( ggPantFrontButtonOff.SubModel ) { ggPantFrontButtonOff.PutValue( ( mvControlled->PantFrontUp ? - 0.0 : - 1.0 ) ); + 0.f : + 1.f ) ); } // NOTE: currently we animate the selectable pantograph control for both pantographs // TODO: implement actual selection control, and refactor handling this control setup in a separate method if( ggPantSelectedButton.SubModel ) { ggPantSelectedButton.PutValue( ( mvControlled->PantFrontUp ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); } if( ggPantSelectedDownButton.SubModel ) { ggPantSelectedDownButton.PutValue( ( mvControlled->PantFrontUp ? - 0.0 : - 1.0 ) ); + 0.f : + 1.f ) ); } } if( mvOccupied->PantSwitchType != "impulse" ) { if( ggPantRearButton.SubModel ) { ggPantRearButton.PutValue( ( mvControlled->PantRearUp ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); } if( ggPantRearButtonOff.SubModel ) { ggPantRearButtonOff.PutValue( ( mvControlled->PantRearUp ? - 0.0 : - 1.0 ) ); + 0.f : + 1.f ) ); } // NOTE: currently we animate the selectable pantograph control for both pantographs // TODO: implement actual selection control, and refactor handling this control setup in a separate method if( ggPantSelectedButton.SubModel ) { ggPantSelectedButton.PutValue( ( mvControlled->PantRearUp ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); } if( ggPantSelectedDownButton.SubModel ) { ggPantSelectedDownButton.PutValue( ( mvControlled->PantRearUp ? - 0.0 : - 1.0 ) ); + 0.f : + 1.f ) ); } } // auxiliary compressor ggPantCompressorValve.PutValue( mvControlled->bPantKurek3 ? - 0.0 : // default setting is pantographs connected with primary tank - 1.0 ); + 0.f : // default setting is pantographs connected with primary tank + 1.f ); ggPantCompressorButton.PutValue( mvControlled->PantCompFlag ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // converter if( mvOccupied->ConvSwitchType != "impulse" ) { ggConverterButton.PutValue( mvControlled->ConverterAllow ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } ggConverterLocalButton.PutValue( mvControlled->ConverterAllowLocal ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // compressor ggCompressorButton.PutValue( mvControlled->CompressorAllow ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); ggCompressorLocalButton.PutValue( mvControlled->CompressorAllowLocal ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // motor overload relay threshold / shunt mode ggMaxCurrentCtrl.PutValue( ( true == mvControlled->ShuntModeAllow ? ( true == mvControlled->ShuntMode ? - 1.0 : - 0.0 ) : + 1.f : + 0.f ) : ( mvControlled->Imax == mvControlled->ImaxHi ? - 1.0 : - 0.0 ) ) ); + 1.f : + 0.f ) ) ); // lights ggLightsButton.PutValue( mvOccupied->LightsPos - 1 ); @@ -7232,84 +7308,84 @@ void TTrain::set_cab_controls() { side::rear ); if( ( DynamicObject->iLights[ vehicleside ] & light::headlight_left ) != 0 ) { - ggLeftLightButton.PutValue( 1.0 ); + ggLeftLightButton.PutValue( 1.f ); } if( ( DynamicObject->iLights[ vehicleside ] & light::headlight_right ) != 0 ) { - ggRightLightButton.PutValue( 1.0 ); + ggRightLightButton.PutValue( 1.f ); } if( ( DynamicObject->iLights[ vehicleside ] & light::headlight_upper ) != 0 ) { - ggUpperLightButton.PutValue( 1.0 ); + ggUpperLightButton.PutValue( 1.f ); } if( ( DynamicObject->iLights[ vehicleside ] & light::redmarker_left ) != 0 ) { if( ggLeftEndLightButton.SubModel != nullptr ) { - ggLeftEndLightButton.PutValue( 1.0 ); + ggLeftEndLightButton.PutValue( 1.f ); } else { - ggLeftLightButton.PutValue( -1.0 ); + ggLeftLightButton.PutValue( -1.f ); } } if( ( DynamicObject->iLights[ vehicleside ] & light::redmarker_right ) != 0 ) { if( ggRightEndLightButton.SubModel != nullptr ) { - ggRightEndLightButton.PutValue( 1.0 ); + ggRightEndLightButton.PutValue( 1.f ); } else { - ggRightLightButton.PutValue( -1.0 ); + ggRightLightButton.PutValue( -1.f ); } } if( true == DynamicObject->DimHeadlights ) { - ggDimHeadlightsButton.PutValue( 1.0 ); + ggDimHeadlightsButton.PutValue( 1.f ); } // cab lights - if( true == bCabLight ) { - ggCabLightButton.PutValue( 1.0 ); + if( true == Cabine[Cab].bLight ) { + ggCabLightButton.PutValue( 1.f ); } - if( true == bCabLightDim ) { - ggCabLightDimButton.PutValue( 1.0 ); + if( true == Cabine[Cab].bLightDim ) { + ggCabLightDimButton.PutValue( 1.f ); } ggInstrumentLightButton.PutValue( ( InstrumentLightActive ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); ggDashboardLightButton.PutValue( ( DashboardLightActive ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); ggTimetableLightButton.PutValue( ( TimetableLightActive ? - 1.0 : - 0.0 ) ); + 1.f : + 0.f ) ); // doors // NOTE: we're relying on the cab models to have switches reversed for the rear cab(?) - ggDoorLeftButton.PutValue( mvOccupied->DoorLeftOpened ? 1.0 : 0.0 ); - ggDoorRightButton.PutValue( mvOccupied->DoorRightOpened ? 1.0 : 0.0 ); + ggDoorLeftButton.PutValue( /*mvOccupied->DoorLeftOpened*/ DynamicObject->dDoorMoveL > 0.0 ? 1.f : 0.f ); + ggDoorRightButton.PutValue( /*mvOccupied->DoorRightOpened*/ DynamicObject->dDoorMoveR > 0.0 ? 1.f : 0.f ); // door lock ggDoorSignallingButton.PutValue( mvOccupied->DoorLockEnabled ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // heating if( true == mvControlled->Heating ) { - ggTrainHeatingButton.PutValue( 1.0 ); + ggTrainHeatingButton.PutValue( 1.f ); } // brake acting time if( ggBrakeProfileCtrl.SubModel != nullptr ) { ggBrakeProfileCtrl.PutValue( ( ( mvOccupied->BrakeDelayFlag & bdelay_R ) != 0 ? - 2.0 : + 2.f : mvOccupied->BrakeDelayFlag - 1 ) ); } if( ggBrakeProfileG.SubModel != nullptr ) { ggBrakeProfileG.PutValue( mvOccupied->BrakeDelayFlag == bdelay_G ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } if( ggBrakeProfileR.SubModel != nullptr ) { ggBrakeProfileR.PutValue( ( mvOccupied->BrakeDelayFlag & bdelay_R ) != 0 ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } if (ggBrakeOperationModeCtrl.SubModel != nullptr) { ggBrakeOperationModeCtrl.PutValue( @@ -7320,75 +7396,75 @@ void TTrain::set_cab_controls() { // alarm chain ggAlarmChain.PutValue( mvControlled->AlarmChainFlag ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // brake signalling ggSignallingButton.PutValue( mvControlled->Signalling ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // multiple-unit current indicator source ggNextCurrentButton.PutValue( ShowNextCurrent ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // water pump ggWaterPumpBreakerButton.PutValue( mvControlled->WaterPump.breaker ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); if( ggWaterPumpButton.type() != TGaugeType::push ) { ggWaterPumpButton.PutValue( mvControlled->WaterPump.is_enabled ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } // water heater ggWaterHeaterBreakerButton.PutValue( mvControlled->WaterHeater.breaker ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); ggWaterHeaterButton.PutValue( mvControlled->WaterHeater.is_enabled ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); ggWaterCircuitsLinkButton.PutValue( mvControlled->WaterCircuitsLink ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); // fuel pump if( ggFuelPumpButton.type() != TGaugeType::push ) { ggFuelPumpButton.PutValue( mvControlled->FuelPump.is_enabled ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } // oil pump if( ggOilPumpButton.type() != TGaugeType::push ) { ggOilPumpButton.PutValue( mvControlled->OilPump.is_enabled ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } // traction motor fans if( ggMotorBlowersFrontButton.type() != TGaugeType::push ) { ggMotorBlowersFrontButton.PutValue( mvControlled->MotorBlowers[side::front].is_enabled ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } if( ggMotorBlowersRearButton.type() != TGaugeType::push ) { ggMotorBlowersRearButton.PutValue( mvControlled->MotorBlowers[side::rear].is_enabled ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } if( ggMotorBlowersAllOffButton.type() != TGaugeType::push ) { ggMotorBlowersAllOffButton.PutValue( ( mvControlled->MotorBlowers[side::front].is_disabled || mvControlled->MotorBlowers[ side::front ].is_disabled ) ? - 1.0 : - 0.0 ); + 1.f : + 0.f ); } // we reset all indicators, as they're set during the update pass @@ -7481,33 +7557,37 @@ bool TTrain::initialize_button(cParser &Parser, std::string const &Label, int co }; auto lookup = lights.find( Label ); if( lookup != lights.end() ) { - lookup->second.Load( Parser, DynamicObject, DynamicObject->mdKabina ); + lookup->second.Load( Parser, DynamicObject ); } else if( Label == "i-instrumentlight:" ) { - btInstrumentLight.Load( Parser, DynamicObject, DynamicObject->mdKabina, DynamicObject->mdModel ); + btInstrumentLight.Load( Parser, DynamicObject ); InstrumentLightType = 0; } - else if( Label == "i-instrumentlight_M:" ) { - btInstrumentLight.Load( Parser, DynamicObject, DynamicObject->mdKabina, DynamicObject->mdModel ); + else if( Label == "i-instrumentlight_m:" ) { + btInstrumentLight.Load( Parser, DynamicObject ); InstrumentLightType = 1; } - else if( Label == "i-instrumentlight_C:" ) { - btInstrumentLight.Load( Parser, DynamicObject, DynamicObject->mdKabina, DynamicObject->mdModel ); + else if( Label == "i-instrumentlight_c:" ) { + btInstrumentLight.Load( Parser, DynamicObject ); InstrumentLightType = 2; } + else if( Label == "i-instrumentlight_a:" ) { + btInstrumentLight.Load( Parser, DynamicObject ); + InstrumentLightType = 3; + } else if (Label == "i-doors:") { int i = Parser.getToken() - 1; auto &button = Cabine[Cabindex].Button(-1); // pierwsza wolna lampka - button.Load(Parser, DynamicObject, DynamicObject->mdKabina); + button.Load(Parser, DynamicObject); button.AssignBool(bDoors[0] + 3 * i); } /* else if( Label == "i-malfunction:" ) { // generic malfunction indicator auto &button = Cabine[ Cabindex ].Button( -1 ); // pierwsza wolna gałka - button.Load( Parser, DynamicObject, DynamicObject->mdKabina ); + button.Load( Parser, DynamicObject ); button.AssignBool( &mvOccupied->dizel_heat.PA ); } */ @@ -7627,19 +7707,19 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con }; auto lookup = gauges.find( Label ); if( lookup != gauges.end() ) { - lookup->second.Load( Parser, DynamicObject, DynamicObject->mdKabina ); + lookup->second.Load( Parser, DynamicObject); m_controlmapper.insert( lookup->second, lookup->first ); } // ABu 090305: uniwersalne przyciski lub inne rzeczy else if( Label == "mainctrlact:" ) { - ggMainCtrlAct.Load( Parser, DynamicObject, DynamicObject->mdKabina, DynamicObject->mdModel ); + ggMainCtrlAct.Load( Parser, DynamicObject); } // SEKCJA WSKAZNIKOW else if ((Label == "tachometer:") || (Label == "tachometerb:")) { // predkosciomierz wskaźnikowy z szarpaniem auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fTachoVelocityJump); // bind tachometer sound location to the meter if( dsbHasler.offset() == glm::vec3() ) { @@ -7650,7 +7730,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { // predkosciomierz wskaźnikowy bez szarpania auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fTachoVelocity); // bind tachometer sound location to the meter if( dsbHasler.offset() == glm::vec3() ) { @@ -7661,7 +7741,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { // predkosciomierz cyfrowy auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fTachoVelocity); // bind tachometer sound location to the meter if( dsbHasler.offset() == glm::vec3() ) { @@ -7672,28 +7752,28 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { // 1szy amperomierz auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(fHCurrent + 1); } else if ((Label == "hvcurrent2:") || (Label == "hvcurrent2b:")) { // 2gi amperomierz auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(fHCurrent + 2); } else if ((Label == "hvcurrent3:") || (Label == "hvcurrent3b:")) { // 3ci amperomierz auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałska - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(fHCurrent + 3); } else if ((Label == "hvcurrent:") || (Label == "hvcurrentb:")) { // amperomierz calkowitego pradu auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(fHCurrent); } else if (Label == "eimscreen:") @@ -7703,7 +7783,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con Parser.getTokens(2, false); Parser >> i >> j; auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fEIMParams[i][j]); } else if (Label == "brakes:") @@ -7713,7 +7793,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con Parser.getTokens(2, false); Parser >> i >> j; auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fPress[i - 1][j]); } else if ((Label == "brakepress:") || (Label == "brakepressb:")) @@ -7721,79 +7801,91 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con // manometr cylindrow hamulcowych // Ra 2014-08: przeniesione do TCab auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina, nullptr, 0.1); + gauge.Load(Parser, DynamicObject, 0.1); gauge.AssignDouble(&mvOccupied->BrakePress); } else if ((Label == "pipepress:") || (Label == "pipepressb:")) { // manometr przewodu hamulcowego auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina, nullptr, 0.1); + gauge.Load(Parser, DynamicObject, 0.1); gauge.AssignDouble(&mvOccupied->PipePress); } + else if( Label == "scndpress:" ) { + // manometr przewodu hamulcowego + auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka + gauge.Load( Parser, DynamicObject, 0.1 ); + gauge.AssignDouble( &mvOccupied->ScndPipePress ); + } else if (Label == "limpipepress:") { // manometr zbiornika sterujacego zaworu maszynisty - ggZbS.Load(Parser, DynamicObject, DynamicObject->mdKabina, nullptr, 0.1); + ggZbS.Load(Parser, DynamicObject, 0.1); } else if (Label == "cntrlpress:") { // manometr zbiornika kontrolnego/rorzďż˝du auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina, nullptr, 0.1); + gauge.Load(Parser, DynamicObject, 0.1); gauge.AssignDouble(&mvControlled->PantPress); } else if ((Label == "compressor:") || (Label == "compressorb:")) { // manometr sprezarki/zbiornika glownego auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina, nullptr, 0.1); + gauge.Load(Parser, DynamicObject, 0.1); gauge.AssignDouble(&mvOccupied->Compressor); } else if( Label == "oilpress:" ) { // oil pressure auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka - gauge.Load( Parser, DynamicObject, DynamicObject->mdKabina, nullptr ); + gauge.Load( Parser, DynamicObject ); gauge.AssignFloat( &mvControlled->OilPump.pressure ); } else if( Label == "oiltemp:" ) { // oil temperature auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka - gauge.Load( Parser, DynamicObject, DynamicObject->mdKabina, nullptr ); + gauge.Load( Parser, DynamicObject ); gauge.AssignFloat( &mvControlled->dizel_heat.To ); } else if( Label == "water1temp:" ) { // main circuit water temperature auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka - gauge.Load( Parser, DynamicObject, DynamicObject->mdKabina, nullptr ); + gauge.Load( Parser, DynamicObject ); gauge.AssignFloat( &mvControlled->dizel_heat.temperatura1 ); } else if( Label == "water2temp:" ) { // auxiliary circuit water temperature auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka - gauge.Load( Parser, DynamicObject, DynamicObject->mdKabina, nullptr ); + gauge.Load( Parser, DynamicObject ); gauge.AssignFloat( &mvControlled->dizel_heat.temperatura2 ); } + else if( Label == "pantpress:" ) { + // pantograph tank pressure + auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka + gauge.Load( Parser, DynamicObject, 0.1 ); + gauge.AssignDouble( &mvOccupied->PantPress ); + } // yB - dla drugiej sekcji else if (Label == "hvbcurrent1:") { // 1szy amperomierz - ggI1B.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggI1B.Load(Parser, DynamicObject); } else if (Label == "hvbcurrent2:") { // 2gi amperomierz - ggI2B.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggI2B.Load(Parser, DynamicObject); } else if (Label == "hvbcurrent3:") { // 3ci amperomierz - ggI3B.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggI3B.Load(Parser, DynamicObject); } else if (Label == "hvbcurrent:") { // amperomierz calkowitego pradu - ggItotalB.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggItotalB.Load(Parser, DynamicObject); } //************************************************************* else if (Label == "clock:") @@ -7801,76 +7893,78 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con // zegar analogowy if (Parser.getToken() == "analog") { - // McZapkie-300302: zegarek - ggClockSInd.Init(DynamicObject->mdKabina->GetFromName("ClockShand"), TGaugeAnimation::gt_Rotate, 1.0/60.0); - ggClockMInd.Init(DynamicObject->mdKabina->GetFromName("ClockMhand"), TGaugeAnimation::gt_Rotate, 1.0/60.0); - ggClockHInd.Init(DynamicObject->mdKabina->GetFromName("ClockHhand"), TGaugeAnimation::gt_Rotate, 1.0/12.0); + if( DynamicObject->mdKabina ) { + // McZapkie-300302: zegarek + ggClockSInd.Init( DynamicObject->mdKabina->GetFromName( "ClockShand" ), TGaugeAnimation::gt_Rotate, 1.0 / 60.0 ); + ggClockMInd.Init( DynamicObject->mdKabina->GetFromName( "ClockMhand" ), TGaugeAnimation::gt_Rotate, 1.0 / 60.0 ); + ggClockHInd.Init( DynamicObject->mdKabina->GetFromName( "ClockHhand" ), TGaugeAnimation::gt_Rotate, 1.0 / 12.0 ); + } } } else if (Label == "evoltage:") { // woltomierz napiecia silnikow - ggEngineVoltage.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggEngineVoltage.Load(Parser, DynamicObject); } else if (Label == "hvoltage:") { // woltomierz wysokiego napiecia auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(&fHVoltage); } else if (Label == "lvoltage:") { // woltomierz niskiego napiecia - ggLVoltage.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggLVoltage.Load(Parser, DynamicObject); } else if (Label == "enrot1m:") { // obrotomierz auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(fEngine + 1); } // ggEnrot1m.Load(Parser,DynamicObject->mdKabina); else if (Label == "enrot2m:") { // obrotomierz auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(fEngine + 2); } // ggEnrot2m.Load(Parser,DynamicObject->mdKabina); else if (Label == "enrot3m:") { // obrotomierz auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignFloat(fEngine + 3); } // ggEnrot3m.Load(Parser,DynamicObject->mdKabina); else if (Label == "engageratio:") { // np. ciśnienie sterownika sprzęgła auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignDouble(&mvControlled->dizel_engage); } // ggEngageRatio.Load(Parser,DynamicObject->mdKabina); else if (Label == "maingearstatus:") { // np. ciśnienie sterownika skrzyni biegów - ggMainGearStatus.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggMainGearStatus.Load(Parser, DynamicObject); } else if (Label == "ignitionkey:") { - ggIgnitionKey.Load(Parser, DynamicObject, DynamicObject->mdKabina); + ggIgnitionKey.Load(Parser, DynamicObject); } else if (Label == "distcounter:") { // Ra 2014-07: licznik kilometrów auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignDouble(&mvControlled->DistCounter); } else if( Label == "shuntmodepower:" ) { // shunt mode power slider auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka - gauge.Load(Parser, DynamicObject, DynamicObject->mdKabina); + gauge.Load(Parser, DynamicObject); gauge.AssignDouble(&mvControlled->AnPos); m_controlmapper.insert( gauge, "shuntmodepower:" ); } diff --git a/Train.h b/Train.h index 85b9542c..83d5d0a1 100644 --- a/Train.h +++ b/Train.h @@ -34,16 +34,21 @@ public: // methods void Load(cParser &Parser); void Update(); + TGauge &Gauge( int n = -1 ); // pobranie adresu obiektu + TButton &Button( int n = -1 ); // pobranie adresu obiektu // members Math3D::vector3 CabPos1 { 0, 1, 1 }; Math3D::vector3 CabPos2 { 0, 1, -1 }; bool bEnabled { false }; bool bOccupied { true }; +/* glm::vec3 dimm; // McZapkie-120503: tlumienie swiatla glm::vec3 intlit; // McZapkie-120503: oswietlenie kabiny glm::vec3 intlitlow; // McZapkie-120503: przyciemnione oswietlenie kabiny - TGauge &Gauge( int n = -1 ); // pobranie adresu obiektu - TButton &Button( int n = -1 ); // pobranie adresu obiektu +*/ + bool bLight { false }; // hunter-091012: czy swiatlo jest zapalone? + bool bLightDim { false }; // hunter-091012: czy przyciemnienie kabiny jest zapalone? + float LightLevel{ 0.f }; // last calculated interior light level private: // members @@ -123,7 +128,7 @@ class TTrain void clear_cab_controls(); // sets cabin controls based on current state of the vehicle // NOTE: we can get rid of this function once we have per-cab persistent state - void set_cab_controls(); + void set_cab_controls( int const Cab ); // initializes a gauge matching provided label. returns: true if the label was found, false otherwise bool initialize_gauge(cParser &Parser, std::string const &Label, int const Cabindex); // initializes a button matching provided label. returns: true if the label was found, false otherwise @@ -509,7 +514,7 @@ public: // reszta może by?publiczna TButton btInstrumentLight; TButton btDashboardLight; TButton btTimetableLight; - int InstrumentLightType{ 0 }; // ABu 030405 - swiecenie uzaleznione od: 0-nic, 1-obw.gl, 2-przetw. + int InstrumentLightType{ 0 }; // ABu 030405 - swiecenie uzaleznione od: 0-nic, 1-obw.gl, 2-przetw., 3-rozrzad bool InstrumentLightActive{ false }; bool DashboardLightActive{ false }; bool TimetableLightActive{ false }; @@ -595,14 +600,14 @@ public: // reszta może by?publiczna sound_source m_radiosound { sound_placement::internal, 2 * EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // cached template for radio messages std::vector>> m_radiomessages; // list of currently played radio messages sound_source m_radiostop { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; - +/* int iCabLightFlag; // McZapkie:120503: oswietlenie kabiny (0: wyl, 1: przyciemnione, 2: pelne) bool bCabLight; // hunter-091012: czy swiatlo jest zapalone? bool bCabLightDim; // hunter-091012: czy przyciemnienie kabiny jest zapalone? - +*/ // McZapkie: opis kabiny - obszar poruszania sie mechanika oraz zajetosc - TCab Cabine[ maxcab + 1 ]; // przedzial maszynowy, kabina 1 (A), kabina 2 (B) - int iCabn; + std::array Cabine; // przedzial maszynowy, kabina 1 (A), kabina 2 (B) + int iCabn { 0 }; // McZapkie: do poruszania sie po kabinie Math3D::vector3 pMechSittingPosition; // ABu 180404 Math3D::vector3 MirrorPosition( bool lewe ); @@ -644,6 +649,8 @@ private: bool bHeat[8]; // grzanie // McZapkie: do syczenia float fPPress, fNPress; + bool m_mastercontrollerinuse { false }; + float m_mastercontrollerreturndelay { 0.f }; int iRadioChannel { 1 }; // numer aktualnego kana?u radiowego std::vector>> m_screens; uint16_t vid = 0; diff --git a/audiorenderer.cpp b/audiorenderer.cpp index dd47b435..be7e6320 100644 --- a/audiorenderer.cpp +++ b/audiorenderer.cpp @@ -74,7 +74,13 @@ openal_source::update( double const Deltatime, glm::vec3 const &Listenervelocity if( sound_range < 0.0 ) { sound_velocity = Listenervelocity; // cached for doppler shift calculation } - +/* + // HACK: if the application gets stuck for long time loading assets the audio can gone awry. + // terminate all sources when it happens to stay on the safe side + if( Deltatime > 1.0 ) { + stop(); + } +*/ if( id != audio::null_resource ) { sound_change = false; @@ -441,6 +447,14 @@ openal_renderer::fetch_source() { } } + if( newsource.id == audio::null_resource ) { + // for sources with functional emitter reset emitter parameters from potential last use + ::alSourcef( newsource.id, AL_PITCH, 1.f ); + ::alSourcef( newsource.id, AL_GAIN, 1.f ); + ::alSourcefv( newsource.id, AL_POSITION, glm::value_ptr( glm::vec3{ 0.f } ) ); + ::alSourcefv( newsource.id, AL_VELOCITY, glm::value_ptr( glm::vec3{ 0.f } ) ); + } + return newsource; } diff --git a/audiorenderer.h b/audiorenderer.h index 76f191ae..4ef4480c 100644 --- a/audiorenderer.h +++ b/audiorenderer.h @@ -89,11 +89,11 @@ struct openal_source { private: // members - double update_deltatime; // time delta of most current update + double update_deltatime { 0.0 }; // time delta of most current update float pitch_variation { 1.f }; // emitter-specific variation of the base pitch float sound_range { 50.f }; // cached audible range of the emitted samples - glm::vec3 sound_distance; // cached distance between sound and the listener - glm::vec3 sound_velocity; // sound movement vector + glm::vec3 sound_distance { 0.f }; // cached distance between sound and the listener + glm::vec3 sound_velocity { 0.f }; // sound movement vector bool is_in_range { false }; // helper, indicates the source was recently within audible range bool is_multipart { false }; // multi-part sounds are kept alive at longer ranges }; diff --git a/drivermode.cpp b/drivermode.cpp index 08d637b4..32de3690 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -27,13 +27,13 @@ http://mozilla.org/MPL/2.0/. #include "renderer.h" #include "utilities.h" #include "Logs.h" - +/* namespace input { user_command command; // currently issued control command, if any } - +*/ void driver_mode::drivermode_input::poll() { if (telemetry) @@ -48,11 +48,13 @@ driver_mode::drivermode_input::poll() { if( uart != nullptr ) { uart->poll(); } +/* // TBD, TODO: wrap current command in object, include other input sources? input::command = ( mouse.command() != user_command::none ? mouse.command() : keyboard.command() ); +*/ } bool @@ -465,6 +467,15 @@ driver_mode::update_camera( double const Deltatime ) { simulation::Train->pMechViewAngle = { Camera.Angle.x, Camera.Angle.y }; simulation::Train->pMechOffset = Camera.m_owneroffset; } + + if( ( true == FreeFlyModeFlag ) + && ( Camera.m_owner != nullptr ) ) { + // cache external view config + auto &externalviewconfig { m_externalviewconfigs[ m_externalviewmode ] }; + externalviewconfig.owner = Camera.m_owner; + externalviewconfig.offset = Camera.m_owneroffset; + externalviewconfig.angle = Camera.Angle; + } } else { // debug camera @@ -564,13 +575,13 @@ driver_mode::update_camera( double const Deltatime ) { // gdy w korytarzu Camera.LookAt = Camera.m_owner->GetWorldPosition( Camera.m_owneroffset ) - + simulation::Train->GetDirection() * 5.0; + + Camera.m_owner->VectorFront() * 5.0; } else { // patrzenie w kierunku osi pojazdu, z uwzględnieniem kabiny Camera.LookAt = Camera.m_owner->GetWorldPosition( Camera.m_owneroffset ) - + simulation::Train->GetDirection() * 5.0 + + Camera.m_owner->VectorFront() * 5.0 * simulation::Train->Occupied()->ActiveCab; //-1 albo 1 } Camera.vUp = simulation::Train->GetUp(); @@ -839,18 +850,26 @@ driver_mode::ExternalView() { Camera.m_owner = owner; - auto const offsetflip { - ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) - * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) }; + auto const &viewconfig { m_externalviewconfigs[ m_externalviewmode ] }; + if( owner == viewconfig.owner ) { + // restore view config for previous owner + Camera.m_owneroffset = viewconfig.offset; + Camera.Angle = viewconfig.angle; + } + else { + // default view setup + auto const offsetflip{ + ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) + * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) }; - Camera.m_owneroffset = { - 1.5 * owner->MoverParameters->Dim.W * offsetflip, - std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ), - - 0.4 * owner->MoverParameters->Dim.L * offsetflip }; + Camera.m_owneroffset = { + 1.5 * owner->MoverParameters->Dim.W * offsetflip, + std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ), + -0.4 * owner->MoverParameters->Dim.L * offsetflip }; - Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) ); - - auto const shakeangles { owner->shake_angles() }; + Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) ); + } + auto const shakeangles{ owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki @@ -862,18 +881,26 @@ driver_mode::ExternalView() { Camera.m_owner = owner; - auto const offsetflip { - ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) - * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) - * -1 }; + auto const &viewconfig{ m_externalviewconfigs[ m_externalviewmode ] }; + if( owner == viewconfig.owner ) { + // restore view config for previous owner + Camera.m_owneroffset = viewconfig.offset; + Camera.Angle = viewconfig.angle; + } + else { + // default view setup + auto const offsetflip{ + ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) + * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) + * -1 }; - Camera.m_owneroffset = { - 1.5 * owner->MoverParameters->Dim.W * offsetflip, - std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ), - 0.2 * owner->MoverParameters->Dim.L * offsetflip }; - - Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 0.0 : 180.0 ) ); + Camera.m_owneroffset = { + 1.5 * owner->MoverParameters->Dim.W * offsetflip, + std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ), + 0.2 * owner->MoverParameters->Dim.L * offsetflip }; + Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 0.0 : 180.0 ) ); + } auto const shakeangles { owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki @@ -884,17 +911,25 @@ driver_mode::ExternalView() { Camera.m_owner = owner; - auto const offsetflip { - ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) - * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) }; + auto const &viewconfig{ m_externalviewconfigs[ m_externalviewmode ] }; + if( owner == viewconfig.owner ) { + // restore view config for previous owner + Camera.m_owneroffset = viewconfig.offset; + Camera.Angle = viewconfig.angle; + } + else { + // default view setup + auto const offsetflip{ + ( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab ) + * ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) }; - Camera.m_owneroffset = { - - 0.65 * owner->MoverParameters->Dim.W * offsetflip, - 0.90, - 0.15 * owner->MoverParameters->Dim.L * offsetflip }; - - Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) ); + Camera.m_owneroffset = { + -0.65 * owner->MoverParameters->Dim.W * offsetflip, + 0.90, + 0.15 * owner->MoverParameters->Dim.L * offsetflip }; + Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) ); + } auto const shakeangles { owner->shake_angles() }; Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki diff --git a/drivermode.h b/drivermode.h index ea32dbad..421bf85f 100644 --- a/drivermode.h +++ b/drivermode.h @@ -60,6 +60,12 @@ private: count_ }; + struct view_config { + TDynamicObject const *owner { nullptr }; + Math3D::vector3 offset {}; + Math3D::vector3 angle {}; + }; + struct drivermode_input { gamepad_input gamepad; @@ -94,6 +100,7 @@ private: TCamera DebugCamera; int m_externalviewmode { view::consistfront }; // selected external view mode bool m_externalview { false }; + std::array m_externalviewconfigs; TDynamicObject *pDynamicNearest { nullptr }; // vehicle nearest to the active camera. TODO: move to camera double fTime50Hz { 0.0 }; // bufor czasu dla komunikacji z PoKeys double const m_primaryupdaterate { 1.0 / 100.0 }; diff --git a/drivermouseinput.cpp b/drivermouseinput.cpp index 06fb3965..0843b994 100644 --- a/drivermouseinput.cpp +++ b/drivermouseinput.cpp @@ -317,6 +317,7 @@ drivermouse_input::button( int const Button, int const Action ) { m_pickwaiting = false; if( Button == GLFW_MOUSE_BUTTON_LEFT ) { if( m_slider.command() != user_command::none ) { + m_relay.post( m_slider.command(), 0, 0, Action, 0 ); m_slider.release(); } } @@ -485,6 +486,9 @@ drivermouse_input::default_bindings() { { "brakeprofiler_sw:", { user_command::brakeactingspeedsetrapid, user_command::brakeactingspeedsetpassenger } }, + { "brakeopmode_sw:", { + user_command::trainbrakeoperationmodeincrease, + user_command::trainbrakeoperationmodedecrease } }, { "maxcurrent_sw:", { user_command::motoroverloadrelaythresholdtoggle, user_command::none } }, diff --git a/driveruilayer.cpp b/driveruilayer.cpp index 42e6be85..08d90a9e 100644 --- a/driveruilayer.cpp +++ b/driveruilayer.cpp @@ -208,23 +208,27 @@ driver_ui::set_cursor( bool const Visible ) { // render() subclass details void driver_ui::render_() { - - if( m_paused ) { - // pause/quit modal - auto const popupheader { locale::strings[ locale::string::driver_pause_header ].c_str() }; - ImGui::OpenPopup( popupheader ); - if( ImGui::BeginPopupModal( popupheader, nullptr, ImGuiWindowFlags_AlwaysAutoResize ) ) { - auto const popupwidth{ locale::strings[ locale::string::driver_pause_header ].size() * 7 }; - if( ImGui::Button( locale::strings[ locale::string::driver_pause_resume ].c_str(), ImVec2( popupwidth, 0 ) ) ) { - ImGui::CloseCurrentPopup(); - auto const pausemask { 1 | 2 }; - Global.iPause &= ~pausemask; - } - if( ImGui::Button( locale::strings[ locale::string::driver_pause_quit ].c_str(), ImVec2( popupwidth, 0 ) ) ) { - ImGui::CloseCurrentPopup(); - glfwSetWindowShouldClose( m_window, 1 ); - } - ImGui::EndPopup(); + // pause/quit modal + auto const popupheader { locale::strings[ locale::string::driver_pause_header ].c_str() }; + if (m_paused && !m_pause_modal_opened) + { + m_pause_modal_opened = true; + ImGui::OpenPopup(popupheader); + } + if( ImGui::BeginPopupModal( popupheader, &m_pause_modal_opened, ImGuiWindowFlags_AlwaysAutoResize ) ) { + auto const popupwidth{ locale::strings[ locale::string::driver_pause_header ].size() * 7 }; + if( ImGui::Button( locale::strings[ locale::string::driver_pause_resume ].c_str(), ImVec2( popupwidth, 0 ) ) ) { + auto const pausemask { 1 | 2 }; + Global.iPause &= ~pausemask; } + if( ImGui::Button( locale::strings[ locale::string::driver_pause_quit ].c_str(), ImVec2( popupwidth, 0 ) ) ) { + glfwSetWindowShouldClose( m_window, 1 ); + } + if (!m_paused) + { + m_pause_modal_opened = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); } } diff --git a/driveruilayer.h b/driveruilayer.h index 3ce50b22..d556532e 100644 --- a/driveruilayer.h +++ b/driveruilayer.h @@ -49,5 +49,5 @@ private: debug_panel m_debugpanel { "Debug Data", false }; transcripts_panel m_transcriptspanel { "Transcripts", true }; // voice transcripts bool m_paused { false }; - + bool m_pause_modal_opened { false }; }; diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 5f404369..af07d372 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -117,7 +117,7 @@ drivingaid_panel::update() { { // alerter, hints std::string expandedtext; if( is_expanded ) { - auto const stoptime { static_cast( -1.0 * controlled->Mechanik->fStopTime ) }; + auto const stoptime { static_cast( std::ceil( -1.0 * controlled->Mechanik->fStopTime ) ) }; if( stoptime > 0 ) { std::snprintf( m_buffer.data(), m_buffer.size(), @@ -172,10 +172,10 @@ timetable_panel::update() { text_lines.emplace_back( m_buffer.data(), Global.UITextColor ); } - auto *vehicle { - ( FreeFlyModeFlag ? - std::get( simulation::Region->find_vehicle( camera.Pos, 20, false, false ) ) : - controlled ) }; // w trybie latania lokalizujemy wg mapy + auto *vehicle { ( + false == FreeFlyModeFlag ? controlled : + camera.m_owner != nullptr ? camera.m_owner : + std::get( simulation::Region->find_vehicle( camera.Pos, 20, false, false ) ) ) }; // w trybie latania lokalizujemy wg mapy if( vehicle == nullptr ) { return; } // if the nearest located vehicle doesn't have a direct driver, try to query its owner @@ -285,10 +285,10 @@ debug_panel::update() { m_input.train = simulation::Train; m_input.controlled = ( m_input.train ? m_input.train->Dynamic() : nullptr ); m_input.camera = &( Global.pCamera ); - m_input.vehicle = - ( FreeFlyModeFlag ? - std::get( simulation::Region->find_vehicle( m_input.camera->Pos, 20, false, false ) ) : - m_input.controlled ); // w trybie latania lokalizujemy wg mapy + m_input.vehicle = ( + false == FreeFlyModeFlag ? m_input.controlled : + m_input.camera->m_owner != nullptr ? m_input.camera->m_owner : + std::get( simulation::Region->find_vehicle( m_input.camera->Pos, 20, false, false ) ) ); // w trybie latania lokalizujemy wg mapy m_input.mover = ( m_input.vehicle != nullptr ? m_input.vehicle->MoverParameters : @@ -440,8 +440,8 @@ debug_panel::update_section_vehicle( std::vector &Output ) { std::abs( mover.enrot ) * 60, std::abs( mover.nrot ) * mover.Transmision.Ratio * 60, mover.RventRot * 60, - mover.MotorBlowers[side::front].revolutions, - mover.MotorBlowers[side::rear].revolutions, + std::abs( mover.MotorBlowers[side::front].revolutions ), + std::abs( mover.MotorBlowers[side::rear].revolutions ), mover.dizel_heat.rpmw, mover.dizel_heat.rpmw2 ); @@ -467,6 +467,7 @@ debug_panel::update_section_vehicle( std::vector &Output ) { // brakes mover.fBrakeCtrlPos, mover.LocalBrakePosA, + mover.BrakeOpModeFlag, update_vehicle_brake().c_str(), mover.LoadFlag, // cylinders @@ -479,7 +480,7 @@ debug_panel::update_section_vehicle( std::vector &Output ) { mover.ScndPipePress, mover.CntrlPipePress, // tanks - mover.Volume, + mover.Hamulec->GetBRP(), mover.Compressor, mover.Hamulec->GetCRP() ); @@ -523,37 +524,7 @@ debug_panel::update_section_vehicle( std::vector &Output ) { vehicle.GetPosition().z ); Output.emplace_back( m_buffer.data(), Global.UITextColor ); -/* - textline = " TC:" + to_string( mover.TotalCurrent, 0 ); -*/ -/* - if( mover.ManualBrakePos > 0 ) { - textline += "; manual brake on"; - } -*/ -/* - // McZapkie: warto wiedziec w jakim stanie sa przelaczniki - switch( - mover.ActiveDir * - ( mover.Imin == mover.IminLo ? - 1 : - 2 ) ) { - case 2: { textline += " >> "; break; } - case 1: { textline += " -> "; break; } - case 0: { textline += " -- "; break; } - case -1: { textline += " <- "; break; } - case -2: { textline += " << "; break; } - } - - // McZapkie: komenda i jej parametry - if( mover.CommandIn.Command != ( "" ) ) { - textline = - "C:" + mover.CommandIn.Command - + " V1=" + to_string( mover.CommandIn.Value1, 0 ) - + " V2=" + to_string( mover.CommandIn.Value2, 0 ); - } -*/ } std::string @@ -894,7 +865,10 @@ debug_panel::render_section( std::string const &Header, std::vector c if( false == ImGui::CollapsingHeader( Header.c_str() ) ) { return false; } for( auto const &line : Lines ) { - ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() ); + ImGui::PushStyleColor( ImGuiCol_Text, { line.color.r, line.color.g, line.color.b, line.color.a } ); + ImGui::TextUnformatted( line.data.c_str() ); + ImGui::PopStyleColor(); +// ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() ); } return true; } diff --git a/editormode.h b/editormode.h index dd8a1b5d..932346f8 100644 --- a/editormode.h +++ b/editormode.h @@ -75,12 +75,12 @@ private: bool mode_snap() const; // members + state_backup m_statebackup; // helper, cached variables to be restored on mode exit editormode_input m_input; TCamera Camera; double fTime50Hz { 0.0 }; // bufor czasu dla komunikacji z PoKeys scene::basic_editor m_editor; scene::basic_node *m_node; // currently selected scene node bool m_takesnapshot { true }; // helper, hints whether snapshot of selected node(s) should be taken before modification - state_backup m_statebackup; // helper, cached variables to be restored on mode exit bool m_dragging = false; }; diff --git a/editoruipanels.cpp b/editoruipanels.cpp index 0d1ded79..3345c773 100644 --- a/editoruipanels.cpp +++ b/editoruipanels.cpp @@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "editoruipanels.h" +#include "scenenodegroups.h" #include "Globals.h" #include "Camera.h" @@ -20,10 +21,13 @@ http://mozilla.org/MPL/2.0/. void itemproperties_panel::update( scene::basic_node const *Node ) { + m_node = Node; if( false == is_open ) { return; } text_lines.clear(); + m_grouplines.clear(); + std::string textline; // scenario inspector @@ -38,7 +42,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { } textline = - "node name: " + node->name() + "name: " + ( node->name().empty() ? "(none)" : node->name() ) + "\nlocation: [" + to_string( node->location().x, 2 ) + ", " + to_string( node->location().y, 2 ) + ", " + to_string( node->location().z, 2 ) + "]" + " (distance: " + to_string( glm::length( glm::dvec3{ node->location().x, 0.0, node->location().z } -glm::dvec3{ camera.Pos.x, 0.0, camera.Pos.z } ), 1 ) + " m)"; text_lines.emplace_back( textline, Global.UITextColor ); @@ -62,7 +66,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { textline += ']'; } else { - textline += "none"; + textline += "(none)"; } text_lines.emplace_back( textline, Global.UITextColor ); @@ -70,7 +74,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { auto modelfile { ( ( subnode->pModel != nullptr ) ? subnode->pModel->NameGet() : - "none" ) }; + "(none)" ) }; if( modelfile.find( szModelPath ) == 0 ) { // don't include 'models/' in the path modelfile.erase( 0, std::string{ szModelPath }.size() ); @@ -79,7 +83,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { auto texturefile { ( ( subnode->Material()->replacable_skins[ 1 ] != null_handle ) ? GfxRenderer.Material( subnode->Material()->replacable_skins[ 1 ] ).name : - "none" ) }; + "(none)" ) }; if( texturefile.find( szTexturePath ) == 0 ) { // don't include 'textures/' in the path texturefile.erase( 0, std::string{ szTexturePath }.size() ); @@ -92,7 +96,7 @@ itemproperties_panel::update( scene::basic_node const *Node ) { auto const *subnode = static_cast( node ); // basic attributes textline = - "isolated: " + ( ( subnode->pIsolated != nullptr ) ? subnode->pIsolated->asName : "none" ) + "isolated: " + ( ( subnode->pIsolated != nullptr ) ? subnode->pIsolated->asName : "(none)" ) + "\nvelocity: " + to_string( subnode->SwitchExtension ? subnode->SwitchExtension->fVelocity : subnode->fVelocity ) + "\nwidth: " + to_string( subnode->fTrackWidth ) + " m" + "\nfriction: " + to_string( subnode->fFriction, 2 ) @@ -102,14 +106,14 @@ itemproperties_panel::update( scene::basic_node const *Node ) { auto texturefile { ( ( subnode->m_material1 != null_handle ) ? GfxRenderer.Material( subnode->m_material1 ).name : - "none" ) }; + "(none)" ) }; if( texturefile.find( szTexturePath ) == 0 ) { texturefile.erase( 0, std::string{ szTexturePath }.size() ); } auto texturefile2{ ( ( subnode->m_material2 != null_handle ) ? GfxRenderer.Material( subnode->m_material2 ).name : - "none" ) }; + "(none)" ) }; if( texturefile2.find( szTexturePath ) == 0 ) { texturefile2.erase( 0, std::string{ szTexturePath }.size() ); } @@ -164,11 +168,72 @@ itemproperties_panel::update( scene::basic_node const *Node ) { + " [" + to_string( subnode->Value1(), 2 ) + "]" + " [" + to_string( subnode->Value2(), 2 ) + "]"; text_lines.emplace_back( textline, Global.UITextColor ); - textline = "track: " + ( subnode->asTrackName.empty() ? "none" : subnode->asTrackName ); + textline = "track: " + ( subnode->asTrackName.empty() ? "(none)" : subnode->asTrackName ); text_lines.emplace_back( textline, Global.UITextColor ); } + + update_group(); } +void +itemproperties_panel::update_group() { + + auto const grouphandle { m_node->group() }; + + if( grouphandle == null_handle ) { + m_grouphandle = null_handle; + m_groupprefix.clear(); + return; + } + + auto const &nodegroup { scene::Groups.group( grouphandle ) }; + + if( m_grouphandle != grouphandle ) { + // calculate group name from shared prefix of item names + std::vector> names; + // build list of custom item and event names + for( auto const *node : nodegroup.nodes ) { + auto const &name { node->name() }; + if( name.empty() || name == "none" ) { continue; } + names.emplace_back( name ); + } + for( auto const *event : nodegroup.events ) { + auto const &name { event->m_name }; + if( name.empty() || name == "none" ) { continue; } + names.emplace_back( name ); + } + // find the common prefix + if( names.size() > 1 ) { + m_groupprefix = names.front(); + for( auto const &name : names ) { + // NOTE: first calculation runs over two instances of the same name, but, eh + auto const prefixlength{ len_common_prefix( m_groupprefix, name ) }; + if( prefixlength > 0 ) { + m_groupprefix = m_groupprefix.substr( 0, prefixlength ); + } + else { + m_groupprefix.clear(); + break; + } + } + } + else { + // less than two names to compare means no prefix + m_groupprefix.clear(); + } + m_grouphandle = grouphandle; + } + + m_grouplines.emplace_back( + "nodes: " + to_string( static_cast( nodegroup.nodes.size() ) ) + + "\nevents: " + to_string( static_cast( nodegroup.events.size() ) ), + Global.UITextColor ); + m_grouplines.emplace_back( + "names prefix: " + ( m_groupprefix.empty() ? "(none)" : m_groupprefix ), + Global.UITextColor ); +} + + void itemproperties_panel::render() { @@ -196,6 +261,23 @@ itemproperties_panel::render() { for( auto const &line : text_lines ) { ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() ); } + // group section + render_group(); } ImGui::End(); } + +bool +itemproperties_panel::render_group() { + + if( m_node == nullptr ) { return false; } + if( m_grouplines.empty() ) { return false; } + + if( false == ImGui::CollapsingHeader( "Parent Group" ) ) { return false; } + + for( auto const &line : m_grouplines ) { + ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() ); + } + + return true; +} diff --git a/editoruipanels.h b/editoruipanels.h index 3a18c4d8..efd6f8cd 100644 --- a/editoruipanels.h +++ b/editoruipanels.h @@ -21,4 +21,15 @@ public: void update( scene::basic_node const *Node ); void render() override; + +private: +// methods + void update_group(); + bool render_group(); + +// members + scene::basic_node const *m_node { nullptr }; // scene node bound to the panel + scene::group_handle m_grouphandle { null_handle }; // scene group bound to the panel + std::string m_groupprefix; + std::vector m_grouplines; }; diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp index a65931c6..fd2bfd69 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -31,6 +31,9 @@ void gl::glsl_common_setup() float linear; float quadratic; + + float intensity; + float ambient; }; layout(std140) uniform light_ubo diff --git a/gl/ubo.h b/gl/ubo.h index 690f7c3e..781e57a3 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -88,7 +88,8 @@ namespace gl float linear; float quadratic; - UBS_PAD(8); + float intensity; + float ambient; }; static_assert(sizeof(light_element_ubs) == 64, "bad size of ubs"); diff --git a/openglgeometrybank.cpp b/openglgeometrybank.cpp index 3136f4fd..60e7c712 100644 --- a/openglgeometrybank.cpp +++ b/openglgeometrybank.cpp @@ -308,6 +308,9 @@ opengl_vbogeometrybank::draw_( gfx::geometry_handle const &Geometry) // actual draw procedure starts here auto &chunkrecord = m_chunkrecords.at(Geometry.chunk - 1); + // sanity check; shouldn't be needed but, eh + if( chunkrecord.size == 0 ) + return; auto const &chunk = gfx::geometry_bank::chunk( Geometry ); if( false == chunkrecord.is_good ) { glBindBuffer( GL_ARRAY_BUFFER, m_buffer ); diff --git a/renderer.cpp b/renderer.cpp index feb4bb36..2881bfc5 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -208,7 +208,7 @@ bool opengl_renderer::Init(GLFWwindow *Window) m_msaa_fb->attach(*m_msaa_rbv, GL_COLOR_ATTACHMENT1); m_main_tex = std::make_unique(); - m_main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height); + m_main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height, 1, GL_CLAMP_TO_EDGE); m_main_fb = std::make_unique(); m_main_fb->attach(*m_main_tex, GL_COLOR_ATTACHMENT0); @@ -442,6 +442,28 @@ void opengl_renderer::SwapBuffers() Timer::subsystem.gfx_swap.stop(); } +void opengl_renderer::draw_debug_ui() +{ + if (!debug_ui_active) + return; + + if (ImGui::Begin("headlight config", &debug_ui_active)) + { + ImGui::SetWindowSize(ImVec2(0, 0)); + + headlight_config_s &conf = headlight_config; + ImGui::SliderFloat("in_cutoff", &conf.in_cutoff, 0.9f, 1.1f); + ImGui::SliderFloat("out_cutoff", &conf.out_cutoff, 0.9f, 1.1f); + + ImGui::SliderFloat("falloff_linear", &conf.falloff_linear, 0.0f, 1.0f, "%.3f", 2.0f); + ImGui::SliderFloat("falloff_quadratic", &conf.falloff_quadratic, 0.0f, 1.0f, "%.3f", 2.0f); + + ImGui::SliderFloat("ambient", &conf.ambient, 0.0f, 3.0f); + ImGui::SliderFloat("intensity", &conf.intensity, 0.0f, 10.0f); + } + ImGui::End(); +} + // runs jobs needed to generate graphics for specified render pass void opengl_renderer::Render_pass(rendermode const Mode) { @@ -533,9 +555,9 @@ void opengl_renderer::Render_pass(rendermode const Mode) setup_drawing(true); glm::mat4 future; - if (!FreeFlyModeFlag) + if (Global.pCamera.m_owner != nullptr) { - auto const *vehicle = simulation::Train->Dynamic(); + auto const *vehicle = Global.pCamera.m_owner; glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); future = glm::translate(mv, -glm::vec3(vehicle->get_future_movement())) * glm::inverse(mv); } @@ -560,7 +582,7 @@ void opengl_renderer::Render_pass(rendermode const Mode) setup_shadow_map(m_cabshadows_tex.get(), m_cabshadowpass); auto const *vehicle = simulation::Train->Dynamic(); - Render_cab(vehicle, false); + Render_cab(vehicle, vehicle->InteriorLightLevel, false); } glDebug("render opaque region"); @@ -593,10 +615,10 @@ void opengl_renderer::Render_pass(rendermode const Mode) { // with active precipitation draw the opaque cab parts here to mask rain/snow placed 'inside' the cab setup_drawing(false); - Render_cab(vehicle, false); + Render_cab(vehicle, vehicle->InteriorLightLevel, false); setup_drawing(true); } - Render_cab(vehicle, true); + Render_cab(vehicle, vehicle->InteriorLightLevel, true); } setup_shadow_map(nullptr, m_renderpass); @@ -634,6 +656,7 @@ void opengl_renderer::Render_pass(rendermode const Mode) glDisable(GL_FRAMEBUFFER_SRGB); glDebug("uilayer render"); + draw_debug_ui(); Application.render_ui(); // restore binding @@ -700,8 +723,8 @@ void opengl_renderer::Render_pass(rendermode const Mode) scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); scene_ubo->update(scene_ubs); - Render_cab(simulation::Train->Dynamic(), false); - Render_cab(simulation::Train->Dynamic(), true); + Render_cab(simulation::Train->Dynamic(), 0.0f, false); + Render_cab(simulation::Train->Dynamic(), 0.0f, true); m_cabshadowpass = m_renderpass; glDisable(GL_POLYGON_OFFSET_FILL); @@ -771,7 +794,10 @@ void opengl_renderer::Render_pass(rendermode const Mode) scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); scene_ubo->update(scene_ubs); - Render_cab(simulation::Train->Dynamic()); + if (simulation::Train != nullptr) { + Render_cab(simulation::Train->Dynamic(), 0.0f); + Render(simulation::Train->Dynamic()); + } m_pick_fb->unbind(); @@ -919,7 +945,7 @@ void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mod } case rendermode::cabshadows: { - Config.draw_range = (simulation::Train->Occupied()->ActiveCab != 0 ? 10.f : 20.f); + Config.draw_range = simulation::Train->Occupied()->Dim.L; break; } case rendermode::reflections: @@ -2088,13 +2114,7 @@ bool opengl_renderer::Render(TDynamicObject *Dynamic) // render if (Dynamic->mdLowPolyInt) - { - // low poly interior - if (FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab) - { - Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); - } - } + Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); if (Dynamic->mdModel) Render(Dynamic->mdModel, Dynamic->Material(), squaredistance); @@ -2117,10 +2137,7 @@ bool opengl_renderer::Render(TDynamicObject *Dynamic) if (Dynamic->mdLowPolyInt) { // low poly interior - if (FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab) - { - Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); - } + Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); } if (Dynamic->mdModel) Render(Dynamic->mdModel, Dynamic->Material(), squaredistance); @@ -2130,6 +2147,13 @@ bool opengl_renderer::Render(TDynamicObject *Dynamic) break; } case rendermode::pickcontrols: + { + if (Dynamic->mdLowPolyInt) { + // low poly interior + Render(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); + } + break; + } case rendermode::pickscenery: default: { @@ -2149,7 +2173,7 @@ bool opengl_renderer::Render(TDynamicObject *Dynamic) } // rendering kabiny gdy jest oddzielnym modelem i ma byc wyswietlana -bool opengl_renderer::Render_cab(TDynamicObject const *Dynamic, bool const Alpha) +bool opengl_renderer::Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha) { if (Dynamic == nullptr) @@ -2189,7 +2213,7 @@ bool opengl_renderer::Render_cab(TDynamicObject const *Dynamic, bool const Alpha // crude way to light the cabin, until we have something more complete in place glm::vec3 old_ambient = light_ubs.ambient; - light_ubs.ambient += Dynamic->InteriorLight * Dynamic->InteriorLightLevel; + light_ubs.ambient += Dynamic->InteriorLight * Lightlevel; light_ubo->update(light_ubs); // render @@ -2344,10 +2368,9 @@ void opengl_renderer::Render(TSubModel *Submodel) } // ...luminance - if (Global.fLuminance < Submodel->fLight) - { + auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) }; + if (isemissive) model_ubs.emission = Submodel->f4Emision.a; - } // main draw call draw(Submodel->m_geometry); @@ -2439,7 +2462,8 @@ void opengl_renderer::Render(TSubModel *Submodel) // material configuration: // limit impact of dense fog on the lights - model_ubs.fog_density = 1.0f / std::min(Global.fFogEnd, m_fogrange * 2); + auto const lightrange { std::max( 500, m_fogrange * 2 ) }; // arbitrary, visibility at least 750m + model_ubs.fog_density = 1.0 / lightrange; // main draw call model_ubs.emission = 1.0f; @@ -2984,16 +3008,7 @@ void opengl_renderer::Render_Alpha(TTraction *Traction) { glDebug("Render_Alpha TTraction"); - double distancesquared; - switch (m_renderpass.draw_mode) - { - case rendermode::shadows: - default: - { - distancesquared = glm::length2((Traction->location() - m_renderpass.camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; - break; - } - } + auto const distancesquared { glm::length2( ( Traction->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor }; if ((distancesquared < Traction->m_rangesquaredmin) || (distancesquared >= Traction->m_rangesquaredmax)) { return; @@ -3037,16 +3052,7 @@ void opengl_renderer::Render_Alpha(scene::lines_node const &Lines) auto const &data{Lines.data()}; - double distancesquared; - switch (m_renderpass.draw_mode) - { - case rendermode::shadows: - default: - { - distancesquared = glm::length2((data.area.center - m_renderpass.camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; - break; - } - } + auto const distancesquared { glm::length2( ( data.area.center - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor }; if ((distancesquared < data.rangesquared_min) || (distancesquared >= data.rangesquared_max)) { return; @@ -3114,10 +3120,7 @@ bool opengl_renderer::Render_Alpha(TDynamicObject *Dynamic) if (Dynamic->mdLowPolyInt) { // low poly interior - if (FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab) - { - Render_Alpha(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); - } + Render_Alpha(Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance); } if (Dynamic->mdModel) @@ -3236,10 +3239,9 @@ void opengl_renderer::Render_Alpha(TSubModel *Submodel) } // ...luminance - if (Global.fLuminance < Submodel->fLight) - { + auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) }; + if (isemissive) model_ubs.emission = Submodel->f4Emision.a; - } // main draw call draw(Submodel->m_geometry); @@ -3704,11 +3706,13 @@ void opengl_renderer::Update_Lights(light_array &Lights) l->pos = mv * glm::vec4(renderlight->position, 1.0f); l->dir = mv * glm::vec4(renderlight->direction, 0.0f); l->type = gl::light_element_ubs::SPOT; - l->in_cutoff = 0.997f; - l->out_cutoff = 0.99f; + l->in_cutoff = headlight_config.in_cutoff; + l->out_cutoff = headlight_config.out_cutoff; l->color = renderlight->diffuse * renderlight->factor; - l->linear = 0.007f; - l->quadratic = 0.0002f; + l->linear = headlight_config.falloff_linear / 10.0f; + l->quadratic = headlight_config.falloff_quadratic / 100.0f; + l->ambient = headlight_config.ambient; + l->intensity = headlight_config.intensity; light_i++; ++renderlight; @@ -3718,6 +3722,8 @@ void opengl_renderer::Update_Lights(light_array &Lights) light_ubs.lights[0].type = gl::light_element_ubs::DIR; light_ubs.lights[0].dir = mv * glm::vec4(m_sunlight.direction, 0.0f); light_ubs.lights[0].color = m_sunlight.diffuse * m_sunlight.factor; + light_ubs.lights[0].ambient = 0.0f; + light_ubs.lights[0].intensity = 1.0f; light_ubs.lights_count = light_i; light_ubs.fog_color = Global.FogColor; diff --git a/renderer.h b/renderer.h index 05a8554c..ef09163f 100644 --- a/renderer.h +++ b/renderer.h @@ -28,12 +28,6 @@ http://mozilla.org/MPL/2.0/. #include "gl/glsl_common.h" #include "gl/pbo.h" -#define EU07_USE_PICKING_FRAMEBUFFER -//#define EU07_USE_DEBUG_SHADOWMAP -//#define EU07_USE_DEBUG_CABSHADOWMAP -//#define EU07_USE_DEBUG_CAMERA -//#define EU07_USE_DEBUG_SOUNDEMITTERS - struct opengl_light : public basic_light { @@ -194,6 +188,8 @@ class opengl_renderer GLenum static const sunlight{0}; std::size_t m_drawcount{0}; + bool debug_ui_active = false; + private: // types enum class rendermode @@ -258,7 +254,7 @@ class opengl_renderer void Render(TSubModel *Submodel); void Render(TTrack *Track); void Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last); - bool Render_cab(TDynamicObject const *Dynamic, bool const Alpha = false); + bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false); void Render(TMemCell *Memcell); void Render_precipitation(); void Render_Alpha(scene::basic_region *Region); @@ -277,6 +273,8 @@ class opengl_renderer void draw(const gfx::geometry_handle &handle); void draw(std::vector::iterator begin, std::vector::iterator end); + void draw_debug_ui(); + // members GLFWwindow *m_window{nullptr}; gfx::geometrybank_manager m_geometry; @@ -411,6 +409,20 @@ class opengl_renderer bool m_blendingenabled; bool m_widelines_supported; + + struct headlight_config_s + { + float in_cutoff = 1.005f; + float out_cutoff = 0.993f; + + float falloff_linear = 0.069f; + float falloff_quadratic = 0.03f; + + float intensity = 1.0f; + float ambient = 0.184f; + }; + + headlight_config_s headlight_config; }; extern opengl_renderer GfxRenderer; diff --git a/scenarioloadermode.cpp b/scenarioloadermode.cpp index b519e099..89bec3d9 100644 --- a/scenarioloadermode.cpp +++ b/scenarioloadermode.cpp @@ -36,7 +36,7 @@ scenarioloader_mode::init() { bool scenarioloader_mode::update() { - WriteLog( "\nLoading scenario..." ); + WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." ); auto timestart = std::chrono::system_clock::now(); diff --git a/scenenode.h b/scenenode.h index ca53eaf0..7d2d4c1d 100644 --- a/scenenode.h +++ b/scenenode.h @@ -64,7 +64,7 @@ struct bounding_area { deserialize( std::istream &Input ); }; -using group_handle = std::size_t; +//using group_handle = std::size_t; struct node_data { diff --git a/shaders/apply_fog.glsl b/shaders/apply_fog.glsl new file mode 100644 index 00000000..fc58fb15 --- /dev/null +++ b/shaders/apply_fog.glsl @@ -0,0 +1,9 @@ +vec3 apply_fog(vec3 color) +{ + float sun_amount = 0.0; + if (lights_count >= 1U && lights[0].type == LIGHT_DIR) + sun_amount = max(dot(normalize(f_pos.xyz), normalize(-lights[0].dir)), 0.0); + vec3 fog_color_v = mix(fog_color, lights[0].color, pow(sun_amount, 30.0)); + float fog_amount_v = 1.0 - min(1.0, exp(-length(f_pos.xyz) * fog_density)); + return mix(color, fog_color_v, fog_amount_v); +} \ No newline at end of file diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index 2f68bbee..aed7d107 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -1,3 +1,5 @@ +#include + float calc_shadow() { #if SHADOWMAP_ENABLED @@ -23,28 +25,20 @@ float calc_shadow() #endif } -vec3 apply_fog(vec3 color) -{ - float sun_amount = 0.0; - if (lights_count >= 1U && lights[0].type == LIGHT_DIR) - sun_amount = max(dot(normalize(f_pos.xyz), normalize(-lights[0].dir)), 0.0); - vec3 fog_color_v = mix(fog_color, lights[0].color, pow(sun_amount, 30.0)); - float fog_amount_v = 1.0 - min(1.0, exp(-length(f_pos.xyz) * fog_density)); - return mix(color, fog_color_v, fog_amount_v); -} - // [0] - diffuse, [1] - specular // do magic here vec2 calc_light(vec3 light_dir) { #ifdef NORMALMAP vec3 normal = normalize(f_tbn * normalize(texture(normalmap, f_coord).rgb * 2.0 - 1.0)); +#elif defined(WATER) + vec3 normal = normal_d; #else vec3 normal = normalize(f_normal); #endif vec3 view_dir = normalize(vec3(0.0f, 0.0f, 0.0f) - f_pos.xyz); vec3 halfway_dir = normalize(light_dir + view_dir); - + float diffuse_v = max(dot(normal, light_dir), 0.0); float specular_v = pow(max(dot(normal, halfway_dir), 0.0), 15.0); @@ -55,6 +49,8 @@ vec2 calc_point_light(light_s light) { vec3 light_dir = normalize(light.pos - f_pos.xyz); vec2 val = calc_light(light_dir); + val.x += light.ambient; + val *= light.intensity; float distance = length(light.pos - f_pos.xyz); float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance)); diff --git a/shaders/mat_normalmap.frag b/shaders/mat_normalmap.frag index 044efe34..c4b7a4fa 100644 --- a/shaders/mat_normalmap.frag +++ b/shaders/mat_normalmap.frag @@ -22,7 +22,7 @@ layout(location = 1) out vec4 out_motion; #texture (diffuse, 0, sRGB_A) uniform sampler2D diffuse; -#texture (normalmap, 1, RGB) +#texture (normalmap, 1, RGBA) uniform sampler2D normalmap; #if SHADOWMAP_ENABLED @@ -58,7 +58,7 @@ void main() { vec2 part = calc_dir_light(lights[0]); vec3 c = (part.x * param[1].x + part.y * param[1].y) * calc_shadow() * lights[0].color; - result += mix(c, envcolor, param[1].z); + result += mix(c, envcolor, param[1].z * texture(normalmap, f_coord).a); } for (uint i = 1U; i < lights_count; i++) diff --git a/shaders/mat_reflmap.frag b/shaders/mat_reflmap.frag index 50afdd38..fc1df7d3 100644 --- a/shaders/mat_reflmap.frag +++ b/shaders/mat_reflmap.frag @@ -21,7 +21,7 @@ layout(location = 1) out vec4 out_motion; #texture (diffuse, 0, sRGB_A) uniform sampler2D diffuse; -#texture (reflmap, 1, R) +#texture (reflmap, 1, RGBA) uniform sampler2D reflmap; #if SHADOWMAP_ENABLED @@ -56,7 +56,7 @@ void main() { vec2 part = calc_dir_light(lights[0]); vec3 c = (part.x * param[1].x + part.y * param[1].y) * calc_shadow() * lights[0].color; - result += mix(c, envcolor, param[1].z * texture(reflmap, f_coord).r); + result += mix(c, envcolor, param[1].z * texture(reflmap, f_coord).a); } for (uint i = 1U; i < lights_count; i++) diff --git a/shaders/mat_stars.frag b/shaders/mat_stars.frag index 1a324857..aabc3ef3 100644 --- a/shaders/mat_stars.frag +++ b/shaders/mat_stars.frag @@ -17,7 +17,7 @@ void main() discard; // color data space is shared with normals, ugh - vec4 color = vec4(pow(f_normal_raw.bgr, vec3(2.2)), 1.0f); + vec4 color = vec4(pow(f_normal_raw.rgb, vec3(2.2)), 1.0f); #if POSTFX_ENABLED out_color = color; #else diff --git a/shaders/mat_water.frag b/shaders/mat_water.frag new file mode 100644 index 00000000..2b0649bf --- /dev/null +++ b/shaders/mat_water.frag @@ -0,0 +1,114 @@ +in vec3 f_normal; +in vec2 f_coord; +in vec4 f_pos; +in mat3 f_tbn; +in vec4 f_light_pos; + +in vec4 f_clip_pos; +in vec4 f_clip_future_pos; + +#include + +layout(location = 0) out vec4 out_color; +#if MOTIONBLUR_ENABLED +layout(location = 1) out vec4 out_motion; +#endif + +#param (color, 0, 0, 4, diffuse) +#param (diffuse, 1, 0, 1, diffuse) +#param (specular, 1, 1, 1, specular) +#param (reflection, 1, 2, 1, one) +#param (wave_strength, 2, 1, 1, one) +#param (wave_speed, 2, 2, 1, one) + +#texture (normalmap, 0, RGBA) +uniform sampler2D normalmap; + +#texture (dudvmap, 1, RGB) +uniform sampler2D dudvmap; + +#texture (diffuse, 2, sRGB_A) +uniform sampler2D diffuse; + +#if SHADOWMAP_ENABLED +uniform sampler2DShadow shadowmap; +#endif +#if ENVMAP_ENABLED +uniform samplerCube envmap; +#endif + +//wave distortion variables +float move_factor = 0; +vec3 normal_d; + +#define WATER +#include +#include + +void main() +{ +//wave distortion + move_factor += (param[2].z * time); + move_factor = mod(move_factor, 1); + vec2 texture_coords = f_coord; + vec2 distorted_tex_coord = texture(dudvmap, vec2(texture_coords.x + move_factor, texture_coords.y)).rg * 0.1; + distorted_tex_coord = texture_coords + vec2(distorted_tex_coord.x , distorted_tex_coord.y + move_factor); + vec2 total_distorted_tex_coord = (texture(dudvmap, distorted_tex_coord).rg * 2.0 - 1.0 ) * param[2].y; + texture_coords += total_distorted_tex_coord; + + normal_d = f_tbn * normalize(texture(normalmap, texture_coords).rgb * 2.0 - 1.0); + vec3 refvec = reflect(f_pos.xyz, normal_d); +#if ENVMAP_ENABLED + vec3 envcolor = texture(envmap, refvec).rgb; +#else + vec3 envcolor = vec3(0.5); +#endif + vec4 tex_color = texture(diffuse, texture_coords); +//Fresnel effect + vec3 view_dir = normalize(vec3(0.0f, 0.0f, 0.0f) - f_pos.xyz); + float fresnel = pow ( dot (f_normal, view_dir), 0.2 ); + float fresnel_inv = ((fresnel - 1.0 ) * -1.0 ); + + vec3 result = ambient * 0.5 + param[0].rgb * emission; + + if (lights_count > 0U) + { + vec2 part = calc_dir_light(lights[0]); + vec3 c = (part.x * param[1].x + part.y * param[1].y) * calc_shadow() * lights[0].color; + result += mix(c, envcolor, param[1].z * texture(normalmap, texture_coords ).a); + } + //result = mix(result, param[0].rgb, param[1].z); + result = (result * tex_color.rgb * param[1].z) + (result * (1.0 - param[1].z)); //multiply + //result = ( max(result + param[0].rgb -1.0,0.0) * param[1].z + result * (1.0 - param[1].z)); //linear burn + //result = ( min(param[0].rgb,result) * param[1].z + result * (1.0 - param[1].z)); //darken + + for (uint i = 1U; i < lights_count; i++) + { + light_s light = lights[i]; + vec2 part = vec2(0.0); + + if (light.type == LIGHT_SPOT) + part = calc_spot_light(light); + else if (light.type == LIGHT_POINT) + part = calc_point_light(light); + else if (light.type == LIGHT_DIR) + part = calc_dir_light(light); + result += light.color * (part.x * param[1].x + part.y * param[1].y); + } + + vec4 color = vec4(apply_fog(clamp(result,0.0,1.0)), clamp((fresnel_inv + param[0].a),0.0,1.0)); + +#if POSTFX_ENABLED + out_color = color; +#else + out_color = tonemap(color); +#endif +#if MOTIONBLUR_ENABLED + { + vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; + vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;; + + out_motion = vec4(a - b, 0.0f, 0.0f); + } +#endif +} diff --git a/shaders/traction.frag b/shaders/traction.frag index af40e8e3..f80639df 100644 --- a/shaders/traction.frag +++ b/shaders/traction.frag @@ -1,5 +1,6 @@ #include +in vec4 f_pos; in vec4 f_clip_pos; in vec4 f_clip_future_pos; @@ -9,10 +10,11 @@ layout(location = 1) out vec4 out_motion; #endif #include +#include void main() { - vec4 color = vec4(pow(param[0].rgb, vec3(2.2)), param[0].a); + vec4 color = vec4(apply_fog(pow(param[0].rgb, vec3(2.2))), param[0].a); #if POSTFX_ENABLED out_color = color; #else diff --git a/shaders/traction.vert b/shaders/traction.vert index 87ff1fa4..55d5bd9a 100644 --- a/shaders/traction.vert +++ b/shaders/traction.vert @@ -6,9 +6,11 @@ layout(location = 2) in vec2 v_coord; out vec4 f_clip_pos; out vec4 f_clip_future_pos; +out vec4 f_pos; void main() { + f_pos = modelview * vec4(v_vert, 1.0f); f_clip_pos = (projection * modelview) * vec4(v_vert, 1.0f); f_clip_future_pos = (projection * future * modelview) * vec4(v_vert, 1.0f); diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index 9272c669..806b2066 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -334,6 +334,7 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch >> nodedata.range_min >> nodedata.name >> nodedata.type; + if( nodedata.name == "none" ) { nodedata.name.clear(); } // type-based deserialization. not elegant but it'll do if( nodedata.type == "dynamic" ) { diff --git a/simulationtime.cpp b/simulationtime.cpp index 6ec42d08..6be76b42 100644 --- a/simulationtime.cpp +++ b/simulationtime.cpp @@ -80,6 +80,9 @@ scenario_time::init() { SYSTEMTIME DaylightDate; } timezoneinfo = { -60, 0, -60, { 0, 10, 0, 5, 3, 0, 0, 0 }, { 0, 3, 0, 5, 2, 0, 0, 0 } }; + convert_transition_time( timezoneinfo.StandardDate ); + convert_transition_time( timezoneinfo.DaylightDate ); + auto zonebias { timezoneinfo.Bias }; if( m_yearday < year_day( timezoneinfo.DaylightDate.wDay, timezoneinfo.DaylightDate.wMonth, m_time.wYear ) ) { zonebias += timezoneinfo.StandardBias; @@ -123,7 +126,7 @@ scenario_time::update( double const Deltatime ) { } m_time.wHour -= 24; } - int leap = ( m_time.wYear % 4 == 0 ) && ( m_time.wYear % 100 != 0 ) || ( m_time.wYear % 400 == 0 ); + int leap { ( m_time.wYear % 4 == 0 ) && ( m_time.wYear % 100 != 0 ) || ( m_time.wYear % 400 == 0 ) }; while( m_time.wDay > m_monthdaycounts[ leap ][ m_time.wMonth ] ) { m_time.wDay -= m_monthdaycounts[ leap ][ m_time.wMonth ]; @@ -146,7 +149,7 @@ scenario_time::year_day( int Day, const int Month, const int Year ) const { { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } }; - int leap { ( Year % 4 == 0 ) && ( Year % 100 != 0 ) || ( Year % 400 == 0 ) }; + int const leap { ( Year % 4 == 0 ) && ( Year % 100 != 0 ) || ( Year % 400 == 0 ) }; for( int i = 1; i < Month; ++i ) Day += daytab[ leap ][ i ]; @@ -161,7 +164,7 @@ scenario_time::daymonth( WORD &Day, WORD &Month, WORD const Year, WORD const Yea { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; - int leap = ( Year % 4 == 0 ) && ( Year % 100 != 0 ) || ( Year % 400 == 0 ); + int const leap { ( Year % 4 == 0 ) && ( Year % 100 != 0 ) || ( Year % 400 == 0 ) }; WORD idx = 1; while( ( idx < 13 ) && ( Yearday >= daytab[ leap ][ idx ] ) ) { @@ -194,10 +197,58 @@ scenario_time::julian_day() const { return JD; } -scenario_time::operator std::string(){ +// calculates day of week for provided date +int +scenario_time::day_of_week( int const Day, int const Month, int const Year ) const { - return to_string( m_time.wHour ) + ":" - + ( m_time.wMinute < 10 ? "0" : "" ) + to_string( m_time.wMinute ) + ":" - + ( m_time.wSecond < 10 ? "0" : "" ) + to_string( m_time.wSecond ); -}; -//--------------------------------------------------------------------------- + // using Zeller's congruence, http://en.wikipedia.org/wiki/Zeller%27s_congruence + int const q = Day; + int const m = Month > 2 ? Month : Month + 12; + int const y = Month > 2 ? Year : Year - 1; + + int const h = ( q + ( 26 * ( m + 1 ) / 10 ) + y + ( y / 4 ) + 6 * ( y / 100 ) + ( y / 400 ) ) % 7; + +/* return ( (h + 5) % 7 ) + 1; // iso week standard, with monday = 1 +*/ return ( (h + 6) % 7 ) + 1; // sunday = 1 numbering method, used in north america, japan +} + +// calculates day of month for specified weekday of specified month of the year +int +scenario_time::day_of_month( int const Week, int const Weekday, int const Month, int const Year ) const { + + int day = 0; + int dayoffset = weekdays( day_of_week( 1, Month, Year ), Weekday ); + + day = ( Week - 1 ) * 7 + 1 + dayoffset; + + if( Week == 5 ) { + // 5th week potentially indicates last week in the month, not necessarily actual 5th + char const daytab[ 2 ][ 13 ] = { + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + }; + int const leap { ( Year % 4 == 0 ) && ( Year % 100 != 0 ) || ( Year % 400 == 0 ) }; + + while( day > daytab[ leap ][ Month ] ) { + day -= 7; + } + } + + return day; +} + +// returns number of days between specified days of week +int +scenario_time::weekdays( int const First, int const Second ) const { + + if( Second >= First ) { return Second - First; } + else { return 7 - First + Second; } +} + +// helper, converts provided time transition date to regular date +void +scenario_time::convert_transition_time( SYSTEMTIME &Time ) const { + + // NOTE: windows uses 0-6 range for days of week numbering, our methods use 1-7 + Time.wDay = day_of_month( Time.wDay, Time.wDayOfWeek + 1, Time.wMonth, m_time.wYear ); +} diff --git a/simulationtime.h b/simulationtime.h index be5bdb86..e5667360 100644 --- a/simulationtime.h +++ b/simulationtime.h @@ -39,9 +39,6 @@ public: int year_day() const { return m_yearday; } - // helper, calculates day of year from given date - int - year_day( int Day, int const Month, int const Year ) const; int julian_day() const; inline @@ -52,9 +49,24 @@ public: /** Returns std::string in format: `"mm:ss"`. */ operator std::string(); private: + // converts provided time transition date to regular date + void + convert_transition_time( SYSTEMTIME &Time ) const; // calculates day and month from given day of year void daymonth( WORD &Day, WORD &Month, WORD const Year, WORD const Yearday ); + // calculates day of year from given date + int + year_day( int Day, int const Month, int const Year ) const; + // calculates day of week for provided date + int + day_of_week( int const Day, int const Month, int const Year ) const; + // calculates day of month for specified weekday of specified month of the year + int + day_of_month( int const Week, int const Weekday, int const Month, int const Year ) const; + // returns number of days between specified days of week + int + weekdays( int const First, int const Second ) const; SYSTEMTIME m_time; double m_milliseconds{ 0.0 }; diff --git a/sound.cpp b/sound.cpp index fd512f8c..ebe8be14 100644 --- a/sound.cpp +++ b/sound.cpp @@ -466,7 +466,7 @@ sound_source::stop( bool const Skipend ) { if( ( false == Skipend ) && ( sound( sound_id::end ).buffer != null_handle ) /* && ( sound( sound_id::end ).buffer != sound( sound_id::main ).buffer ) */ // end == main can happen in malformed legacy cases -/* && ( sound( sound_id::end ).playing == 0 ) */ ) { + && ( sound( sound_id::end ).playing < 2 ) ) { // allows potential single extra instance to account for longer overlapping sounds // spawn potentially defined sound end sample, if the emitter is currently active insert( sound_id::end ); } diff --git a/station.cpp b/station.cpp index 88a1c928..7e9d4654 100644 --- a/station.cpp +++ b/station.cpp @@ -37,12 +37,6 @@ basic_station::update_load( TDynamicObject *First, Mtable::TTrainParameters &Sch // go through all vehicles and update their load // NOTE: for the time being we limit ourselves to passenger-carrying cars only auto exchangetime { 0.f }; - // platform (1:left, 2:right, 3:both) - // with exchange performed on both sides waiting times are halved - auto const exchangetimemodifier { ( - Platform == 3 ? - 0.5f : - 1.0f ) }; auto *vehicle { First }; while( vehicle != nullptr ) { @@ -69,18 +63,14 @@ basic_station::update_load( TDynamicObject *First, Mtable::TTrainParameters &Sch 0 : Random( parameters.MaxLoad * 0.15f * stationsizemodifier ) ); if( true == firststop ) { - // slightly larger group at the initial station + // larger group at the initial station loadcount *= 2; } if( ( unloadcount > 0 ) || ( loadcount > 0 ) ) { vehicle->LoadExchange( unloadcount, loadcount, Platform ); -/* - vehicle->LoadUpdate(); - vehicle->update_load_visibility(); -*/ - exchangetime = std::max( exchangetime, exchangetimemodifier * ( unloadcount / parameters.UnLoadSpeed + loadcount / parameters.LoadSpeed ) ); + exchangetime = std::max( exchangetime, vehicle->LoadExchangeTime() ); } } vehicle = vehicle->Next(); diff --git a/translation.cpp b/translation.cpp index aeb6f770..8a267f8e 100644 --- a/translation.cpp +++ b/translation.cpp @@ -57,7 +57,7 @@ init() { "Controllers:\n master: %d(%d), secondary: %s\nEngine output: %.1f, current: %.0f\nRevolutions:\n engine: %.0f, motors: %.0f\n engine fans: %.0f, motor fans: %.0f+%.0f, cooling fans: %.0f+%.0f", " (shunt mode)", "\nTemperatures:\n engine: %.2f, oil: %.2f, water: %.2f%c%.2f", - "Brakes:\n train: %.2f, independent: %.2f, delay: %s, load flag: %d\nBrake cylinder pressures:\n train: %.2f, independent: %.2f, status: 0x%.2x\nPipe pressures:\n brake: %.2f (hat: %.2f), main: %.2f, control: %.2f\nTank pressures:\n auxiliary: %.2f, main: %.2f, control: %.2f", + "Brakes:\n train: %.2f, independent: %.2f, mode: %d, delay: %s, load flag: %d\nBrake cylinder pressures:\n train: %.2f, independent: %.2f, status: 0x%.2x\nPipe pressures:\n brake: %.2f (hat: %.2f), main: %.2f, control: %.2f\nTank pressures:\n auxiliary: %.2f, main: %.2f, control: %.2f", " pantograph: %.2f%cMT", "Forces:\n tractive: %.1f, brake: %.1f, friction: %.2f%s\nAcceleration:\n tangential: %.2f, normal: %.2f (path radius: %s)\nVelocity: %.2f, distance traveled: %.2f\nPosition: [%.2f, %.2f, %.2f]", @@ -89,6 +89,7 @@ init() { "brake acting speed", "brake acting speed: cargo", "brake acting speed: rapid", + "brake operation mode", "motor overload relay threshold", "water pump", "water pump breaker", @@ -211,7 +212,7 @@ init() { u8"Nastawniki:\n glówny: %d(%d), dodatkowy: %s\nMoc silnika: %.1f, prąd silnika: %.0f\nObroty:\n silnik: %.0f, motory: %.0f, went.silnika: %.0f, went.chłodnicy: %.0f+%.0f", u8" (tryb manewrowy)", u8"\nTemperatury:\n silnik: %.2f, olej: %.2f, woda: %.2f%c%.2f", - u8"Hamulce:\n zespolony: %.2f, pomocniczy: %.2f, nastawa: %s, ładunek: %d\nCiśnienie w cylindrach:\n zespolony: %.2f, pomocniczy: %.2f, status: 0x%.2x\nCiśnienia w przewodach:\n główny: %.2f (kapturek: %.2f), zasilający: %.2f, kontrolny: %.2f\nCiśnienia w zbiornikach:\n pomocniczy: %.2f, główny: %.2f, sterujący: %.2f", + u8"Hamulce:\n zespolony: %.2f, pomocniczy: %.2f, tryb: %d, nastawa: %s, ładunek: %d\nCiśnienie w cylindrach:\n zespolony: %.2f, pomocniczy: %.2f, status: 0x%.2x\nCiśnienia w przewodach:\n główny: %.2f (kapturek: %.2f), zasilający: %.2f, kontrolny: %.2f\nCiśnienia w zbiornikach:\n pomocniczy: %.2f, główny: %.2f, sterujący: %.2f", u8" pantograf: %.2f%cZG", u8"Siły:\n napędna: %.1f, hamowania: %.1f, tarcie: %.2f%s\nPrzyśpieszenia:\n styczne: %.2f, normalne: %.2f (promień: %s)\nPrędkość: %.2f, pokonana odleglość: %.2f\nPozycja: [%.2f, %.2f, %.2f]", @@ -243,6 +244,7 @@ init() { u8"nastawa hamulca", u8"nastawa hamulca: towarowy", u8"nastawa hamulca: pośpieszny", + u8"tryb pracy hamulca", u8"zakres prądu rozruchu", u8"pompa wody", u8"wyłącznik samoczynny pompy wody", @@ -352,6 +354,7 @@ init() { "brakeprofile_sw:", "brakeprofileg_sw:", "brakeprofiler_sw:", + "brakeopmode_sw", "maxcurrent_sw:", "waterpump_sw:", "waterpumpbreaker_sw:", diff --git a/translation.h b/translation.h index 5e58540f..1df119d3 100644 --- a/translation.h +++ b/translation.h @@ -78,6 +78,7 @@ enum string { cab_brakeprofile_sw, cab_brakeprofileg_sw, cab_brakeprofiler_sw, + cab_brakeopmode_sw, cab_maxcurrent_sw, cab_waterpump_sw, cab_waterpumpbreaker_sw, diff --git a/uilayer.cpp b/uilayer.cpp index 7ecc0d3f..f1ab3498 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -368,6 +368,8 @@ void ui_layer::render_menu_contents() ImGui::MenuItem(LOC_STR(ui_log), "F9", &m_logpanel.is_open); if (Global.map_enabled && m_map) ImGui::MenuItem(LOC_STR(ui_map), "Tab", &m_map->map_opened); + if (DebugModeFlag) + ImGui::MenuItem("Headlight config", nullptr, &GfxRenderer.debug_ui_active); ImGui::EndMenu(); } } diff --git a/utilities.cpp b/utilities.cpp index 8f54ae06..9285ca43 100644 --- a/utilities.cpp +++ b/utilities.cpp @@ -411,6 +411,18 @@ substr_path( std::string const &Filename ) { "" ); } +// returns length of common prefix between two provided strings +std::ptrdiff_t +len_common_prefix( std::string const &Left, std::string const &Right ) { + + auto const *left { Left.data() }; + auto const *right { Right.data() }; + // compare up to the length of the shorter string + return ( Right.size() <= Left.size() ? + std::distance( right, std::mismatch( right, right + Right.size(), left ).first ) : + std::distance( left, std::mismatch( left, left + Left.size(), right ).first ) ); +} + // helper, restores content of a 3d vector from provided input stream // TODO: review and clean up the helper routines, there's likely some redundant ones diff --git a/utilities.h b/utilities.h index 7cdce507..d8dd175e 100644 --- a/utilities.h +++ b/utilities.h @@ -201,6 +201,9 @@ replace_slashes( std::string &Filename ); // returns potential path part from provided file name std::string substr_path( std::string const &Filename ); +// returns common prefix of two provided strings +std::ptrdiff_t len_common_prefix( std::string const &Left, std::string const &Right ); + template void SafeDelete( Type_ &Pointer ) { delete Pointer; diff --git a/version.h b/version.h index 40ef8cb8..7435b613 100644 --- a/version.h +++ b/version.h @@ -1 +1 @@ -#define VERSION_INFO "M7 (GL3) 10.11.2018, based on milek-a1926c8b, tmj-c59b530" +#define VERSION_INFO "M7 (GL3) 31.12.2018, based on milek-4495f6a4, tmj-bdbbaaf"