diff --git a/AnimModel.cpp b/AnimModel.cpp index 1b6940d2..95564f22 100644 --- a/AnimModel.cpp +++ b/AnimModel.cpp @@ -445,8 +445,9 @@ bool TAnimModel::Init(std::string const &asName, std::string const &asReplacable // tekstura nieprzezroczysta - nie renderować w cyklu przezroczystych m_materialdata.textures_alpha = 0x30300030; } - - fBlinkTimer = double( Random( 1000 * fOffTime ) ) / ( 1000 * fOffTime ); + +// TODO: redo the random timer initialization +// fBlinkTimer = Random() * ( fOnTime + fOffTime ); pModel = TModelsManager::GetModel( asName ); return ( pModel != nullptr ); @@ -548,9 +549,73 @@ void TAnimModel::RaAnimate( unsigned int const Framestamp ) { if( Framestamp == m_framestamp ) { return; } - fBlinkTimer -= Timer::GetDeltaTime(); - if( fBlinkTimer <= 0 ) - fBlinkTimer += fOffTime; + auto const timedelta { Timer::GetDeltaTime() }; + + // interpretacja ułamka zależnie od typu + // case ls_Off: ustalenie czasu migotania, t<1s (f>1Hz), np. 0.1 => t=0.1 (f=10Hz) + // case ls_On: ustalenie wypełnienia ułamkiem, np. 1.25 => zapalony przez 1/4 okresu + // case ls_Blink: ustalenie częstotliwości migotania, f<1Hz (t>1s), np. 2.2 => f=0.2Hz (t=5s) + float modeintegral, modefractional; + for( int idx = 0; idx < iNumLights; ++idx ) { + + modefractional = std::modf( std::abs( lsLights[ idx ] ), &modeintegral ); + + if( modeintegral >= ls_Dark ) { + // light threshold modes don't use timers + continue; + } + auto const mode { static_cast( modeintegral ) }; + + auto &opacity { m_lightopacities[ idx ] }; + auto &timer { m_lighttimers[ idx ] }; + if( ( modeintegral < ls_Blink ) && ( modefractional < 0.01f ) ) { + // simple flip modes + switch( mode ) { + case ls_Off: { + // reduce to zero + timer = std::max( 0.f, timer - timedelta ); + break; + } + case ls_On: { + // increase to max value + timer = std::min( fTransitionTime, timer + timedelta ); + break; + } + default: { + break; + } + } + opacity = timer / fTransitionTime; + } + else { + // blink modes + auto const ontime { ( + ( mode == ls_Blink ) ? ( ( modefractional < 0.01f ) ? fOnTime : ( 1.f / modefractional ) * 0.5f ) : + ( mode == ls_Off ) ? modefractional * 0.5f : + ( mode == ls_On ) ? modefractional * ( fOnTime + fOffTime ) : + fOnTime ) }; // fallback + auto const offtime { ( + ( mode == ls_Blink ) ? ( ( modefractional < 0.01f ) ? fOffTime : ontime ) : + ( mode == ls_Off ) ? ontime : + ( mode == ls_On ) ? ( fOnTime + fOffTime ) - ontime : + fOffTime ) }; // fallback + auto const transitiontime { + std::min( + 1.f, + std::min( ontime, offtime ) * 0.9f ) }; + + timer = clamp_circular( timer + timedelta * ( lsLights[ idx ] > 0.f ? 1.f : -1.f ), ontime + offtime ); + // set opacity depending on blink stage + if( timer < ontime ) { + // blink on + opacity = clamp( timer / transitiontime, 0.f, 1.f ); + } + else { + // blink off + opacity = 1.f - clamp( ( timer - ontime ) / transitiontime, 0.f, 1.f ); + } + } + } // Ra 2F1I: to by można pomijać dla modeli bez animacji, których jest większość TAnimContainer *pCurrent; @@ -569,17 +634,19 @@ void TAnimModel::RaPrepare() bool state; // stan światła for (int i = 0; i < iNumLights; ++i) { - auto const lightmode { static_cast( lsLights[ i ] ) }; + auto const lightmode { static_cast( std::abs( lsLights[ i ] ) ) }; switch( lightmode ) { case ls_On: - case ls_Off: { - // zapalony albo zgaszony - state = ( lightmode == ls_On ); - break; - } + case ls_Off: case ls_Blink: { - // migotanie - state = ( fBlinkTimer < fOnTime ); + if (LightsOn[i]) { + LightsOn[i]->iVisible = ( m_lightopacities[i] > 0.f ); + LightsOn[i]->SetVisibilityLevel( m_lightopacities[i], true, false ); + } + if (LightsOff[i]) { + LightsOff[i]->iVisible = ( m_lightopacities[i] < 1.f ); + LightsOff[i]->SetVisibilityLevel( 1.f, true, false ); + } break; } case ls_Dark: { @@ -607,10 +674,16 @@ void TAnimModel::RaPrepare() break; } } - if (LightsOn[i]) - LightsOn[i]->iVisible = state; - if (LightsOff[i]) - LightsOff[i]->iVisible = !state; + if( lightmode >= ls_Dark ) { + // crude as hell but for test will do :x + if (LightsOn[i]) { + LightsOn[i]->iVisible = state; + // TODO: set visibility for the entire submodel's children as well + LightsOn[i]->fVisible = m_lightopacities[i]; + } + if (LightsOff[i]) + LightsOff[i]->iVisible = !state; + } } TSubModel::iInstance = reinterpret_cast( this ); //żeby nie robić cudzych animacji TSubModel::pasText = &asText; // przekazanie tekstu do wyświetlacza (!!!! do przemyślenia) @@ -775,8 +848,9 @@ void TAnimModel::AnimationVND(void *pData, double a, double b, double c, double //--------------------------------------------------------------------------- void TAnimModel::LightSet(int const n, float const v) { // ustawienie światła (n) na wartość (v) - if (n >= iMaxNumLights) + if( n >= iMaxNumLights ) { return; // przekroczony zakres + } lsLights[ n ] = v; }; diff --git a/AnimModel.h b/AnimModel.h index de3bdd01..ac33e7b3 100644 --- a/AnimModel.h +++ b/AnimModel.h @@ -179,7 +179,7 @@ private: // members TAnimContainer *pRoot { nullptr }; // pojemniki sterujące, tylko dla aniomowanych submodeli TModel3d *pModel { nullptr }; - double fBlinkTimer { 0.0 }; +// double fBlinkTimer { 0.0 }; int iNumLights { 0 }; TSubModel *LightsOn[ iMaxNumLights ]; // Ra: te wskaźniki powinny być w ramach TModel3d TSubModel *LightsOff[ iMaxNumLights ]; @@ -188,10 +188,13 @@ private: std::string asText; // tekst dla wyświetlacza znakowego TAnimAdvanced *pAdvanced { nullptr }; + // TODO: wrap into a light state struct float lsLights[ iMaxNumLights ]; -// float fDark { DefaultDarkThresholdLevel }; // poziom zapalanie światła (powinno być chyba powiązane z danym światłem?) - float fOnTime { 0.66f }; - float fOffTime { 0.66f + 0.66f }; // były stałymi, teraz mogą być zmienne dla każdego egzemplarza + std::array m_lighttimers { 0.f }; + std::array m_lightopacities { 1.f }; + float fOnTime { 1.f / 2 };// { 60.f / 45.f / 2 }; + float fOffTime { 1.f / 2 };// { 60.f / 45.f / 2 }; // były stałymi, teraz mogą być zmienne dla każdego egzemplarza + float fTransitionTime { fOnTime * 0.9f }; // time unsigned int m_framestamp { 0 }; // id of last rendered gfx frame }; diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a44df0b..69d4a496 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,9 +115,17 @@ set(SOURCES "imgui/imgui_demo.cpp" "imgui/imgui_draw.cpp" "imgui/imgui_impl_glfw.cpp" -"imgui/imgui_impl_opengl3.cpp" ) +option(USE_IMGUI_GL3 "Use OpenGL3+ imgui implementation" ON) + +if (USE_IMGUI_GL3) + set(SOURCES ${SOURCES} "imgui/imgui_impl_opengl3.cpp") +else() + add_definitions(-DEU07_USEIMGUIIMPLOPENGL2) + set(SOURCES ${SOURCES} "imgui/imgui_impl_opengl2.cpp") +endif() + set (PREFIX "") if (WIN32) diff --git a/Driver.cpp b/Driver.cpp index 7a2a29c6..7fcada44 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -339,8 +339,8 @@ std::string TSpeedPos::TableText() const { // pozycja tabelki pr?dko?ci if (iFlags & spEnabled) { // o ile pozycja istotna - return "Flags:" + to_hex_str(iFlags, 8) + ", Dist:" + to_string(fDist, 1, 6) + - ", Vel:" + (fVelNext == -1.0 ? " - " : to_string(static_cast(fVelNext), 0, 3)) + ", Name:" + GetName(); + return to_hex_str(iFlags, 8) + " " + to_string(fDist, 1, 6) + + " " + (fVelNext == -1.0 ? " -" : to_string(static_cast(fVelNext), 0, 3)) + " " + GetName(); } return "Empty"; } @@ -2437,6 +2437,11 @@ bool TController::PrepareEngine() mvOccupied->ConverterSwitch( true ); // w EN57 sprężarka w ra jest zasilana z silnikowego mvOccupied->CompressorSwitch( true ); + // enable motor blowers + mvOccupied->MotorBlowersSwitchOff( false, side::front ); + mvOccupied->MotorBlowersSwitch( true, side::front ); + mvOccupied->MotorBlowersSwitchOff( false, side::rear ); + mvOccupied->MotorBlowersSwitch( true, side::rear ); } } else @@ -4254,123 +4259,6 @@ TController::UpdateSituation(double dt) { fMaxProximityDist = 10.0; //[m] fVelPlus = 1.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu - if (AIControllFlag) { - if (iVehicleCount >= 0) { - // jeśli była podana ilość wagonów - if (iDrivigFlags & movePress) { - // jeśli dociskanie w celu odczepienia - // 3. faza odczepiania. - SetVelocity(2, 0); // jazda w ustawionym kierunku z prędkością 2 - if ((mvControlling->MainCtrlPos > 0) || - (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic)) // jeśli jazda - { - WriteLog(mvOccupied->Name + " odczepianie w kierunku " + std::to_string(mvOccupied->DirAbsolute)); - TDynamicObject *p = - pVehicle; // pojazd do odczepienia, w (pVehicle) siedzi AI - int d; // numer sprzęgu, który sprawdzamy albo odczepiamy - int n = iVehicleCount; // ile wagonów ma zostać - do - { // szukanie pojazdu do odczepienia - d = p->DirectionGet() > 0 ? - 0 : - 1; // numer sprzęgu od strony czoła składu - // if (p->MoverParameters->Couplers[d].CouplerType==Articulated) - // //jeśli sprzęg typu wózek (za mało) - if (p->MoverParameters->Couplers[d].CouplingFlag & - ctrain_depot) // jeżeli sprzęg zablokowany - // if (p->GetTrack()->) //a nie stoi na torze warsztatowym - // (ustalić po czym poznać taki tor) - ++n; // to liczymy człony jako jeden - p->MoverParameters->BrakeReleaser(1); // wyluzuj pojazd, aby dało się dopychać - p->MoverParameters->BrakeLevelSet(0); // hamulec na zero, aby nie hamował - if (n) - { // jeśli jeszcze nie koniec - p = p->Prev(); // kolejny w stronę czoła składu (licząc od - // tyłu), bo dociskamy - if (!p) - iVehicleCount = -2, - n = 0; // nie ma co dalej sprawdzać, doczepianie zakończone - } - } while (n--); - if( p ? p->MoverParameters->Couplers[ d ].CouplingFlag == coupling::faux : true ) { - // no target, or already just virtual coupling - WriteLog( mvOccupied->Name + " didn't find anything to disconnect." ); - iVehicleCount = -2; // odczepiono, co było do odczepienia - } else if ( p->Dettach(d) == coupling::faux ) { - // tylko jeśli odepnie - WriteLog( mvOccupied->Name + " odczepiony." ); - iVehicleCount = -2; - } // a jak nie, to dociskać dalej - } - if (iVehicleCount >= 0) // zmieni się po odczepieniu - if (!mvOccupied->DecLocalBrakeLevel(1)) - { // dociśnij sklad - WriteLog( mvOccupied->Name + " dociskanie..." ); - // mvOccupied->BrakeReleaser(); //wyluzuj lokomotywę - // Ready=true; //zamiast sprawdzenia odhamowania całego składu - IncSpeed(); // dla (Ready)==false nie ruszy - } - } - if ((mvOccupied->Vel < 0.01) && !(iDrivigFlags & movePress)) - { // 2. faza odczepiania: zmień kierunek na przeciwny i dociśnij - // za radą yB ustawiamy pozycję 3 kranu (ruszanie kranem w innych miejscach - // powino zostać wyłączone) - // WriteLog("Zahamowanie składu"); - mvOccupied->BrakeLevelSet( - mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ? - 1 : - 3 ); - double p = mvOccupied->BrakePressureActual.PipePressureVal; - if( p < 3.9 ) { - // tu może być 0 albo -1 nawet - // TODO: zabezpieczenie przed dziwnymi CHK do czasu wyjaśnienia sensu 0 oraz -1 w tym miejscu - p = 3.9; - } - if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ? - mvOccupied->BrakePress > 2 : - mvOccupied->PipePress < p + 0.1) - { // jeśli w miarę został zahamowany (ciśnienie mniejsze niż podane na - // pozycji 3, zwyle 0.37) - if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic) - mvOccupied->BrakeLevelSet(0); // wyłączenie EP, gdy wystarczy (może - // nie być potrzebne, bo na początku - // jest) - WriteLog("Luzowanie lokomotywy i zmiana kierunku"); - mvOccupied->BrakeReleaser(1); // wyluzuj lokomotywę; a ST45? - mvOccupied->DecLocalBrakeLevel(LocalBrakePosNo); // zwolnienie hamulca - iDrivigFlags |= movePress; // następnie będzie dociskanie - DirectionForward(mvOccupied->ActiveDir < 0); // zmiana kierunku jazdy na przeciwny (dociskanie) - CheckVehicles(); // od razu zmienić światła (zgasić) - bez tego się nie odczepi -/* - // NOTE: disabled to prevent closing the door before passengers can disembark - fStopTime = 0.0; // nie ma na co czekać z odczepianiem -*/ - } - } - else { - if( mvOccupied->Vel > 0.01 ) { - // 1st phase(?) - // bring it to stop if it's not already stopped - SetVelocity( 0, 0, stopJoin ); // wyłączyć przyspieszanie - } - } - } // odczepiania - else // to poniżej jeśli ilość wagonów ujemna - if (iDrivigFlags & movePress) - { // 4. faza odczepiania: zwolnij i zmień kierunek - SetVelocity(0, 0, stopJoin); // wyłączyć przyspieszanie - if (!DecSpeed()) // jeśli już bardziej wyłączyć się nie da - { // ponowna zmiana kierunku - WriteLog( mvOccupied->Name + " ponowna zmiana kierunku" ); - DirectionForward(mvOccupied->ActiveDir < 0); // zmiana kierunku jazdy na właściwy - iDrivigFlags &= ~movePress; // koniec dociskania - JumpToNextOrder(); // zmieni światła - TableClear(); // skanowanie od nowa - iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem - SetVelocity(fShuntVelocity, fShuntVelocity); // ustawienie prędkości jazdy - } - } - } break; } case Shunt: { @@ -4585,10 +4473,13 @@ TController::UpdateSituation(double dt) { } // ustalanie zadanej predkosci - if (iDrivigFlags & moveActive) // jeśli może skanować sygnały i reagować na komendy - { // jeśli jest wybrany kierunek jazdy, można ustalić prędkość jazdy - // Ra: tu by jeszcze trzeba było wstawić uzależnienie (VelDesired) od odległości od - // przeszkody + if (iDrivigFlags & moveActive) { + + SetDriverPsyche(); // ustawia AccPreferred (potrzebne tu?) + + // jeśli może skanować sygnały i reagować na komendy + // jeśli jest wybrany kierunek jazdy, można ustalić prędkość jazdy + // Ra: tu by jeszcze trzeba było wstawić uzależnienie (VelDesired) od odległości od przeszkody // no chyba żeby to uwzgldnić już w (ActualProximityDist) VelDesired = fVelMax; // wstępnie prędkość maksymalna dla pojazdu(-ów), będzie // następnie ograniczana @@ -4597,9 +4488,6 @@ TController::UpdateSituation(double dt) { // jeśli ma rozkład i ograniczenie w rozkładzie to nie przekraczać rozkladowej VelDesired = min_speed( VelDesired, TrainParams->TTVmax ); } - - SetDriverPsyche(); // ustawia AccPreferred (potrzebne tu?) - // szukanie optymalnych wartości AccDesired = AccPreferred; // AccPreferred wynika z osobowości mechanika VelNext = VelDesired; // maksymalna prędkość wynikająca z innych czynników niż trajektoria ruchu @@ -4647,6 +4535,126 @@ TController::UpdateSituation(double dt) { default: break; } + // disconnect mode potentially overrides scan results + // TBD: when in this mode skip scanning altogether? + if( ( OrderCurrentGet() & Disconnect ) != 0 ) { + + if (AIControllFlag) { + if (iVehicleCount >= 0) { + // jeśli była podana ilość wagonów + if (iDrivigFlags & movePress) { + // jeśli dociskanie w celu odczepienia + // 3. faza odczepiania. + SetVelocity(2, 0); // jazda w ustawionym kierunku z prędkością 2 + if ((mvControlling->MainCtrlPos > 0) || + (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic)) // jeśli jazda + { + WriteLog(mvOccupied->Name + " odczepianie w kierunku " + std::to_string(mvOccupied->DirAbsolute)); + TDynamicObject *p = + pVehicle; // pojazd do odczepienia, w (pVehicle) siedzi AI + int d; // numer sprzęgu, który sprawdzamy albo odczepiamy + int n = iVehicleCount; // ile wagonów ma zostać + do + { // szukanie pojazdu do odczepienia + d = p->DirectionGet() > 0 ? + 0 : + 1; // numer sprzęgu od strony czoła składu + // if (p->MoverParameters->Couplers[d].CouplerType==Articulated) + // //jeśli sprzęg typu wózek (za mało) + if (p->MoverParameters->Couplers[d].CouplingFlag & ctrain_depot) // jeżeli sprzęg zablokowany + // if (p->GetTrack()->) //a nie stoi na torze warsztatowym + // (ustalić po czym poznać taki tor) + ++n; // to liczymy człony jako jeden + p->MoverParameters->BrakeReleaser(1); // wyluzuj pojazd, aby dało się dopychać + p->MoverParameters->BrakeLevelSet(0); // hamulec na zero, aby nie hamował + if (n) + { // jeśli jeszcze nie koniec + p = p->Prev(); // kolejny w stronę czoła składu (licząc od + // tyłu), bo dociskamy + if (!p) + iVehicleCount = -2, + n = 0; // nie ma co dalej sprawdzać, doczepianie zakończone + } + } while (n--); + if( p ? p->MoverParameters->Couplers[ d ].CouplingFlag == coupling::faux : true ) { + // no target, or already just virtual coupling + WriteLog( mvOccupied->Name + " didn't find anything to disconnect." ); + iVehicleCount = -2; // odczepiono, co było do odczepienia + } else if ( p->Dettach(d) == coupling::faux ) { + // tylko jeśli odepnie + WriteLog( mvOccupied->Name + " odczepiony." ); + iVehicleCount = -2; + } // a jak nie, to dociskać dalej + } + if (iVehicleCount >= 0) // zmieni się po odczepieniu + if (!mvOccupied->DecLocalBrakeLevel(1)) + { // dociśnij sklad + WriteLog( mvOccupied->Name + " dociskanie..." ); + // mvOccupied->BrakeReleaser(); //wyluzuj lokomotywę + // Ready=true; //zamiast sprawdzenia odhamowania całego składu + IncSpeed(); // dla (Ready)==false nie ruszy + } + } + if ((mvOccupied->Vel < 0.01) && !(iDrivigFlags & movePress)) + { // 2. faza odczepiania: zmień kierunek na przeciwny i dociśnij + // za radą yB ustawiamy pozycję 3 kranu (ruszanie kranem w innych miejscach + // powino zostać wyłączone) + // WriteLog("Zahamowanie składu"); + mvOccupied->BrakeLevelSet( + mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ? + 1 : + 3 ); + double p = mvOccupied->BrakePressureActual.PipePressureVal; + if( p < 3.9 ) { + // tu może być 0 albo -1 nawet + // TODO: zabezpieczenie przed dziwnymi CHK do czasu wyjaśnienia sensu 0 oraz -1 w tym miejscu + p = 3.9; + } + if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic ? + mvOccupied->BrakePress > 2 : + mvOccupied->PipePress < p + 0.1) + { // jeśli w miarę został zahamowany (ciśnienie mniejsze niż podane na + // pozycji 3, zwyle 0.37) + if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic) + mvOccupied->BrakeLevelSet(0); // wyłączenie EP, gdy wystarczy (może + // nie być potrzebne, bo na początku jest) + WriteLog("Luzowanie lokomotywy i zmiana kierunku"); + mvOccupied->BrakeReleaser(1); // wyluzuj lokomotywę; a ST45? + mvOccupied->DecLocalBrakeLevel(LocalBrakePosNo); // zwolnienie hamulca + iDrivigFlags |= movePress; // następnie będzie dociskanie + DirectionForward(mvOccupied->ActiveDir < 0); // zmiana kierunku jazdy na przeciwny (dociskanie) + CheckVehicles(); // od razu zmienić światła (zgasić) - bez tego się nie odczepi + /* + // NOTE: disabled to prevent closing the door before passengers can disembark + fStopTime = 0.0; // nie ma na co czekać z odczepianiem + */ + } + } + else { + if( mvOccupied->Vel > 0.01 ) { + // 1st phase(?) + // bring it to stop if it's not already stopped + SetVelocity( 0, 0, stopJoin ); // wyłączyć przyspieszanie + } + } + } // odczepiania + else // to poniżej jeśli ilość wagonów ujemna + if (iDrivigFlags & movePress) + { // 4. faza odczepiania: zwolnij i zmień kierunek + SetVelocity(0, 0, stopJoin); // wyłączyć przyspieszanie + if (!DecSpeed()) // jeśli już bardziej wyłączyć się nie da + { // ponowna zmiana kierunku + WriteLog( mvOccupied->Name + " ponowna zmiana kierunku" ); + DirectionForward(mvOccupied->ActiveDir < 0); // zmiana kierunku jazdy na właściwy + iDrivigFlags &= ~movePress; // koniec dociskania + JumpToNextOrder(); // zmieni światła + TableClear(); // skanowanie od nowa + iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem + SetVelocity(fShuntVelocity, fShuntVelocity); // ustawienie prędkości jazdy + } + } + } + } if( true == TestFlag( OrderList[ OrderPos ], Change_direction ) ) { // if ordered to change direction, try to stop diff --git a/Driver.h b/Driver.h index 1596ff03..89bf75ef 100644 --- a/Driver.h +++ b/Driver.h @@ -167,7 +167,7 @@ static const bool Aggressive = true; static const bool Easyman = false; static const bool AIdriver = true; static const bool Humandriver = false; -static const int maxorders = 32; // ilość rozkazów w tabelce +static const int maxorders = 64; // ilość rozkazów w tabelce static const int maxdriverfails = 4; // ile błędów może zrobić AI zanim zmieni nastawienie extern bool WriteLogFlag; // logowanie parametrów fizycznych static const int BrakeAccTableSize = 20; diff --git a/DynObj.cpp b/DynObj.cpp index 3da8ec51..35822970 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -2687,7 +2687,7 @@ void TDynamicObject::LoadUpdate() { } else if( MoverParameters->LoadAmount == 0 ) { // nie ma ładunku - MoverParameters->AssignLoad( "" ); +// MoverParameters->AssignLoad( "" ); mdLoad = nullptr; // erase bindings between lowpoly sections and potential load chunks placed inside them update_load_sections(); @@ -5274,6 +5274,35 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co } } + else if( token == "motorblower:" ) { + + sound_source blowertemplate { sound_placement::engine }; + blowertemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); + blowertemplate.owner( this ); + + auto const amplitudedivisor = static_cast( + MoverParameters->MotorBlowers[ side::front ].speed > 0.f ? + MoverParameters->MotorBlowers[ side::front ].speed * MoverParameters->nmax * 60 + MoverParameters->Power * 3 : + MoverParameters->MotorBlowers[ side::front ].speed * -1 ); + blowertemplate.m_amplitudefactor /= amplitudedivisor; + blowertemplate.m_frequencyfactor /= amplitudedivisor; + + if( true == m_powertrainsounds.motors.empty() ) { + // fallback for cases without specified motor locations, convert sound template to a single sound source + m_powertrainsounds.motorblowers.emplace_back( blowertemplate ); + } + else { + // apply configuration to all defined motor blowers + for( auto &blower : m_powertrainsounds.motorblowers ) { + // combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual blowers + auto bloweroffset { blowertemplate.offset() }; + bloweroffset.z = blower.offset().z; + blower = blowertemplate; + blower.offset( bloweroffset ); + } + } + } + else if( token == "inverter:" ) { // plik z dzwiekiem wentylatora, mnozniki i ofsety amp. i czest. m_powertrainsounds.inverter.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency ); @@ -5621,10 +5650,13 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co auto const offset { std::atof( token.c_str() ) * -1.f }; // NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization sound_source motor { sound_placement::external }; // generally traction motor + sound_source motorblower { sound_placement::engine }; // associated motor blowers // add entry to the list auto const location { glm::vec3 { 0.f, 0.f, offset } }; motor.offset( location ); m_powertrainsounds.motors.emplace_back( motor ); + motorblower.offset( location ); + m_powertrainsounds.motorblowers.emplace_back( motorblower ); } } @@ -6728,6 +6760,27 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub motor.stop(); } } + // motor blowers + if( false == motorblowers.empty() ) { + for( auto &blowersound : motorblowers ) { + // match the motor blower and the sound source based on whether they're located in the front or the back of the vehicle + auto const &blower { Vehicle.MotorBlowers[ ( blowersound.offset().z > 0 ? side::front : side::rear ) ] }; + if( blower.revolutions > 1 ) { + + blowersound + .pitch( + true == blowersound.is_combined() ? + blower.revolutions * 0.01f : + blowersound.m_frequencyoffset + blowersound.m_frequencyfactor * blower.revolutions ) + .gain( blowersound.m_amplitudeoffset + blowersound.m_amplitudefactor * blower.revolutions ) + .play( sound_flags::exclusive | sound_flags::looping ); + } + else { + blowersound.stop(); + } + } + } + // inverter sounds if( Vehicle.EngineType == TEngineType::ElectricInductionMotor ) { if( Vehicle.InverterFrequency > 0.1 ) { @@ -7034,7 +7087,7 @@ vehicle_table::erase_disabled() { if( ( simulation::Train != nullptr ) && ( simulation::Train->Dynamic() == vehicle ) ) { // clear potential train binding - // TBD, TODO: manually eject the driver first ? + // TBD, TODO: kill vehicle sounds SafeDelete( simulation::Train ); } // remove potential entries in the light array diff --git a/DynObj.h b/DynObj.h index ea667610..f54a3e37 100644 --- a/DynObj.h +++ b/DynObj.h @@ -330,6 +330,7 @@ private: struct powertrain_sounds { sound_source inverter { sound_placement::engine }; + std::vector motorblowers; std::vector motors; // generally traction motor(s) double motor_volume { 0.0 }; // MC: pomocnicze zeby gladziej silnik buczal float motor_momentum { 0.f }; // recent change in motor revolutions diff --git a/Globals.cpp b/Globals.cpp index 445ab4ff..0284037a 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -349,6 +349,11 @@ global_settings::ConfigParse(cParser &Parser) { Parser >> splinefidelity; SplineFidelity = clamp( splinefidelity, 1.f, 4.f ); } + else if( token == "createswitchtrackbeds" ) { + // podwójna jasność ambient + Parser.getTokens(); + Parser >> CreateSwitchTrackbeds; + } else if( token == "gfx.resource.sweep" ) { Parser.getTokens(); diff --git a/Globals.h b/Globals.h index c94cb881..4c9e9625 100644 --- a/Globals.h +++ b/Globals.h @@ -53,6 +53,7 @@ struct global_settings { // settings // filesystem bool bLoadTraction{ true }; + bool CreateSwitchTrackbeds { true }; std::string szTexturesTGA{ ".tga" }; // lista tekstur od TGA std::string szTexturesDDS{ ".dds" }; // lista tekstur od DDS std::string szDefaultExt{ szTexturesDDS }; diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index fed359ee..57d948a1 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -166,6 +166,7 @@ enum class start_t { manual, automatic, manualwithautofallback, + converter, battery }; // recognized vehicle light locations and types; can be combined @@ -619,107 +620,122 @@ struct TCoupling { int sounds { 0 }; // sounds emitted by the coupling devices }; -// basic approximation of a fuel pump -// TODO: fuel consumption, optional automatic engine start after activation -struct fuel_pump { - - bool is_enabled { false }; // device is allowed/requested to operate - bool is_disabled { false }; // device is requested to stop - bool is_active { false }; // device is working - start_t start_type { start_t::manual }; -}; - -// basic approximation of a fuel pump -// TODO: fuel consumption, optional automatic engine start after activation -struct oil_pump { - - bool is_enabled { false }; // device is allowed/requested to operate - bool is_disabled { false }; // device is requested to stop - bool is_active { false }; // device is working - start_t start_type { start_t::manual }; - float resource_amount { 1.f }; - float pressure_minimum { 0.f }; // lowest acceptable working pressure - float pressure_maximum { 0.65f }; // oil pressure at maximum engine revolutions - float pressure_target { 0.f }; - float pressure_present { 0.f }; -}; - -struct water_pump { - - bool breaker { true }; // device is allowed to operate - bool is_enabled { false }; // device is requested to operate - bool is_disabled { false }; // device is requested to stop - bool is_active { false }; // device is working - start_t start_type { start_t::manual }; -}; - -struct water_heater { - - bool breaker { true }; // device is allowed to operate - bool is_enabled { false }; // device is requested to operate - bool is_active { false }; // device is working - bool is_damaged { false }; // device is damaged - - struct heater_config_t { - float temp_min { -1 }; // lowest accepted temperature - float temp_max { -1 }; // highest accepted temperature - } config; -}; - -struct heat_data { - // input, state of relevant devices - bool cooling { false }; // TODO: user controlled device, implement -// bool okienko { true }; // window in the engine compartment - // system configuration - bool auxiliary_water_circuit { false }; // cooling system has an extra water circuit - double fan_speed { 0.075 }; // cooling fan rpm; either fraction of engine rpm, or absolute value if negative - // heat exchange factors - double kw { 0.35 }; - double kv { 0.6 }; - double kfe { 1.0 }; - double kfs { 80.0 }; - double kfo { 25.0 }; - double kfo2 { 25.0 }; - // system parts - struct fluid_circuit_t { - - struct circuit_config_t { - float temp_min { -1 }; // lowest accepted temperature - float temp_max { -1 }; // highest accepted temperature - float temp_cooling { -1 }; // active cooling activation point - float temp_flow { -1 }; // fluid flow activation point - bool shutters { false }; // the radiator has shutters to assist the cooling - } config; - bool is_cold { false }; // fluid is too cold - bool is_warm { false }; // fluid is too hot - bool is_hot { false }; // fluid temperature crossed cooling threshold - bool is_flowing { false }; // fluid is being pushed through the circuit - } water, - water_aux, - oil; - // output, state of affected devices - bool PA { false }; // malfunction flag - float rpmw { 0.0 }; // current main circuit fan revolutions - float rpmwz { 0.0 }; // desired main circuit fan revolutions - bool zaluzje1 { false }; - float rpmw2 { 0.0 }; // current auxiliary circuit fan revolutions - float rpmwz2 { 0.0 }; // desired auxiliary circuit fan revolutions - bool zaluzje2 { false }; - // output, temperatures - float Te { 15.0 }; // ambient temperature TODO: get it from environment data - // NOTE: by default the engine is initialized in warm, startup-ready state - float Ts { 50.0 }; // engine temperature - float To { 45.0 }; // oil temperature - float Tsr { 50.0 }; // main circuit radiator temperature (?) - float Twy { 50.0 }; // main circuit water temperature - float Tsr2 { 40.0 }; // secondary circuit radiator temperature (?) - float Twy2 { 40.0 }; // secondary circuit water temperature - float temperatura1 { 50.0 }; - float temperatura2 { 40.0 }; -}; - class TMoverParameters { // Ra: wrapper na kod pascalowy, przejmujący jego funkcje Q: 20160824 - juz nie wrapper a klasa bazowa :) +private: +// types + + // basic approximation of a generic device + // TBD: inheritance or composition? + struct basic_device { + // config + start_t start_type { start_t::manual }; + // ld inputs + bool is_enabled { false }; // device is allowed/requested to operate + bool is_disabled { false }; // device is requested to stop + // TODO: add remaining inputs; start conditions and potential breakers + // ld outputs + bool is_active { false }; // device is working + }; + + struct cooling_fan : public basic_device { + // config + float speed { 0.f }; // cooling fan rpm; either fraction of parent rpm, or absolute value if negative + // ld outputs + float revolutions { 0.f }; // current fan rpm + }; + + // basic approximation of a fuel pump + struct fuel_pump : public basic_device { + // TODO: fuel consumption, optional automatic engine start after activation + }; + + // basic approximation of an oil pump + struct oil_pump : public basic_device { + // config + float pressure_minimum { 0.f }; // lowest acceptable working pressure + float pressure_maximum { 0.65f }; // oil pressure at maximum engine revolutions + // ld inputs + float resource_amount { 1.f }; // amount of affected resource, compared to nominal value + // internal data + float pressure_target { 0.f }; + // ld outputs + float pressure { 0.f }; // current pressure + }; + + // basic approximation of a water pump + struct water_pump : public basic_device { + // ld inputs + // TODO: move to breaker list in the basic device once implemented + bool breaker { true }; // device is allowed to operate + }; + + struct water_heater { + // config + struct heater_config_t { + float temp_min { -1 }; // lowest accepted temperature + float temp_max { -1 }; // highest accepted temperature + } config; + // ld inputs + bool breaker { true }; // device is allowed to operate + bool is_enabled { false }; // device is requested to operate + // ld outputs + bool is_active { false }; // device is working + bool is_damaged { false }; // device is damaged + }; + + struct heat_data { + // input, state of relevant devices + bool cooling { false }; // TODO: user controlled device, implement + // bool okienko { true }; // window in the engine compartment + // system configuration + bool auxiliary_water_circuit { false }; // cooling system has an extra water circuit + double fan_speed { 0.075 }; // cooling fan rpm; either fraction of engine rpm, or absolute value if negative + // heat exchange factors + double kw { 0.35 }; + double kv { 0.6 }; + double kfe { 1.0 }; + double kfs { 80.0 }; + double kfo { 25.0 }; + double kfo2 { 25.0 }; + // system parts + struct fluid_circuit_t { + + struct circuit_config_t { + float temp_min { -1 }; // lowest accepted temperature + float temp_max { -1 }; // highest accepted temperature + float temp_cooling { -1 }; // active cooling activation point + float temp_flow { -1 }; // fluid flow activation point + bool shutters { false }; // the radiator has shutters to assist the cooling + } config; + bool is_cold { false }; // fluid is too cold + bool is_warm { false }; // fluid is too hot + bool is_hot { false }; // fluid temperature crossed cooling threshold + bool is_flowing { false }; // fluid is being pushed through the circuit + } water, + water_aux, + oil; + // output, state of affected devices + bool PA { false }; // malfunction flag + float rpmw { 0.0 }; // current main circuit fan revolutions + float rpmwz { 0.0 }; // desired main circuit fan revolutions + bool zaluzje1 { false }; + float rpmw2 { 0.0 }; // current auxiliary circuit fan revolutions + float rpmwz2 { 0.0 }; // desired auxiliary circuit fan revolutions + bool zaluzje2 { false }; + // output, temperatures + float Te { 15.0 }; // ambient temperature TODO: get it from environment data + // NOTE: by default the engine is initialized in warm, startup-ready state + float Ts { 50.0 }; // engine temperature + float To { 45.0 }; // oil temperature + float Tsr { 50.0 }; // main circuit radiator temperature (?) + float Twy { 50.0 }; // main circuit water temperature + float Tsr2 { 40.0 }; // secondary circuit radiator temperature (?) + float Twy2 { 40.0 }; // secondary circuit water temperature + float temperatura1 { 50.0 }; + float temperatura2 { 40.0 }; + }; + public: double dMoveLen = 0.0; @@ -1040,6 +1056,7 @@ public: water_heater WaterHeater; bool WaterCircuitsLink { false }; // optional connection between water circuits heat_data dizel_heat; + std::array MotorBlowers; int BrakeCtrlPos = -2; /*nastawa hamulca zespolonego*/ double BrakeCtrlPosR = 0.0; /*nastawa hamulca zespolonego - plynna dla FV4a*/ @@ -1126,7 +1143,7 @@ public: bool StLinSwitchOff{ false }; // state of the button forcing motor connectors open bool ResistorsFlag = false; /*!o jazda rezystorowa*/ double RventRot = 0.0; /*!s obroty wentylatorow rozruchowych*/ - bool UnBrake = false; /*w EZT - nacisniete odhamowywanie*/ + bool UnBrake = false; /*w EZT - nacisniete odhamowywanie*/ double PantPress = 0.0; /*Cisnienie w zbiornikach pantografow*/ bool PantPressSwitchActive{ false }; // state of the pantograph pressure switch. gets primed at defined pressure level in pantograph air system bool PantPressLockActive{ false }; // pwr system state flag. fires when pressure switch activates by pantograph pressure dropping below defined level @@ -1343,6 +1360,8 @@ public: bool FuelPumpSwitchOff( bool State, range_t const Notify = range_t::consist ); // fuel pump state toggle bool OilPumpSwitch( bool State, range_t const Notify = range_t::consist ); // oil pump state toggle bool OilPumpSwitchOff( bool State, range_t const Notify = range_t::consist ); // oil pump state toggle + 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*/ 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*/ @@ -1353,6 +1372,7 @@ public: void WaterHeaterCheck( double const Timestep ); void FuelPumpCheck( double const Timestep ); void OilPumpCheck( double const Timestep ); + void MotorBlowersCheck( double const Timestep ); bool FuseOn(void); //bezpiecznik nadamiary bool FuseFlagCheck(void) const; // sprawdzanie flagi nadmiarowego void FuseOff(void); // wylaczenie nadmiarowego diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index b57c961f..617568a7 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1436,6 +1436,8 @@ void TMoverParameters::compute_movement_( double const Deltatime ) { SetFlag( SoundFlag, sound::relay ); } } + // traction motors + MotorBlowersCheck( Deltatime ); // uklady hamulcowe: ConverterCheck( Deltatime ); if (VeselVolume > 0) @@ -1532,6 +1534,11 @@ void TMoverParameters::WaterPumpCheck( double const Timestep ) { // water heater status check void TMoverParameters::WaterHeaterCheck( double const Timestep ) { + WaterHeater.is_damaged = ( + ( true == WaterHeater.is_damaged ) + || ( ( true == WaterHeater.is_active ) + && ( false == WaterPump.is_active ) ) ); + WaterHeater.is_active = ( ( false == WaterHeater.is_damaged ) && ( true == Battery ) @@ -1543,11 +1550,6 @@ void TMoverParameters::WaterHeaterCheck( double const Timestep ) { && ( dizel_heat.temperatura1 > WaterHeater.config.temp_max ) ) { WaterHeater.is_active = false; } - - WaterHeater.is_damaged = ( - ( true == WaterHeater.is_damaged ) - || ( ( true == WaterHeater.is_active ) - && ( false == WaterPump.is_active ) ) ); } // fuel pump status update @@ -1590,20 +1592,57 @@ void TMoverParameters::OilPumpCheck( double const Timestep ) { true == OilPump.is_active ? std::min( minpressure + 0.1f, OilPump.pressure_maximum ) : // slight pressure margin to give time to switch off the pump and start the engine 0.f ); - if( OilPump.pressure_present < OilPump.pressure_target ) { + if( OilPump.pressure < OilPump.pressure_target ) { // TODO: scale change rate from 0.01-0.05 with oil/engine temperature/idle time - OilPump.pressure_present = + OilPump.pressure = std::min( OilPump.pressure_target, - OilPump.pressure_present + ( enrot > 5.0 ? 0.05 : 0.035 ) * Timestep ); + OilPump.pressure + ( enrot > 5.0 ? 0.05 : 0.035 ) * Timestep ); } - if( OilPump.pressure_present > OilPump.pressure_target ) { - OilPump.pressure_present = + if( OilPump.pressure > OilPump.pressure_target ) { + OilPump.pressure = std::max( OilPump.pressure_target, - OilPump.pressure_present - 0.01 * Timestep ); + OilPump.pressure - 0.01 * Timestep ); + } + OilPump.pressure = clamp( OilPump.pressure, 0.f, 1.5f ); +} + +void TMoverParameters::MotorBlowersCheck( double const Timestep ) { + // activation check + for( auto &blower : MotorBlowers ) { + + blower.is_active = ( + // TODO: bind properly power source when ld is in place + ( blower.start_type == start_t::battery ? Battery : + blower.start_type == start_t::converter ? ConverterFlag : + Mains ) // power source + // breaker condition disabled until it's implemented in the class data +// && ( true == blower.breaker ) + && ( false == blower.is_disabled ) + && ( ( true == blower.is_active ) + || ( blower.start_type == start_t::manual ? blower.is_enabled : true ) ) ); + } + // update + for( auto &fan : MotorBlowers ) { + + auto const revolutionstarget { ( + fan.is_active ? + ( fan.speed > 0.f ? fan.speed * static_cast( enrot ) * 60 : fan.speed * -1 ) : + 0.f ) }; + + if( std::abs( fan.revolutions - revolutionstarget ) < 0.01f ) { + fan.revolutions = revolutionstarget; + continue; + } + if( revolutionstarget > 0.f ) { + auto const speedincreasecap { std::max( 50.f, fan.speed * 0.05f * -1 ) }; // 5% of fixed revolution speed, or 50 + fan.revolutions += clamp( revolutionstarget - fan.revolutions, speedincreasecap * -2, speedincreasecap ) * Timestep; + } + else { + fan.revolutions *= std::max( 0.0, 1.0 - Timestep ); + } } - OilPump.pressure_present = clamp( OilPump.pressure_present, 0.f, 1.5f ); } @@ -2606,6 +2645,60 @@ bool TMoverParameters::OilPumpSwitchOff( bool State, range_t const Notify ) { return ( OilPump.is_disabled != initialstate ); } +bool TMoverParameters::MotorBlowersSwitch( bool State, side const Side, range_t const Notify ) { + + auto &fan { MotorBlowers[ Side ] }; + + if( ( fan.start_type != start_t::manual ) + && ( fan.start_type != start_t::manualwithautofallback ) ) { + // automatic device ignores 'manual' state commands + return false; + } + + bool const initialstate { fan.is_enabled }; + + fan.is_enabled = State; + + if( Notify != range_t::local ) { + SendCtrlToNext( + ( Side == side::front ? "MotorBlowersFrontSwitch" : "MotorBlowersRearSwitch" ), + ( fan.is_enabled ? 1 : 0 ), + CabNo, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); + } + + return ( fan.is_enabled != initialstate ); +} + +bool TMoverParameters::MotorBlowersSwitchOff( bool State, side const Side, range_t const Notify ) { + + auto &fan { MotorBlowers[ Side ] }; + + if( ( fan.start_type != start_t::manual ) + && ( fan.start_type != start_t::manualwithautofallback ) ) { + // automatic device ignores 'manual' state commands + return false; + } + + bool const initialstate { fan.is_disabled }; + + fan.is_disabled = State; + + if( Notify != range_t::local ) { + SendCtrlToNext( + ( Side == side::front ? "MotorBlowersFrontSwitchOff" : "MotorBlowersRearSwitchOff" ), + ( fan.is_disabled ? 1 : 0 ), + CabNo, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); + } + + return ( fan.is_disabled != initialstate ); +} + // ************************************************************************************************* // Q: 20160713 // włączenie / wyłączenie obwodu głownego @@ -6020,7 +6113,7 @@ bool TMoverParameters::dizel_StartupCheck() { } // test the oil pump if( ( false == OilPump.is_active ) - || ( OilPump.pressure_present < OilPump.pressure_minimum ) ) { + || ( OilPump.pressure < OilPump.pressure_minimum ) ) { engineisready = false; if( OilPump.start_type == start_t::manual ) { // with manual pump control startup procedure is done only once per starter switch press @@ -6545,15 +6638,14 @@ TMoverParameters::AssignLoad( std::string const &Name, float const Amount ) { } } - // can't mix load types, at least for the time being - if( ( LoadAmount > 0 ) && ( LoadType.name != Name ) ) { return false; } - if( Name.empty() ) { - // empty the vehicle + // empty the vehicle if requested LoadType = load_attributes(); LoadAmount = 0.f; return true; } + // can't mix load types, at least for the time being + if( ( LoadAmount > 0 ) && ( LoadType.name != Name ) ) { return false; } for( auto const &loadattributes : LoadAttributes ) { if( Name == loadattributes.name ) { @@ -8211,59 +8303,54 @@ void TMoverParameters::LoadFIZ_Cntrl( std::string const &line ) { } extract_value( ConverterStartDelay, "ConverterStartDelay", line, "" ); + // devices + std::map starts { + { "Manual", start_t::manual }, + { "Automatic", start_t::automatic }, + { "Mixed", start_t::manualwithautofallback }, + { "Battery", start_t::battery }, + { "Converter", start_t::converter } }; // compressor { - std::map starts { - { "Manual", start_t::manual }, - { "Automatic", start_t::automatic } - }; auto lookup = starts.find( extract_value( "CompressorStart", line ) ); CompressorStart = lookup != starts.end() ? lookup->second : start_t::manual; } - // fuel pump { - std::map starts { - { "Manual", start_t::manual }, - { "Automatic", start_t::automatic }, - { "Mixed", start_t::manualwithautofallback } - }; auto lookup = starts.find( extract_value( "FuelStart", line ) ); FuelPump.start_type = lookup != starts.end() ? lookup->second : start_t::manual; } - // oil pump { - std::map starts { - { "Manual", start_t::manual }, - { "Automatic", start_t::automatic }, - { "Mixed", start_t::manualwithautofallback } - }; auto lookup = starts.find( extract_value( "OilStart", line ) ); OilPump.start_type = lookup != starts.end() ? lookup->second : start_t::manual; } - // water pump { - std::map starts { - { "Manual", start_t::manual }, - { "Battery", start_t::battery } - }; auto lookup = starts.find( extract_value( "WaterStart", line ) ); WaterPump.start_type = lookup != starts.end() ? lookup->second : start_t::manual; } + // traction motor fans + { + auto lookup = starts.find( extract_value( "MotorBlowersStart", line ) ); + MotorBlowers[side::front].start_type = + MotorBlowers[side::rear].start_type = + lookup != starts.end() ? + lookup->second : + start_t::manual; + } } void TMoverParameters::LoadFIZ_Blending(std::string const &line) { @@ -8516,6 +8603,10 @@ void TMoverParameters::LoadFIZ_Engine( std::string const &Input ) { extract_value( WaterHeater.config.temp_min, "HeaterMinTemperature", Input, "" ); extract_value( WaterHeater.config.temp_max, "HeaterMaxTemperature", Input, "" ); } + + // traction motors + extract_value( MotorBlowers[ side::front ].speed, "MotorBlowersSpeed", Input, "" ); + MotorBlowers[ side::rear ] = MotorBlowers[ side::front ]; } void TMoverParameters::LoadFIZ_Switches( std::string const &Input ) { @@ -9292,7 +9383,39 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C } OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } - else if (Command == "MainSwitch") + else if( Command == "MotorBlowersFrontSwitch" ) { + if( ( MotorBlowers[ side::front ].start_type != start_t::manual ) + && ( MotorBlowers[ side::front ].start_type != start_t::manualwithautofallback ) ) { + // automatic device ignores 'manual' state commands + MotorBlowers[side::front].is_enabled = ( CValue1 == 1 ); + } + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } + else if( Command == "MotorBlowersFrontSwitchOff" ) { + if( ( MotorBlowers[ side::front ].start_type != start_t::manual ) + && ( MotorBlowers[ side::front ].start_type != start_t::manualwithautofallback ) ) { + // automatic device ignores 'manual' state commands + MotorBlowers[side::front].is_disabled = ( CValue1 == 1 ); + } + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } + else if( Command == "MotorBlowersRearSwitch" ) { + if( ( MotorBlowers[ side::rear ].start_type != start_t::manual ) + && ( MotorBlowers[ side::rear ].start_type != start_t::manualwithautofallback ) ) { + // automatic device ignores 'manual' state commands + MotorBlowers[side::rear].is_enabled = ( CValue1 == 1 ); + } + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } + else if( Command == "MotorBlowersRearSwitchOff" ) { + if( ( MotorBlowers[ side::rear ].start_type != start_t::manual ) + && ( MotorBlowers[ side::rear ].start_type != start_t::manualwithautofallback ) ) { + // automatic device ignores 'manual' state commands + MotorBlowers[side::rear].is_disabled = ( CValue1 == 1 ); + } + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } + else if (Command == "MainSwitch") { if (CValue1 == 1) { diff --git a/Model3d.cpp b/Model3d.cpp index 6c6fcc5c..20cc9bdd 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -72,6 +72,23 @@ void TSubModel::Name(std::string const &Name) pName = Name; }; +// sets visibility level (alpha component) to specified value +void +TSubModel::SetVisibilityLevel( float const Level, bool const Includechildren, bool const Includesiblings ) { + + fVisible = Level; + if( true == Includesiblings ) { + auto sibling { this }; + while( ( sibling = sibling->Next ) != nullptr ) { + sibling->SetVisibilityLevel( Level, Includechildren, false ); // no need for all siblings to duplicate the work + } + } + if( ( true == Includechildren ) + && ( Child != nullptr ) ) { + Child->SetVisibilityLevel( Level, Includechildren, true ); // node's children include child's siblings and children + } +} + // sets light level (alpha component of illumination color) to specified value void TSubModel::SetLightLevel( float const Level, bool const Includechildren, bool const Includesiblings ) { @@ -1340,7 +1357,8 @@ void TSubModel::serialize(std::ostream &s, else sn_utils::ls_int32(s, (int32_t)get_container_pos(textures, m_materialname)); - sn_utils::ls_float32(s, fVisible); +// sn_utils::ls_float32(s, fVisible); + sn_utils::ls_float32(s, 1.f); sn_utils::ls_float32(s, fLight); sn_utils::s_vec4(s, f4Ambient); @@ -1460,7 +1478,8 @@ void TSubModel::deserialize(std::istream &s) tVboPtr = sn_utils::ld_int32(s); iTexture = sn_utils::ld_int32(s); - fVisible = sn_utils::ld_float32(s); +// fVisible = sn_utils::ld_float32(s); + auto discard = sn_utils::ld_float32(s); fLight = sn_utils::ld_float32(s); f4Ambient = sn_utils::d_vec4(s); diff --git a/Model3d.h b/Model3d.h index 535b069e..6f41c506 100644 --- a/Model3d.h +++ b/Model3d.h @@ -100,7 +100,6 @@ private: int iNumVerts { -1 }; // ilość wierzchołków (1 dla FreeSpotLight) int tVboPtr; // początek na liście wierzchołków albo indeksów int iTexture { 0 }; // numer nazwy tekstury, -1 wymienna, 0 brak - float fVisible { 0.0f }; // próg jasności światła do załączenia submodelu float fLight { -1.0f }; // próg jasności światła do zadziałania selfillum glm::vec4 f4Ambient { 1.0f,1.0f,1.0f,1.0f }, @@ -142,7 +141,8 @@ public: // chwilowo float4x4 *mAnimMatrix{ nullptr }; // macierz do animacji kwaternionowych (należy do AnimContainer) TSubModel **smLetter{ nullptr }; // wskaźnik na tablicę submdeli do generoania tekstu (docelowo zapisać do E3D) TSubModel *Parent{ nullptr }; // nadrzędny, np. do wymnażania macierzy - int iVisible{ 1 }; // roboczy stan widoczności + int iVisible { 1 }; // roboczy stan widoczności + float fVisible { 1.f }; // visibility level std::string m_materialname; // robocza nazwa tekstury do zapisania w pliku binarnym std::string pName; // robocza nazwa private: @@ -195,6 +195,8 @@ public: uint32_t Flags() const { return iFlags; }; void UnFlagNext() { iFlags &= 0x00FFFFFF; }; void ColorsSet( glm::vec3 const &Ambient, glm::vec3 const &Diffuse, glm::vec3 const &Specular ); + // 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 ); inline float3 Translation1Get() { diff --git a/Segment.cpp b/Segment.cpp index aaaa5725..5adac9b3 100644 --- a/Segment.cpp +++ b/Segment.cpp @@ -112,9 +112,6 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M ErrorLog( "Bad track: zero length spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); fLength = 0.01; // crude workaround TODO: fix this properly -/* - return false; // zerowe nie mogą być -*/ } fStoop = std::atan2((Point2.y - Point1.y), fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie @@ -126,13 +123,12 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M // NOTE: a workaround for too short switches (less than 3 segments) messing up animation/generation of blades fStep = fLength / ( 3.0 * Global.SplineFidelity ); } - iSegCount = static_cast( std::ceil( fLength / fStep ) ); // potrzebne do VBO -/* +// iSegCount = static_cast( std::ceil( fLength / fStep ) ); // potrzebne do VBO iSegCount = ( pOwner->eType == tt_Switch ? 6 * Global.SplineFidelity : static_cast( std::ceil( fLength / fStep ) ) ); // potrzebne do VBO -*/ + fStep = fLength / iSegCount; // update step to equalize size of individual pieces fTsBuffer.resize( iSegCount + 1 ); @@ -376,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::basic_vertex *ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale, int iSkip, int iEnd, float fOffsetX, glm::vec3 **p, bool bRender) +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, float fOffsetX, glm::vec3 **p, bool bRender) { // generowanie trójkątów dla odcinka trajektorii ruchu // standardowo tworzy triangle_strip dla prostego albo ich zestaw dla łuku // po modyfikacji - dla ujemnego (iNumShapePoints) w dodatkowych polach tabeli @@ -457,7 +453,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori Output.emplace_back( pt, glm::normalize( norm ), - glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } ); + glm::vec2 { 1.f - ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } ); } if( p ) // jeśli jest wskaźnik do tablicy if( *p ) @@ -477,7 +473,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori Output.emplace_back( pt, glm::normalize( norm ), - glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } ); + glm::vec2 { 1.f - ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } ); } if( p ) // jeśli jest wskaźnik do tablicy if( *p ) @@ -501,7 +497,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori Output.emplace_back( pt, glm::normalize( norm ), - glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } ); + glm::vec2 { 1.f - ShapePoints[ j ].texture.x / texturescale, tv1 } ); pt = parallel2 * ShapePoints[ j ].position.x + pos2; pt.y += ShapePoints[ j ].position.y; @@ -512,7 +508,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori Output.emplace_back( pt, glm::normalize( norm ), - glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } ); + glm::vec2 { 1.f - ShapePoints[ j ].texture.x / texturescale, tv2 } ); } } } diff --git a/Segment.h b/Segment.h index a24db593..853fc1c9 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::basic_vertex const *ShapePoints, int iNumShapePoints, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, float fOffsetX = 0.f, glm::vec3 **p = nullptr, bool bRender = true); + 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, float fOffsetX = 0.f, glm::vec3 **p = nullptr, bool bRender = true); /* void Render(); diff --git a/Track.cpp b/Track.cpp index 9c308719..c704dac4 100644 --- a/Track.cpp +++ b/Track.cpp @@ -876,6 +876,13 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) parser->getTokens(); *parser >> fVerticalRadius; } + else if( str == "trackbed" ) { + // switch trackbed texture + auto const trackbedtexture { parser->getToken() }; + if( eType == tt_Switch ) { + SwitchExtension->m_material3 = GfxRenderer.Fetch_Material( trackbedtexture ); + } + } else ErrorLog("Unknown property: \"" + str + "\" in track \"" + m_name + "\""); parser->getTokens(); @@ -1146,261 +1153,25 @@ void TTrack::create_map_geometry(std::vector &Bank) // wypełnianie tablic VBO void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { - // Ra: trzeba rozdzielić szyny od podsypki, aby móc grupować wg tekstur - auto const fHTW = 0.5f * std::abs(fTrackWidth); - auto const side = std::abs(fTexWidth); // szerokść podsypki na zewnątrz szyny albo pobocza - auto const slop = std::abs(fTexSlope); // brzeg zewnętrzny - auto const rozp = fHTW + side + slop; // brzeg zewnętrzny - auto hypot1 = std::hypot(slop, fTexHeight1); // rozmiar pochylenia do liczenia normalnych - if( hypot1 == 0.f ) - hypot1 = 1.f; - glm::vec3 const normalup{ 0.f, 1.f, 0.f }; - glm::vec3 normal1 { fTexHeight1 / hypot1, fTexSlope / hypot1, 0.f }; // wektor normalny - if( glm::length( normal1 ) == 0.f ) { - // fix normal for vertical surfaces - normal1 = glm::vec3 { 1.f, 0.f, 0.f }; - } - glm::vec3 normal2; - float fHTW2, side2, slop2, rozp2, fTexHeight2, hypot2; - if( iTrapezoid & 2 ) { - // ten bit oznacza, że istnieje odpowiednie pNext - // Ra: jest OK - fHTW2 = 0.5f * std::fabs(trNext->fTrackWidth); // połowa rozstawu/nawierzchni - side2 = std::fabs(trNext->fTexWidth); - slop2 = std::fabs(trNext->fTexSlope); // nie jest używane później - rozp2 = fHTW2 + side2 + slop2; - fTexHeight2 = trNext->fTexHeight1; - hypot2 = std::hypot(slop2, fTexHeight2); - if( hypot2 == 0.f ) - hypot2 = 1.f; - normal2 = { fTexHeight2 / hypot2, trNext->fTexSlope / hypot2, 0.f }; - if( glm::length( normal2 ) == 0.f ) { - // fix normal for vertical surfaces - normal2 = glm::vec3 { 1.f, 0.f, 0.f }; - } - } - else { - // gdy nie ma następnego albo jest nieodpowiednim końcem podpięty - fHTW2 = fHTW; - side2 = side; - slop2 = slop; - rozp2 = rozp; - fTexHeight2 = fTexHeight1; - hypot2 = hypot1; - normal2 = normal1; - } - float roll1, roll2; switch (iCategoryFlag & 15) { case 1: // tor { - if (Segment) - Segment->GetRolls(roll1, roll2); - else - roll1 = roll2 = 0.0; // dla zwrotnic - float const - sin1 = std::sin(roll1), - cos1 = std::cos(roll1), - sin2 = std::sin(roll2), - cos2 = std::cos(roll2); // zwykla szyna: //Ra: czemu główki są asymetryczne na wysokości 0.140? - gfx::basic_vertex rpts1[24], rpts2[24], rpts3[24], rpts4[24]; - for( int i = 0; i < 12; ++i ) { - - rpts1[ i ] = { - // position - {( fHTW + szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1, - -( fHTW + szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1, - szyna[ i ].position.z}, - // normal - { szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1, - -szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1, - szyna[ i ].normal.z }, - // texture - { szyna[ i ].texture.x, - szyna[ i ].texture.y } }; - - rpts2[ 11 - i ] = { - // position - {(-fHTW - szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1, - -(-fHTW - szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1, - szyna[ i ].position.z}, - // normal - {-szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1, - szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1, - szyna[ i ].normal.z }, - // texture - { szyna[ i ].texture.x, - szyna[ i ].texture.y } }; - - if( iTrapezoid == 0 ) { continue; } - // trapez albo przechyłki, to oddzielne punkty na końcu - - rpts1[ 12 + i ] = { - // position - {( fHTW + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, - -( fHTW + szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2, - szyna[ i ].position.z}, - // normal - { szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2, - -szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2, - szyna[ i ].normal.z }, - // texture - { szyna[ i ].texture.x, - szyna[ i ].texture.y } }; - - rpts2[ 23 - i ] = { - // position - {(-fHTW - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, - -(-fHTW - szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2, - szyna[ i ].position.z}, - // normal - {-szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2, - szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2, - szyna[ i ].normal.z }, - // texture - { szyna[ i ].texture.x, - szyna[ i ].texture.y } }; - } + gfx::vertex_array rpts1, rpts2; + create_track_rail_profile( rpts1, rpts2 ); switch (eType) // dalej zależnie od typu { case tt_Table: // obrotnica jak zwykły tor, tylko animacja dochodzi case tt_Normal: if (m_material2) { // podsypka z podkładami jest tylko dla zwykłego toru - // potentially retrieve texture length override from the assigned material + gfx::vertex_array bpts1; + create_track_bed_profile( bpts1, trPrev, trNext ); auto const texturelength { texture_length( m_material2 ) }; - gfx::basic_vertex bpts1[ 8 ]; // punkty głównej płaszczyzny nie przydają się do robienia boków - if( texturelength == 4.f ) { - // stare mapowanie z różną gęstością pikseli i oddzielnymi teksturami na każdy profil - auto const normalx = std::cos( glm::radians( 75.f ) ); - auto const normaly = std::sin( glm::radians( 75.f ) ); - if( iTrapezoid ) { - // trapez albo przechyłki - // ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671 ((0.3464,0.6536) - // bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu - bpts1[ 0 ] = { - {rozp, -fTexHeight1 - 0.18f, 0.f}, - {normalx, normaly, 0.f}, - {0.00f, 0.f} }; // lewy brzeg - bpts1[ 1 ] = { - {( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - 0.18f, 0.f}, - {normalx, normaly, 0.f}, - {0.33f, 0.f} }; // krawędź załamania - bpts1[ 2 ] = { - {-bpts1[ 1 ].position.x, +( fHTW + side ) * sin1 - 0.18f, 0.f}, - {-normalx, normaly, 0.f}, - {0.67f, 0.f} }; // prawy brzeg początku symetrycznie - bpts1[ 3 ] = { - {-rozp, -fTexHeight1 - 0.18f, 0.f}, - {-normalx, normaly, 0.f}, - {1.f, 0.f} }; // prawy skos - // końcowy przekrój - bpts1[ 4 ] = { - {rozp2, -fTexHeight2 - 0.18f, 0.f}, - {normalx, normaly, 0.f}, - {0.00f, 0.f} }; // lewy brzeg - bpts1[ 5 ] = { - {( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, - {normalx, normaly, 0.f}, - {0.33f, 0.f} }; // krawędź załamania - bpts1[ 6 ] = { - {-bpts1[ 5 ].position.x, +( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, - {-normalx, normaly, 0.f}, - {0.67f, 0.f} }; // prawy brzeg początku symetrycznie - bpts1[ 7 ] = { - {-rozp2, -fTexHeight2 - 0.18f, 0.f}, - {-normalx, normaly, 0.f}, - {1.00f, 0.f} }; // prawy skos - } - else { - bpts1[ 0 ] = { - {rozp, -fTexHeight1 - 0.18f, 0.f}, - {normalx, normaly, 0.f}, - {0.00f, 0.f} }; // lewy brzeg - bpts1[ 1 ] = { - {fHTW + side, -0.18f, 0.f}, - {normalx, normaly, 0.f}, - {0.33f, 0.f} }; // krawędź załamania - bpts1[ 2 ] = { - {-fHTW - side, -0.18f, 0.f}, - {-normalx, normaly, 0.f}, - {0.67f, 0.f} }; // druga - bpts1[ 3 ] = { - {-rozp, -fTexHeight1 - 0.18f, 0.f}, - {-normalx, normaly, 0.f}, - {1.00f, 0.f} }; // prawy skos - } - } - else { - // mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa fTexLength - auto const max = fTexRatio2 * texturelength; // szerokość proporcjonalna do długości - auto const map11 = max > 0.f ? (fHTW + side) / max : 0.25f; // załamanie od strony 1 - auto const map12 = max > 0.f ? (fHTW + side + hypot1) / max : 0.5f; // brzeg od strony 1 - if (iTrapezoid) { - // trapez albo przechyłki - auto const map21 = max > 0.f ? (fHTW2 + side2) / max : 0.25f; // załamanie od strony 2 - auto const map22 = max > 0.f ? (fHTW2 + side2 + hypot2) / max : 0.5f; // brzeg od strony 2 - // ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671 - // ((0.3464,0.6536) - // bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu - bpts1[ 0 ] = { - {rozp, -fTexHeight1 - 0.18f, 0.f}, - {normal1.x, normal1.y, 0.f}, - {0.5f - map12, 0.f} }; // lewy brzeg - bpts1[ 1 ] = { - {( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - 0.18f, 0.f}, - {normal1.x, normal1.y, 0.f}, - {0.5f - map11 , 0.f} }; // krawędź załamania - bpts1[ 2 ] = { - {-bpts1[ 1 ].position.x, +( fHTW + side ) * sin1 - 0.18f, 0.f}, - {-normal1.x, normal1.y, 0.f}, - {0.5 + map11, 0.f} }; // prawy brzeg początku symetrycznie - bpts1[ 3 ] = { - {-rozp, -fTexHeight1 - 0.18f, 0.f}, - {-normal1.x, normal1.y, 0.f}, - {0.5f + map12, 0.f} }; // prawy skos - // przekrój końcowy - bpts1[ 4 ] = { - {rozp2, -fTexHeight2 - 0.18f, 0.f}, - {normal2.x, normal2.y, 0.f}, - {0.5f - map22, 0.f} }; // lewy brzeg - bpts1[ 5 ] = { - {( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, - {normal2.x, normal2.y, 0.f}, - {0.5f - map21 , 0.f} }; // krawędź załamania - bpts1[ 6 ] = { - {-bpts1[ 5 ].position.x, +( fHTW2 + side2 ) * sin2 - 0.18f, 0.f}, - {-normal2.x, normal2.y, 0.f}, - {0.5f + map21, 0.f} }; // prawy brzeg początku symetrycznie - bpts1[ 7 ] = { - {-rozp2, -fTexHeight2 - 0.18f, 0.f}, - {-normal2.x, normal2.y, 0.f}, - {0.5f + map22, 0.f} }; // prawy skos - } - else - { - bpts1[ 0 ] = { - {rozp, -fTexHeight1 - 0.18f, 0.f}, - {+normal1.x, normal1.y, 0.f}, - {0.5f - map12, 0.f} }; // lewy brzeg - bpts1[ 1 ] = { - {fHTW + side, - 0.18f, 0.f}, - {+normal1.x, normal1.y, 0.f}, - {0.5f - map11, 0.f} }; // krawędź załamania - bpts1[ 2 ] = { - {-fHTW - side, - 0.18f, 0.f}, - {-normal1.x, normal1.y, 0.f}, - {0.5f + map11, 0.f} }; // druga - bpts1[ 3 ] = { - {-rozp, -fTexHeight1 - 0.18f, 0.f}, - {-normal1.x, normal1.y, 0.f}, - {0.5f + map12, 0.f} }; // prawy skos - } - } gfx::vertex_array vertices; - Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -4 : 4, texturelength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -5 : 5, texturelength); if( ( Bank != 0 ) && ( true == Geometry2.empty() ) ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } @@ -1433,37 +1204,8 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { case tt_Switch: // dla zwrotnicy dwa razy szyny if( m_material1 || m_material2 ) { // iglice liczone tylko dla zwrotnic - gfx::basic_vertex - rpts3[24], - rpts4[24]; - glm::vec3 const flipxvalue { -1, 1, 1 }; - for( int i = 0; i < 12; ++i ) { - - rpts3[ i ] = { - {+( fHTW + iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, - -( fHTW + iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, - 0.f}, - {iglica[ i ].normal}, - {iglica[ i ].texture.x, 0.f} }; - rpts3[ i + 12 ] = { - {+( fHTW2 + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, - -( fHTW2 + szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, - 0.f}, - {szyna[ i ].normal}, - {szyna[ i ].texture.x, 0.f} }; - rpts4[ 11 - i ] = { - { ( -fHTW - iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, - -( -fHTW - iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, - 0.f}, - {iglica[ i ].normal * flipxvalue}, - {iglica[ i ].texture.x, 0.f} }; - rpts4[ 23 - i ] = { - { ( -fHTW2 - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, - -( -fHTW2 - szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, - 0.f}, - {szyna[ i ].normal * flipxvalue}, - {szyna[ i ].texture.x, 0.f} }; - } + 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 ) ) }; if (SwitchExtension->RightSwitch) @@ -1531,6 +1273,14 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } } } + // auto-generated switch trackbed + if( true == Global.CreateSwitchTrackbeds ) { + gfx::vertex_array vertices; + create_switch_trackbed( vertices ); + SwitchExtension->Geometry3 = GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ); + vertices.clear(); + } + break; } } // koniec obsługi torów @@ -1540,44 +1290,10 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { { case tt_Normal: // drogi proste, bo skrzyżowania osobno { - gfx::basic_vertex bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków + gfx::vertex_array bpts1; // punkty głównej płaszczyzny przydają się do robienia boków if (m_material1 || m_material2) { // punkty się przydadzą, nawet jeśli nawierzchni nie ma - auto const texturelength { texture_length( m_material1 ) }; - auto const max = fTexRatio1 * texturelength; // test: szerokość proporcjonalna do długości - auto const map1 = max > 0.f ? fHTW / max : 0.5f; // obcięcie tekstury od strony 1 - auto const map2 = max > 0.f ? fHTW2 / max : 0.5f; // obcięcie tekstury od strony 2 - if (iTrapezoid) { - // trapez albo przechyłki - Segment->GetRolls(roll1, roll2); - bpts1[ 0 ] = { - {fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f}, - normalup, - {0.5f - map1, 0.f} }; // lewy brzeg początku - bpts1[ 1 ] = { - {-bpts1[ 0 ].position.x, -bpts1[ 0 ].position.y, 0.f}, - normalup, - {0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie - bpts1[ 2 ] = { - {fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f}, - normalup, - {0.5f - map2, 0.f} }; // lewy brzeg końca - bpts1[ 3 ] = { - {-bpts1[ 2 ].position.x, -bpts1[ 2 ].position.y, 0.f}, - normalup, - {0.5f + map2, 0.f} }; // prawy brzeg początku symetrycznie - } - else - { - bpts1[ 0 ] = { - {fHTW, 0.f, 0.f}, - normalup, - {0.5f - map1, 0.f} }; - bpts1[ 1 ] = { - {-fHTW, 0.f, 0.f}, - normalup, - {0.5f + map1, 0.f} }; - } + create_road_profile( bpts1 ); } if (m_material1) // jeśli podana była tekstura, generujemy trójkąty { // tworzenie trójkątów nawierzchni szosy @@ -1588,161 +1304,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } if (m_material2) { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?) + auto const side{ std::abs( fTexWidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza + auto const slop{ std::abs( fTexSlope ) }; // brzeg zewnętrzny auto const texturelength { texture_length( m_material2 ) }; - gfx::basic_vertex - rpts1[6], - rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony - if (fTexHeight1 >= 0.f) - { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo - rpts1[ 0 ] = { - {rozp, -fTexHeight1, 0.f}, - { 1.f, 0.f, 0.f }, - {0.f, 0.f} }; // lewy brzeg podstawy - rpts1[ 1 ] = { - {bpts1[ 0 ].position.x + side, bpts1[ 0 ].position.y, 0.f}, - normalup, - {0.5, 0.f} }; // lewa krawędź załamania - rpts1[ 2 ] = { - {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne - rpts2[ 0 ] = { - {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; // prawy brzeg pobocza - rpts2[ 1 ] = { - {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; // prawa krawędź załamania - rpts2[ 2 ] = { - {-rozp, -fTexHeight1, 0.f}, - { -1.f, 0.f, 0.f }, - {0.f, 0.f} }; // prawy brzeg podstawy - if (iTrapezoid) { - // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - rpts1[ 3 ] = { - {rozp2, -fTexHeight2, 0.f}, - { 1.f, 0.f, 0.f }, - {0.f, 0.f} }; // lewy brzeg lewego pobocza - rpts1[ 4 ] = { - {bpts1[ 2 ].position.x + side2, bpts1[ 2 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; // krawędź załamania - rpts1[ 5 ] = { - {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; // brzeg pobocza - rpts2[ 3 ] = { - {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; - rpts2[ 4 ] = { - {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; - rpts2[ 5 ] = { - {-rozp2, -fTexHeight2, 0.f}, - { -1.f, 0.f, 0.f }, - {0.f, 0.f} }; // prawy brzeg prawego pobocza - } - } - else - { // wersja dla chodnika: skos 1:3.75, każdy chodnik innej szerokości - // mapowanie propocjonalne do szerokości chodnika - // krawężnik jest mapowany od 31/64 do 32/64 lewy i od 32/64 do 33/64 prawy - auto const d = -fTexHeight1 / 3.75f; // krawężnik o wysokości 150mm jest pochylony 40mm - auto const max = fTexRatio2 * texturelength; // test: szerokość proporcjonalna do długości - auto const map1l = ( - max > 0.f ? - side / max : - 0.484375f ); // obcięcie tekstury od lewej strony punktu 1 - auto const map1r = ( - max > 0.f ? - slop / max : - 0.484375f ); // obcięcie tekstury od prawej strony punktu 1 - auto const h1r = ( - slop > d ? - -fTexHeight1 : - 0.f ); - auto const h1l = ( - side > d ? - -fTexHeight1 : - 0.f ); - - rpts1[ 0 ] = { - {bpts1[ 0 ].position.x + slop, bpts1[ 0 ].position.y + h1r, 0.f}, - normalup, - {0.515625f + map1r, 0.f} }; // prawy brzeg prawego chodnika - rpts1[ 1 ] = { - {bpts1[ 0 ].position.x + d, bpts1[ 0 ].position.y + h1r, 0.f}, - normalup, - {0.515625f, 0.f} }; // prawy krawężnik u góry - rpts1[ 2 ] = { - {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, - { -1.f, 0.f, 0.f }, - {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu - rpts2[ 0 ] = { - {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, - { 1.f, 0.f, 0.f }, - {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu - rpts2[ 1 ] = { - {bpts1[ 1 ].position.x - d, bpts1[ 1 ].position.y + h1l, 0.f}, - normalup, - {0.484375f, 0.f} }; // lewy krawężnik u góry - rpts2[ 2 ] = { - {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y + h1l, 0.f}, - normalup, - {0.484375f - map1l, 0.f} }; // lewy brzeg lewego chodnika - - if (iTrapezoid) { - // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - slop2 = ( - std::fabs((iTrapezoid & 2) ? - slop2 : - slop) ); // szerokość chodnika po prawej - auto const map2l = ( - max > 0.f ? - side2 / max : - 0.484375f ); // obcięcie tekstury od lewej strony punktu 2 - auto const map2r = ( - max > 0.f ? - slop2 / max : - 0.484375f ); // obcięcie tekstury od prawej strony punktu 2 - auto const h2r = ( - slop2 > d ? - -fTexHeight2 : - 0.f ); - auto const h2l = ( - side2 > d ? - -fTexHeight2 : - 0.f ); - - rpts1[ 3 ] = { - {bpts1[ 2 ].position.x + slop2, bpts1[ 2 ].position.y + h2r, 0.f}, - normalup, - {0.515625f + map2r, 0.f} }; // prawy brzeg prawego chodnika - rpts1[ 4 ] = { - {bpts1[ 2 ].position.x + d, bpts1[ 2 ].position.y + h2r, 0.f}, - normalup, - {0.515625f, 0.f} }; // prawy krawężnik u góry - rpts1[ 5 ] = { - {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, - { -1.f, 0.f, 0.f }, - {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu - rpts2[ 3 ] = { - {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, - { 1.f, 0.f, 0.f }, - {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu - rpts2[ 4 ] = { - {bpts1[ 3 ].position.x - d, bpts1[ 3 ].position.y + h2l, 0.f}, - normalup, - {0.484375f, 0.f} }; // lewy krawężnik u góry - rpts2[ 5 ] = { - {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y + h2l, 0.f}, - normalup, - {0.484375f - map2l, 0.f} }; // lewy brzeg lewego chodnika - } - } + 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 @@ -1822,33 +1388,9 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { SwitchExtension->bPoints ? nullptr : SwitchExtension->vPoints; // zmienna robocza, NULL gdy tablica punktów już jest wypełniona - gfx::basic_vertex bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków - if (m_material1 || m_material2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma - { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100% - auto const texturelength { texture_length( m_material1 ) }; - auto const max = fTexRatio1 * texturelength; // test: szerokość proporcjonalna do długości - auto const map1 = max > 0.f ? fHTW / max : 0.5f; // obcięcie tekstury od strony 1 - auto const map2 = max > 0.f ? fHTW2 / max : 0.5f; // obcięcie tekstury od strony 2 - // if (iTrapezoid) //trapez albo przechyłki - { // nawierzchnia trapezowata - Segment->GetRolls(roll1, roll2); - bpts1[ 0 ] = { - {fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f}, - {std::sin( roll1 ), std::cos( roll1 ), 0.f}, - {0.5f - map1, 0.f} }; // lewy brzeg początku - bpts1[ 1 ] = { - {-bpts1[ 0 ].position.x, -bpts1[ 0 ].position.y, 0.f}, - {-std::sin( roll1 ), std::cos( roll1 ), 0.f}, - {0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie - bpts1[ 2 ] = { - {fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f}, - {std::sin( roll2 ), std::cos( roll2 ), 0.f}, - {0.5f - map2, 0.f} }; // lewy brzeg końca - bpts1[ 3 ] = { - {-bpts1[ 2 ].position.x, -bpts1[ 2 ].position.y, 0.f}, - {-std::sin( roll2 ), std::cos( roll2 ), 0.f}, - {0.5 + map2, 0.f} }; // prawy brzeg początku symetrycznie - } + gfx::vertex_array bpts1; // punkty głównej płaszczyzny przydają się do robienia boków + if (m_material1 || m_material2) { // punkty się przydadzą, nawet jeśli nawierzchni nie ma + create_road_profile( bpts1, true ); } // najpierw renderowanie poboczy i zapamiętywanie punktów // problem ze skrzyżowaniami jest taki, że teren chce się pogrupować wg tekstur, ale zaczyna od nawierzchni @@ -1856,146 +1398,12 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { // ale pobocza renderują się później, więc nawierzchnia nie załapuje się na renderowanie w swoim czasie if( m_material2 ) { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?) - auto const texturelength { texture_length( m_material2 ) }; - gfx::basic_vertex - rpts1[6], - rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony + gfx::vertex_array rpts1, rpts2; // współrzędne przekroju i mapowania dla prawej i lewej strony + create_road_side_profile( rpts1, rpts2, bpts1, true ); // Ra 2014-07: trzeba to przerobić na pętlę i pobierać profile (przynajmniej 2..4) z sąsiednich dróg - if (fTexHeight1 >= 0.0) - { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo - rpts1[ 0 ] = { - {rozp, -fTexHeight1, 0.f}, - { 1.f, 0.f, 0.f }, - {0.f, 0.f} }; // lewy brzeg podstawy - rpts1[ 1 ] = { - {bpts1[ 0 ].position.x + side, bpts1[ 0 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} };// lewa krawędź załamania - rpts1[ 2 ] = { - {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne - rpts2[ 0 ] = { - {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; // prawy brzeg pobocza - rpts2[ 1 ] = { - {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; // prawa krawędź załamania - rpts2[ 2 ] = { - {-rozp, -fTexHeight1, 0.f}, - { -1.f, 0.f, 0.f }, - {0.f, 0.f} }; // prawy brzeg podstawy - // if (iTrapezoid) //trapez albo przechyłki - { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - rpts1[ 3 ] = { - {rozp2, -fTexHeight2, 0.f}, - { 1.f, 0.f, 0.f }, - {0.f, 0.f} }; // lewy brzeg lewego pobocza - rpts1[ 4 ] = { - {bpts1[ 2 ].position.x + side2, bpts1[ 2 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; // krawędź załamania - rpts1[ 5 ] = { - {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; // brzeg pobocza - rpts2[ 3 ] = { - {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, - normalup, - {1.f, 0.f} }; - rpts2[ 4 ] = { - {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; - rpts2[ 5 ] = { - {-rozp2, -fTexHeight2, 0.f}, - { -1.f, 0.f, 0.f }, - {0.f, 0.f} }; // prawy brzeg prawego pobocza - } - } - else - { // wersja dla chodnika: skos 1:3.75, każdy chodnik innej szerokości - // mapowanie propocjonalne do szerokości chodnika - // krawężnik jest mapowany od 31/64 do 32/64 lewy i od 32/64 do 33/64 prawy - auto const d = -fTexHeight1 / 3.75f; // krawężnik o wysokości 150mm jest pochylony 40mm - auto const max = fTexRatio2 * texturelength; // test: szerokość proporcjonalna do długości - auto const map1l = ( - max > 0.f ? - side / max : - 0.484375f ); // obcięcie tekstury od lewej strony punktu 1 - auto const map1r = ( - max > 0.f ? - slop / max : - 0.484375f ); // obcięcie tekstury od prawej strony punktu 1 - - rpts1[ 0 ] = { - {bpts1[ 0 ].position.x + slop, bpts1[ 0 ].position.y - fTexHeight1, 0.f}, - normalup, - { 0.515625f + map1r, 0.f} }; // prawy brzeg prawego chodnika - rpts1[ 1 ] = { - {bpts1[ 0 ].position.x + d, bpts1[ 0 ].position.y - fTexHeight1, 0.f}, - normalup, - {0.515625f, 0.f} }; // prawy krawężnik u góry - rpts1[ 2 ] = { - {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, - { -1.f, 0.f, 0.f }, - {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu - rpts2[ 0 ] = { - {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, - { 1.f, 0.f, 0.f }, - {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu - rpts2[ 1 ] = { - {bpts1[ 1 ].position.x - d, bpts1[ 1 ].position.y - fTexHeight1, 0.f}, - normalup, - {0.484375f, 0.f} }; // lewy krawężnik u góry - rpts2[ 2 ] = { - {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y - fTexHeight1, 0.f}, - normalup, - {0.484375f - map1l, 0.f} }; // lewy brzeg lewego chodnika - // if (iTrapezoid) //trapez albo przechyłki - { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - slop2 = std::abs( - ( (iTrapezoid & 2) ? - slop2 : - slop ) ); // szerokość chodnika po prawej - auto const map2l = ( - max > 0.f ? - side2 / max : - 0.484375f ); // obcięcie tekstury od lewej strony punktu 2 - auto const map2r = ( - max > 0.f ? - slop2 / max : - 0.484375f ); // obcięcie tekstury od prawej strony punktu 2 - - rpts1[ 3 ] = { - {bpts1[ 2 ].position.x + slop2, bpts1[ 2 ].position.y - fTexHeight2, 0.f}, - normalup, - { 0.515625f + map2r, 0.f} }; // prawy brzeg prawego chodnika - rpts1[ 4 ] = { - {bpts1[ 2 ].position.x + d, bpts1[ 2 ].position.y - fTexHeight2, 0.f}, - normalup, - {0.515625f, 0.f} }; // prawy krawężnik u góry - rpts1[ 5 ] = { - {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, - { -1.f, 0.f, 0.f }, - {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu - rpts2[ 3 ] = { - {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, - { 1.f, 0.f, 0.f }, - {0.484375f + d / 2.56, 0.f} }; // lewy krawężnik u dołu - rpts2[ 4 ] = { - {bpts1[ 3 ].position.x - d, bpts1[ 3 ].position.y - fTexHeight2, 0.f}, - normalup, - {0.484375f, 0.f} }; // lewy krawężnik u góry - rpts2[ 5 ] = { - {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y - fTexHeight2, 0.f}, - normalup, - {0.484375f - map2l, 0.f} }; // lewy brzeg lewego chodnika - } - } bool render = ( m_material2 != 0 ); // renderować nie trzeba, ale trzeba wyznaczyć punkty brzegowe nawierzchni + auto const side{ std::abs( fTexWidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza + auto const texturelength{ texture_length( m_material2 ) }; gfx::vertex_array vertices; if (SwitchExtension->iRoads == 4) { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka @@ -2103,53 +1511,9 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { { case tt_Normal: // drogi proste, bo skrzyżowania osobno { - gfx::basic_vertex bpts1[4]; // punkty głównej płaszczyzny przydają się do robienia boków - if (m_material1 || m_material2) // punkty się przydadzą, nawet jeśli nawierzchni nie ma - { // double max=2.0*(fHTW>fHTW2?fHTW:fHTW2); //z szerszej strony jest 100% - auto const max = ( - ( iCategoryFlag & 4 ) ? - 0.f : - fTexLength ); // test: szerokość dróg proporcjonalna do długości - auto const map1 = ( - max > 0.f ? - fHTW / max : - 0.5f ); // obcięcie tekstury od strony 1 - auto const map2 = ( - max > 0.f ? - fHTW2 / max : - 0.5f ); // obcięcie tekstury od strony 2 - - if (iTrapezoid) { - // nawierzchnia trapezowata - Segment->GetRolls(roll1, roll2); - bpts1[ 0 ] = { - {fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f}, - normalup, - {0.5f - map1, 0.f} }; // lewy brzeg początku - bpts1[ 1 ] = { - {-bpts1[ 0 ].position.x, -bpts1[ 0 ].position.y, 0.f}, - normalup, - {0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie - bpts1[ 2 ] = { - {fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f}, - normalup, - {0.5f - map2, 0.f} }; // lewy brzeg końca - bpts1[ 3 ] = { - {-bpts1[ 2 ].position.x, -bpts1[ 2 ].position.y, 0.f}, - normalup, - {0.5f + map2, 0.f} }; // prawy brzeg początku symetrycznie - } - else - { - bpts1[ 0 ] = { - {fHTW, 0.f, 0.f}, - normalup, - {0.5 - map1, 0.f} }; // zawsze standardowe mapowanie - bpts1[ 1 ] = { - {-fHTW, 0.f, 0.f}, - normalup, - {0.5f + map1, 0.f} }; - } + gfx::vertex_array bpts1; // punkty głównej płaszczyzny przydają się do robienia boków + if (m_material1 || m_material2) { // punkty się przydadzą, nawet jeśli nawierzchni nie ma + create_road_profile( bpts1 ); } if (m_material1) // jeśli podana była tekstura, generujemy trójkąty { // tworzenie trójkątów nawierzchni szosy @@ -2159,61 +1523,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } if (m_material2) { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?) + 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; - gfx::basic_vertex - rpts1[6], - rpts2[6]; // współrzędne przekroju i mapowania dla prawej i lewej strony - - rpts1[ 0 ] = { - {rozp, -fTexHeight1, 0.f}, - normalup, - {0.0f, 0.f} }; // lewy brzeg podstawy - rpts1[ 1 ] = { - {bpts1[ 0 ].position.x + side, bpts1[ 0 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; // lewa krawędź załamania - rpts1[ 2 ] = { - {bpts1[ 0 ].position.x, bpts1[ 0 ].position.y, 0.f}, - normalup, - {1.0f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne - rpts2[ 0 ] = { - {bpts1[ 1 ].position.x, bpts1[ 1 ].position.y, 0.f}, - normalup, - {1.0f, 0.f} }; // prawy brzeg pobocza - rpts2[ 1 ] = { - {bpts1[ 1 ].position.x - side, bpts1[ 1 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; // prawa krawędź załamania - rpts2[ 2 ] = { - {-rozp, -fTexHeight1, 0.f}, - normalup, - {0.0f, 0.f} }; // prawy brzeg podstawy if (iTrapezoid) // trapez albo przechyłki { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka - rpts1[ 3 ] = { - {rozp2, -fTexHeight2, 0.f}, - normalup, - {0.0f, 0.f} }; // lewy brzeg lewego pobocza - rpts1[ 4 ] = { - {bpts1[ 2 ].position.x + side2, bpts1[ 2 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; // krawędź załamania - rpts1[ 5 ] = { - {bpts1[ 2 ].position.x, bpts1[ 2 ].position.y, 0.f}, - normalup, - {1.0f, 0.f} }; // brzeg pobocza - rpts2[ 3 ] = { - {bpts1[ 3 ].position.x, bpts1[ 3 ].position.y, 0.f}, - normalup, - {1.0f, 0.f} }; - rpts2[ 4 ] = { - {bpts1[ 3 ].position.x - side2, bpts1[ 3 ].position.y, 0.f}, - normalup, - {0.5f, 0.f} }; - rpts2[ 5 ] = { - {-rozp2, -fTexHeight2, 0.f}, - normalup, - {0.0f, 0.f} }; // prawy brzeg prawego pobocza Segment->RenderLoft(vertices, m_origin, rpts1, -3, fTexLength); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); @@ -2491,42 +1805,8 @@ TTrack * TTrack::RaAnimate() && ( ( false == Geometry1.empty() ) || ( false == Geometry2.empty() ) ) ) { // iglice liczone tylko dla zwrotnic - auto const fHTW = 0.5f * std::abs( fTrackWidth ); - auto const fHTW2 = fHTW; // Ra: na razie niech tak będzie - auto const cos1 = 1.0f, sin1 = 0.0f, cos2 = 1.0f, sin2 = 0.0f; // Ra: ... - - gfx::basic_vertex - rpts3[ 24 ], - rpts4[ 24 ]; - glm::vec3 const flipxvalue { -1, 1, 1 }; - for (int i = 0; i < 12; ++i) { - - rpts3[ i ] = { - {+( fHTW + iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, - -( fHTW + iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, - 0.f}, - {iglica[ i ].normal}, - {iglica[ i ].texture.x, 0.f} }; - rpts3[ i + 12 ] = { - {+( fHTW2 + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, - -( fHTW2 + szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, - 0.f}, - {szyna[ i ].normal}, - {szyna[ i ].texture.x, 0.f} }; - rpts4[ 11 - i ] = { - {+( -fHTW - iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, - -( -fHTW - iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, - 0.f}, - {iglica[ i ].normal * flipxvalue}, - {iglica[ i ].texture.x, 0.f} }; - rpts4[ 23 - i ] = { - { ( -fHTW2 - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, - -( -fHTW2 - szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, - 0.f}, - {szyna[ i ].normal * flipxvalue}, - {szyna[ i ].texture.x, 0.f} }; - } - + gfx::vertex_array rpts3, rpts4; + create_track_blade_profile( rpts3, rpts4 ); gfx::vertex_array vertices; auto const bladelength { static_cast( std::ceil( SwitchExtension->Segments[ 0 ]->RaSegCount() * 0.65 ) ) }; if (SwitchExtension->RightSwitch) @@ -2959,6 +2239,22 @@ void TTrack::ConnectionsLog() } }; +bool +TTrack::DoubleSlip() const { + + // crude way to discern part of double slip switch: + // a switch with name ending in _a or _b or _c or _d + return ( + ( iCategoryFlag == 1 ) + && ( eType == tt_Switch ) + && ( m_name.size() > 2 ) + && ( m_name.back() >= 'a' ) + && ( m_name.back() <= 'd' ) + && ( ( m_name[ m_name.size() - 2 ] == '_' ) + || ( m_name.rfind( '_' ) != std::string::npos ) ) ); +} + + TTrack * TTrack::Connected(int s, double &d) const { // zwraca wskaźnik na sąsiedni tor, w kierunku określonym znakiem (s), odwraca (d) w razie // niezgodności kierunku torów @@ -3009,6 +2305,754 @@ TTrack * TTrack::Connected(int s, double &d) const return NULL; }; +// creates rail profile data for current track +void +TTrack::create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ) { + + auto const fHTW { 0.5f * std::abs( fTrackWidth ) }; + + float + roll1{ 0.f }, + roll2{ 0.f }; + + if( Segment ) { + Segment->GetRolls( roll1, roll2 ); + } + + float const + sin1 { std::sin( roll1 ) }, + cos1 { std::cos( roll1 ) }, + sin2 { std::sin( roll2 ) }, + cos2 { std::cos( roll2 ) }; + + auto const pointcount { iTrapezoid == 0 ? 12 : 24 }; + Right.resize( pointcount ); + Left.resize( pointcount ); + + for( int i = 0; i < 12; ++i ) { + + Right[ i ] = { + // position + {( fHTW + szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1, + -( fHTW + szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1, + szyna[ i ].position.z}, + // normal + { szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1, + -szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; + + Left[ 11 - i ] = { + // position + {(-fHTW - szyna[ i ].position.x ) * cos1 + szyna[ i ].position.y * sin1, + -(-fHTW - szyna[ i ].position.x ) * sin1 + szyna[ i ].position.y * cos1, + szyna[ i ].position.z}, + // normal + {-szyna[ i ].normal.x * cos1 + szyna[ i ].normal.y * sin1, + szyna[ i ].normal.x * sin1 + szyna[ i ].normal.y * cos1, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; + + if( iTrapezoid == 0 ) { continue; } + // trapez albo przechyłki, to oddzielne punkty na końcu + + Right[ 12 + i ] = { + // position + {( fHTW + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( fHTW + szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2, + szyna[ i ].position.z}, + // normal + { szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2, + -szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; + + Left[ 23 - i ] = { + // position + {(-fHTW - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -(-fHTW - szyna[ i ].position.x ) * sin2 + szyna[ i ].position.y * cos2, + szyna[ i ].position.z}, + // normal + {-szyna[ i ].normal.x * cos2 + szyna[ i ].normal.y * sin2, + szyna[ i ].normal.x * sin2 + szyna[ i ].normal.y * cos2, + szyna[ i ].normal.z }, + // texture + { szyna[ i ].texture.x, + szyna[ i ].texture.y } }; + } +} + +// creates switch blades profile data for current track +void +TTrack::create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ) { + + auto const fHTW { 0.5f * std::abs( fTrackWidth ) }; + float const fHTW2 { ( + ( iTrapezoid & 2 ) != 0 ? // ten bit oznacza, że istnieje odpowiednie pNext + 0.5f * std::fabs( trNext->fTrackWidth ) : // połowa rozstawu/nawierzchni + fHTW ) }; + + float + roll1 { 0.f }, + roll2 { 0.f }; + + if( Segment ) { + Segment->GetRolls( roll1, roll2 ); + } + + float const + sin1 { std::sin( roll1 ) }, + cos1 { std::cos( roll1 ) }, + sin2 { std::sin( roll2 ) }, + cos2 { std::cos( roll2 ) }; + + auto const pointcount { 24 }; + Right.resize( pointcount ); + Left.resize( pointcount ); + + glm::vec3 const flipxvalue { -1, 1, 1 }; + for( int i = 0; i < 12; ++i ) { + + Right[ i ] = { + {+( fHTW + iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, + -( fHTW + iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, + 0.f}, + {iglica[ i ].normal}, + {iglica[ i ].texture.x, 0.f} }; + Right[ i + 12 ] = { + {+( fHTW2 + szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( fHTW2 + szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, + 0.f}, + {szyna[ i ].normal}, + {szyna[ i ].texture.x, 0.f} }; + Left[ 11 - i ] = { + { ( -fHTW - iglica[ i ].position.x ) * cos1 + iglica[ i ].position.y * sin1, + -( -fHTW - iglica[ i ].position.x ) * sin1 + iglica[ i ].position.y * cos1, + 0.f}, + {iglica[ i ].normal * flipxvalue}, + {iglica[ i ].texture.x, 0.f} }; + Left[ 23 - i ] = { + { ( -fHTW2 - szyna[ i ].position.x ) * cos2 + szyna[ i ].position.y * sin2, + -( -fHTW2 - szyna[ i ].position.x ) * sin2 + iglica[ i ].position.y * cos2, + 0.f}, + {szyna[ i ].normal * flipxvalue}, + {szyna[ i ].texture.x, 0.f} }; + } +} + +// creates trackbed profile data for current track +void +TTrack::create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next ) { + // geometry parameters + auto * profilesource = ( + eType != tt_Switch ? this : + Previous && Previous->eType != tt_Switch ? Previous : + Next && Next->eType != tt_Switch ? Next : + this ); + + auto const texheight1 { profilesource->fTexHeight1 }; + auto const texwidth { profilesource->fTexWidth }; + auto const texslope { profilesource->fTexSlope }; + + auto const fHTW { 0.5f * std::abs( fTrackWidth ) }; + auto const side { std::abs( texwidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza + auto const slop { std::abs( texslope ) }; // brzeg zewnętrzny + auto const rozp { fHTW + side + slop }; // brzeg zewnętrzny + + auto hypot1 { std::hypot( slop, texheight1 ) }; // rozmiar pochylenia do liczenia normalnych + if( hypot1 == 0.f ) + hypot1 = 1.f; + + glm::vec3 normal1 { texheight1 / hypot1, texslope / hypot1, 0.f }; // wektor normalny + if( glm::length( normal1 ) == 0.f ) { + // fix normal for vertical surfaces + normal1 = glm::vec3 { 1.f, 0.f, 0.f }; + } + + glm::vec3 normal2; + float fHTW2, side2, slop2, rozp2, fTexHeight2, hypot2; + if( ( Next != nullptr ) + && ( Next->eType != tt_Switch ) + && ( ( iTrapezoid & 2 ) // ten bit oznacza, że istnieje odpowiednie pNext + || ( eType == tt_Switch ) ) ) { + fHTW2 = 0.5f * std::abs(Next->fTrackWidth); // połowa rozstawu/nawierzchni + side2 = std::abs(Next->fTexWidth); + slop2 = std::abs(Next->fTexSlope); // nie jest używane później + rozp2 = fHTW2 + side2 + slop2; + fTexHeight2 = Next->fTexHeight1; + hypot2 = std::hypot(slop2, fTexHeight2); + if( hypot2 == 0.f ) + hypot2 = 1.f; + normal2 = { fTexHeight2 / hypot2, Next->fTexSlope / hypot2, 0.f }; + if( glm::length( normal2 ) == 0.f ) { + // fix normal for vertical surfaces + normal2 = glm::vec3 { 1.f, 0.f, 0.f }; + } + } + else { + // gdy nie ma następnego albo jest nieodpowiednim końcem podpięty + fHTW2 = fHTW; + side2 = side; + slop2 = slop; + rozp2 = rozp; + fTexHeight2 = texheight1; + hypot2 = hypot1; + normal2 = normal1; + } + + float + roll1{ 0.f }, + roll2{ 0.f }; + + if( Segment ) { + Segment->GetRolls( roll1, roll2 ); + } + + float const + sin1 { std::sin( roll1 ) }, + cos1 { std::cos( roll1 ) }, + sin2 { std::sin( roll2 ) }, + cos2 { std::cos( roll2 ) }; + + // profile + auto const transition { ( iTrapezoid != 0 ) || ( eType == tt_Switch ) }; + auto const pointcount { transition ? 10 : 5 }; + Output.resize( pointcount ); + // potentially retrieve texture length override from the assigned material + auto const texturelength { texture_length( copy_adjacent_trackbed_material() ) }; + auto const railheight { 0.18f }; + if( texturelength == 4.f ) { + // stare mapowanie z różną gęstością pikseli i oddzielnymi teksturami na każdy profil + auto const normalx = std::cos( glm::radians( 75.f ) ); + auto const normaly = std::sin( glm::radians( 75.f ) ); + if( transition ) { + // trapez albo przechyłki + // ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671 ((0.3464,0.6536) + // bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu + Output[ 0 ] = { + {rozp, -texheight1 - railheight, 0.f}, + {normalx, normaly, 0.f}, + {0.00f, 0.f} }; // lewy brzeg + Output[ 1 ] = { + {( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - railheight, 0.f}, + {normalx, normaly, 0.f}, + {0.33f, 0.f} }; // krawędź załamania + Output[ 2 ] = { + {0.f, -railheight + 0.01f, 0.f}, + {0.f, 1.f, 0.f}, + {0.5f, 0.f} }; // middle + Output[ 3 ] = { + {-Output[ 1 ].position.x, +( fHTW + side ) * sin1 - railheight, 0.f}, + {-normalx, normaly, 0.f}, + {0.67f, 0.f} }; // prawy brzeg początku symetrycznie + Output[ 4 ] = { + {-rozp, -texheight1 - railheight, 0.f}, + {-normalx, normaly, 0.f}, + {1.f, 0.f} }; // prawy skos + // końcowy przekrój + Output[ 5 ] = { + {rozp2, -fTexHeight2 - railheight, 0.f}, + {normalx, normaly, 0.f}, + {0.00f, 0.f} }; // lewy brzeg + Output[ 6 ] = { + {( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - railheight, 0.f}, + {normalx, normaly, 0.f}, + {0.33f, 0.f} }; // krawędź załamania + Output[ 7 ] = { + {0.f, -railheight + 0.01f, 0.f}, + {0.f, 1.f, 0.f}, + {0.5f, 0.f} }; // middle + Output[ 8 ] = { + {-Output[ 6 ].position.x, +( fHTW2 + side2 ) * sin2 - railheight, 0.f}, + {-normalx, normaly, 0.f}, + {0.67f, 0.f} }; // prawy brzeg początku symetrycznie + Output[ 9 ] = { + {-rozp2, -fTexHeight2 - railheight, 0.f}, + {-normalx, normaly, 0.f}, + {1.00f, 0.f} }; // prawy skos + } + else { + Output[ 0 ] = { + {rozp, -texheight1 - railheight, 0.f}, + {normalx, normaly, 0.f}, + {0.00f, 0.f} }; // lewy brzeg + Output[ 1 ] = { + {fHTW + side, -railheight, 0.f}, + {normalx, normaly, 0.f}, + {0.33f, 0.f} }; // krawędź załamania + Output[ 2 ] = { + {0.f, -railheight + 0.01f, 0.f}, + {0.f, 1.f, 0.f}, + {0.5f, 0.f} }; // middle + Output[ 3 ] = { + {-fHTW - side, -railheight, 0.f}, + {-normalx, normaly, 0.f}, + {0.67f, 0.f} }; // druga + Output[ 4 ] = { + {-rozp, -texheight1 - railheight, 0.f}, + {-normalx, normaly, 0.f}, + {1.00f, 0.f} }; // prawy skos + } + } + else { + // mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa fTexLength + auto const max = fTexRatio2 * texturelength; // szerokość proporcjonalna do długości + auto const map11 = max > 0.f ? (fHTW + side) / max : 0.25f; // załamanie od strony 1 + auto const map12 = max > 0.f ? (fHTW + side + hypot1) / max : 0.5f; // brzeg od strony 1 + if (transition) { + // trapez albo przechyłki + auto const map21 = max > 0.f ? (fHTW2 + side2) / max : 0.25f; // załamanie od strony 2 + auto const map22 = max > 0.f ? (fHTW2 + side2 + hypot2) / max : 0.5f; // brzeg od strony 2 + // ewentualnie poprawić mapowanie, żeby środek mapował się na 1.435/4.671 + // ((0.3464,0.6536) + // bo się tekstury podsypki rozjeżdżają po zmianie proporcji profilu + Output[ 0 ] = { + {rozp, -texheight1 - railheight, 0.f}, + {normal1.x, normal1.y, 0.f}, + {0.5f - map12, 0.f} }; // lewy brzeg + Output[ 1 ] = { + {( fHTW + side ) * cos1, -( fHTW + side ) * sin1 - railheight, 0.f}, + {normal1.x, normal1.y, 0.f}, + {0.5f - map11 , 0.f} }; // krawędź załamania + Output[ 2 ] = { + {0.f, -railheight + 0.01f, 0.f}, + {0.f, 1.f, 0.f}, + {0.5f, 0.f} }; // middle + Output[ 3 ] = { + {-Output[ 1 ].position.x, +( fHTW + side ) * sin1 - railheight, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5 + map11, 0.f} }; // prawy brzeg początku symetrycznie + Output[ 4 ] = { + {-rozp, -texheight1 - railheight, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5f + map12, 0.f} }; // prawy skos + // przekrój końcowy + Output[ 5 ] = { + {rozp2, -fTexHeight2 - railheight, 0.f}, + {normal2.x, normal2.y, 0.f}, + {0.5f - map22, 0.f} }; // lewy brzeg + Output[ 6 ] = { + {( fHTW2 + side2 ) * cos2, -( fHTW2 + side2 ) * sin2 - railheight, 0.f}, + {normal2.x, normal2.y, 0.f}, + {0.5f - map21 , 0.f} }; // krawędź załamania + Output[ 7 ] = { + {0.f, -railheight + 0.01f, 0.f}, + {0.f, 1.f, 0.f}, + {0.5f, 0.f} }; // middle + Output[ 8 ] = { + {-Output[ 6 ].position.x, +( fHTW2 + side2 ) * sin2 - railheight, 0.f}, + {-normal2.x, normal2.y, 0.f}, + {0.5f + map21, 0.f} }; // prawy brzeg początku symetrycznie + Output[ 9 ] = { + {-rozp2, -fTexHeight2 - railheight, 0.f}, + {-normal2.x, normal2.y, 0.f}, + {0.5f + map22, 0.f} }; // prawy skos + } + else + { + Output[ 0 ] = { + {rozp, -texheight1 - railheight, 0.f}, + {+normal1.x, normal1.y, 0.f}, + {0.5f - map12, 0.f} }; // lewy brzeg + Output[ 1 ] = { + {fHTW + side, - railheight, 0.f}, + {+normal1.x, normal1.y, 0.f}, + {0.5f - map11, 0.f} }; // krawędź załamania + Output[ 2 ] = { + {0.f, -railheight + 0.01f, 0.f}, + {0.f, 1.f, 0.f}, + {0.5f, 0.f} }; // middle + Output[ 3 ] = { + {-fHTW - side, - railheight, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5f + map11, 0.f} }; // druga + Output[ 4 ] = { + {-rozp, -texheight1 - railheight, 0.f}, + {-normal1.x, normal1.y, 0.f}, + {0.5f + map12, 0.f} }; // prawy skos + } + } +} + +// creates road profile data for current path +void +TTrack::create_road_profile( gfx::vertex_array &Output, bool const Forcetransition ) { + + auto const fHTW { 0.5f * std::abs( fTrackWidth ) }; + float const fHTW2 { ( + ( iTrapezoid & 2 ) != 0 ? // ten bit oznacza, że istnieje odpowiednie pNext + 0.5f * std::fabs( trNext->fTrackWidth ) : // połowa rozstawu/nawierzchni + fHTW ) }; + + glm::vec3 const normalup { 0.f, 1.f, 0.f }; + + auto const texturelength { texture_length( m_material1 ) }; + auto const max = fTexRatio1 * texturelength; // test: szerokość proporcjonalna do długości + auto const map1 = max > 0.f ? fHTW / max : 0.5f; // obcięcie tekstury od strony 1 + auto const map2 = max > 0.f ? fHTW2 / max : 0.5f; // obcięcie tekstury od strony 2 + + auto const transition { ( true == Forcetransition ) || ( iTrapezoid != 0 ) }; + + auto const pointcount{ transition ? 4 : 2 }; + Output.resize( pointcount ); + + if( transition ) { + // trapez albo przechyłki + float + roll1 { 0.f }, + roll2 { 0.f }; + + if( Segment ) { + Segment->GetRolls( roll1, roll2 ); + } + + Output[ 0 ] = { + {fHTW * std::cos( roll1 ), -fHTW * std::sin( roll1 ), 0.f}, + normalup, + {0.5f - map1, 0.f} }; // lewy brzeg początku + Output[ 1 ] = { + {-Output[ 0 ].position.x, -Output[ 0 ].position.y, 0.f}, + normalup, + {0.5f + map1, 0.f} }; // prawy brzeg początku symetrycznie + Output[ 2 ] = { + {fHTW2 * std::cos( roll2 ), -fHTW2 * std::sin( roll2 ), 0.f}, + normalup, + {0.5f - map2, 0.f} }; // lewy brzeg końca + Output[ 3 ] = { + {-Output[ 2 ].position.x, -Output[ 2 ].position.y, 0.f}, + normalup, + {0.5f + map2, 0.f} }; // prawy brzeg początku symetrycznie + } + else { + Output[ 0 ] = { + {fHTW, 0.f, 0.f}, + normalup, + {0.5f - map1, 0.f} }; + Output[ 1 ] = { + {-fHTW, 0.f, 0.f}, + normalup, + {0.5f + map1, 0.f} }; + } +} + +void +TTrack::create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition ) { + + auto const fHTW{ 0.5f * std::abs( fTrackWidth ) }; + auto const side{ std::abs( fTexWidth ) }; // szerokść podsypki na zewnątrz szyny albo pobocza + auto const slop{ std::abs( fTexSlope ) }; // brzeg zewnętrzny + auto const rozp{ fHTW + side + slop }; // brzeg zewnętrzny + + float fHTW2, side2, slop2, rozp2, fTexHeight2; + if( iTrapezoid & 2 ) { + // ten bit oznacza, że istnieje odpowiednie pNext + // Ra: jest OK + fHTW2 = 0.5f * std::fabs( trNext->fTrackWidth ); // połowa rozstawu/nawierzchni + side2 = std::fabs( trNext->fTexWidth ); + slop2 = std::fabs( trNext->fTexSlope ); // nie jest używane później + rozp2 = fHTW2 + side2 + slop2; + fTexHeight2 = trNext->fTexHeight1; + } + else { + // gdy nie ma następnego albo jest nieodpowiednim końcem podpięty + fHTW2 = fHTW; + side2 = side; + slop2 = slop; + rozp2 = rozp; + fTexHeight2 = fTexHeight1; + } + + glm::vec3 const normalup{ 0.f, 1.f, 0.f }; + + auto const texturelength{ texture_length( m_material2 ) }; + + auto const transition { ( true == Forcetransition ) || ( iTrapezoid != 0 ) }; + + auto const pointcount{ transition ? 6 : 3 }; + Right.resize( pointcount ); + Left.resize( pointcount ); + + + if( fTexHeight1 >= 0.f ) { // standardowo: od zewnątrz pochylenie, a od wewnątrz poziomo + Right[ 0 ] = { + {rozp, -fTexHeight1, 0.f}, + { 1.f, 0.f, 0.f }, + {0.f, 0.f} }; // lewy brzeg podstawy + Right[ 1 ] = { + {Road[ 0 ].position.x + side, Road[ 0 ].position.y, 0.f}, + normalup, + {0.5, 0.f} }; // lewa krawędź załamania + Right[ 2 ] = { + {Road[ 0 ].position.x, Road[ 0 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // lewy brzeg pobocza (mapowanie może być inne + Left[ 0 ] = { + {Road[ 1 ].position.x, Road[ 1 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // prawy brzeg pobocza + Left[ 1 ] = { + {Road[ 1 ].position.x - side, Road[ 1 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // prawa krawędź załamania + Left[ 2 ] = { + {-rozp, -fTexHeight1, 0.f}, + { -1.f, 0.f, 0.f }, + {0.f, 0.f} }; // prawy brzeg podstawy + if( transition ) { + // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka + Right[ 3 ] = { + {rozp2, -fTexHeight2, 0.f}, + { 1.f, 0.f, 0.f }, + {0.f, 0.f} }; // lewy brzeg lewego pobocza + Right[ 4 ] = { + {Road[ 2 ].position.x + side2, Road[ 2 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; // krawędź załamania + Right[ 5 ] = { + {Road[ 2 ].position.x, Road[ 2 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; // brzeg pobocza + Left[ 3 ] = { + {Road[ 3 ].position.x, Road[ 3 ].position.y, 0.f}, + normalup, + {1.f, 0.f} }; + Left[ 4 ] = { + {Road[ 3 ].position.x - side2, Road[ 3 ].position.y, 0.f}, + normalup, + {0.5f, 0.f} }; + Left[ 5 ] = { + {-rozp2, -fTexHeight2, 0.f}, + { -1.f, 0.f, 0.f }, + {0.f, 0.f} }; // prawy brzeg prawego pobocza + } + } + else { // wersja dla chodnika: skos 1:3.75, każdy chodnik innej szerokości + // mapowanie propocjonalne do szerokości chodnika + // krawężnik jest mapowany od 31/64 do 32/64 lewy i od 32/64 do 33/64 prawy + auto const d = -fTexHeight1 / 3.75f; // krawężnik o wysokości 150mm jest pochylony 40mm + auto const max = fTexRatio2 * texturelength; // test: szerokość proporcjonalna do długości + auto const map1l = ( + max > 0.f ? + side / max : + 0.484375f ); // obcięcie tekstury od lewej strony punktu 1 + auto const map1r = ( + max > 0.f ? + slop / max : + 0.484375f ); // obcięcie tekstury od prawej strony punktu 1 + auto const h1r = ( + slop > d ? + -fTexHeight1 : + 0.f ); + auto const h1l = ( + side > d ? + -fTexHeight1 : + 0.f ); + + Right[ 0 ] = { + {Road[ 0 ].position.x + slop, Road[ 0 ].position.y + h1r, 0.f}, + normalup, + {0.515625f + map1r, 0.f} }; // prawy brzeg prawego chodnika + Right[ 1 ] = { + {Road[ 0 ].position.x + d, Road[ 0 ].position.y + h1r, 0.f}, + normalup, + {0.515625f, 0.f} }; // prawy krawężnik u góry + Right[ 2 ] = { + {Road[ 0 ].position.x, Road[ 0 ].position.y, 0.f}, + { -1.f, 0.f, 0.f }, + {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu + Left[ 0 ] = { + {Road[ 1 ].position.x, Road[ 1 ].position.y, 0.f}, + { 1.f, 0.f, 0.f }, + {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu + Left[ 1 ] = { + {Road[ 1 ].position.x - d, Road[ 1 ].position.y + h1l, 0.f}, + normalup, + {0.484375f, 0.f} }; // lewy krawężnik u góry + Left[ 2 ] = { + {Road[ 1 ].position.x - side, Road[ 1 ].position.y + h1l, 0.f}, + normalup, + {0.484375f - map1l, 0.f} }; // lewy brzeg lewego chodnika + + if( transition ) { + // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka + slop2 = ( + std::fabs( ( iTrapezoid & 2 ) ? + slop2 : + slop ) ); // szerokość chodnika po prawej + auto const map2l = ( + max > 0.f ? + side2 / max : + 0.484375f ); // obcięcie tekstury od lewej strony punktu 2 + auto const map2r = ( + max > 0.f ? + slop2 / max : + 0.484375f ); // obcięcie tekstury od prawej strony punktu 2 + auto const h2r = ( + slop2 > d ? + -fTexHeight2 : + 0.f ); + auto const h2l = ( + side2 > d ? + -fTexHeight2 : + 0.f ); + + Right[ 3 ] = { + {Road[ 2 ].position.x + slop2, Road[ 2 ].position.y + h2r, 0.f}, + normalup, + {0.515625f + map2r, 0.f} }; // prawy brzeg prawego chodnika + Right[ 4 ] = { + {Road[ 2 ].position.x + d, Road[ 2 ].position.y + h2r, 0.f}, + normalup, + {0.515625f, 0.f} }; // prawy krawężnik u góry + Right[ 5 ] = { + {Road[ 2 ].position.x, Road[ 2 ].position.y, 0.f}, + { -1.f, 0.f, 0.f }, + {0.515625f - d / 2.56f, 0.f} }; // prawy krawężnik u dołu + Left[ 3 ] = { + {Road[ 3 ].position.x, Road[ 3 ].position.y, 0.f}, + { 1.f, 0.f, 0.f }, + {0.484375f + d / 2.56f, 0.f} }; // lewy krawężnik u dołu + Left[ 4 ] = { + {Road[ 3 ].position.x - d, Road[ 3 ].position.y + h2l, 0.f}, + normalup, + {0.484375f, 0.f} }; // lewy krawężnik u góry + Left[ 5 ] = { + {Road[ 3 ].position.x - side2, Road[ 3 ].position.y + h2l, 0.f}, + normalup, + {0.484375f - map2l, 0.f} }; // lewy brzeg lewego chodnika + } + } +} + +void +TTrack::create_switch_trackbed( gfx::vertex_array &Output ) { + // try to get trackbed material from a regular track connected to the primary path + if( ( SwitchExtension->m_material3 == null_handle ) + && ( trPrev != nullptr ) + && ( trPrev->eType == tt_Normal ) ) { + SwitchExtension->m_material3 = trPrev->m_material2; + } + if( ( SwitchExtension->m_material3 == null_handle ) + && ( trNext != nullptr ) + && ( trNext->eType == tt_Normal ) ) { + SwitchExtension->m_material3 = trNext->m_material2; + } + // without material don't bother + if( SwitchExtension->m_material3 == null_handle ) { return; } + // generate trackbed for each path of the switch... + auto const texturelength { texture_length( SwitchExtension->m_material3 ) }; + gfx::vertex_array trackbedprofile; + 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 ); + // side trackbed + create_track_bed_profile( trackbedprofile, SwitchExtension->pPrevs[ 1 ], SwitchExtension->pNexts[ 1 ] ); + SwitchExtension->Segments[ 1 ]->RenderLoft( trackbedvertices2, m_origin, trackbedprofile, -5, texturelength ); + // ...then combine them into a single geometry sequence + auto const segmentsize { 10 }; + auto const segmentcount { trackbedvertices1.size() / segmentsize }; + auto *sampler1 { trackbedvertices1.data() }; + auto *sampler2 { trackbedvertices2.data() }; + auto const isright { SwitchExtension->RightSwitch }; + auto const isleft { false == isright }; + auto const samplersoffset { isright ? 2 : 0 }; + auto const geometryoffset { 0.025f }; + for( int segment = 0; segment < segmentcount; ++segment ) { + // main trackbed + // lower outer edge to avoid z-fighting + if( isright ) { + ( sampler1 + samplersoffset + 0 )->position.y -= geometryoffset; + ( sampler1 + samplersoffset + 1 )->position.y -= geometryoffset; + } + if( isleft ) { + ( sampler1 + samplersoffset + 6 )->position.y -= geometryoffset; + ( sampler1 + samplersoffset + 7 )->position.y -= geometryoffset; + } + // copy the data + for( auto pointidx = 0; pointidx < segmentsize; ++pointidx ) { + Output.emplace_back( *( sampler1 + pointidx ) ); + } + // side trackbed + // lower outer edge to avoid z-fighting + if( isleft ) { + ( sampler2 - samplersoffset + 2 )->position.y -= geometryoffset; + ( sampler2 - samplersoffset + 3 )->position.y -= geometryoffset; + } + if( isright ) { + ( sampler2 - samplersoffset + 8 )->position.y -= geometryoffset; + ( sampler2 - samplersoffset + 9 )->position.y -= geometryoffset; + } + // copy the data + for( auto pointidx = 0; pointidx < segmentsize; ++pointidx ) { + Output.emplace_back( *( sampler2 + pointidx ) ); + } + // switch to next segment data + sampler1 += segmentsize; + sampler2 += segmentsize; + } +} + +material_handle +TTrack::copy_adjacent_trackbed_material( TTrack const *Exclude ) { + + if( iCategoryFlag != 1 ) { return null_handle; } // tracks only + + auto &material { eType == tt_Switch ? SwitchExtension->m_material3 : m_material2 }; + + if( material != null_handle ) { return material; } // already has material + + std::vector adjacents; + switch( eType ) { + case tt_Normal: { + // for regular tracks don't set the trackbed texture if we aren't sitting next to a part of a double slip +/* + auto const hasadjacentdoubleslip { + ( trPrev ? trPrev->DoubleSlip() : false ) + || ( trNext ? trNext->DoubleSlip() : false ) }; + + if( true == hasadjacentdoubleslip ) { + adjacents.emplace_back( trPrev ); + adjacents.emplace_back( trNext ); + } +*/ + auto const hasadjacentswitch { + ( trPrev && trPrev->eType == tt_Switch ) + || ( trNext && trNext->eType == tt_Switch ) }; + +// if( true == hasadjacentdoubleslip ) { + if( true == hasadjacentswitch ) { + adjacents.emplace_back( trPrev ); + adjacents.emplace_back( trNext ); + } + break; + } + case tt_Switch: { + // only check the neighbour on the joint side + adjacents.emplace_back( SwitchExtension->pPrevs[ 0 ] ); + break; + } + default: { + break; + } + } + + for( auto *adjacent : adjacents ) { + if( ( adjacent != nullptr ) && ( adjacent != Exclude ) ) { + material = adjacent->copy_adjacent_trackbed_material( this ); + if( material != null_handle ) { break; } // got what we wanted + } + } + + return material; +} + path_table::~path_table() { @@ -3131,6 +3175,10 @@ path_table::InitTracks() { break; } } + if( Global.CreateSwitchTrackbeds ) { + // when autogenerating trackbeds, try to restore trackbeds for tracks neighbouring double slips + track->copy_adjacent_trackbed_material(); + } break; } case tt_Switch: { @@ -3138,6 +3186,10 @@ path_table::InitTracks() { track->AssignForcedEvents( simulation::Events.FindEvent( trackname + ":forced+" ), simulation::Events.FindEvent( trackname + ":forced-" ) ); + if( Global.CreateSwitchTrackbeds ) { + // when autogenerating trackbeds, try to restore trackbeds for tracks neighbouring double slips + track->copy_adjacent_trackbed_material(); + } break; } default: { diff --git a/Track.h b/Track.h index e94cfa05..9dda44b5 100644 --- a/Track.h +++ b/Track.h @@ -93,6 +93,8 @@ class TSwitchExtension *evMinus = nullptr; // zdarzenia sygnalizacji rozprucia float fVelocity = -1.0; // maksymalne ograniczenie prędkości (ustawianej eventem) Math3D::vector3 vTrans; // docelowa translacja przesuwnicy + material_handle m_material3 = 0; // texture of auto generated switch trackbed + gfx::geometry_handle Geometry3; // geometry of auto generated switch trackbed }; class TIsolated @@ -134,7 +136,7 @@ private: // members int iAxles { 0 }; // ilość osi na odcinkach obsługiwanych przez obiekt TIsolated *pNext { nullptr }; // odcinki izolowane są trzymane w postaci listy jednikierunkowej - TIsolated *pParent { nullptr }; // optional parent piece, collecting data from its children + TIsolated *pParent { nullptr }; // optional parent piece, receiving data from its children static TIsolated *pRoot; // początek listy }; @@ -222,6 +224,7 @@ public: void ConnectPrevNext(TTrack *pNewPrev, int typ); void ConnectNextPrev(TTrack *pNewNext, int typ); void ConnectNextNext(TTrack *pNewNext, int typ); + material_handle copy_adjacent_trackbed_material( TTrack const *Exclude = nullptr ); inline double Length() const { return Segment->GetLength(); }; inline std::shared_ptr CurrentSegment() const { @@ -289,6 +292,7 @@ public: void VelocitySet(float v); double VelocityGet(); void ConnectionsLog(); + bool DoubleSlip() const; private: // radius() subclass details, calculates node's bounding radius @@ -301,6 +305,13 @@ private: void export_as_text_( std::ostream &Output ) const; // returns texture length for specified material float texture_length( material_handle const Material ); + // creates profile for a part of current path + void create_switch_trackbed( gfx::vertex_array &Output ); + void create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ); + void create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ); + void create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next ); + void create_road_profile( gfx::vertex_array &Output, bool const Forcetransition = false ); + void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false ); }; diff --git a/Traction.cpp b/Traction.cpp index fc83e01d..4173d82d 100644 --- a/Traction.cpp +++ b/Traction.cpp @@ -567,7 +567,7 @@ TTraction::wire_color() const { color.r *= Global.DayLight.ambient[ 0 ]; color.g *= Global.DayLight.ambient[ 1 ]; color.b *= Global.DayLight.ambient[ 2 ]; - color *= 0.5f; + color *= 0.35f; } else { // tymczasowo pokazanie zasilanych odcinków diff --git a/Train.cpp b/Train.cpp index cd2e38bd..4b6c0d22 100644 --- a/Train.cpp +++ b/Train.cpp @@ -266,6 +266,9 @@ TTrain::commandhandler_map const TTrain::m_commandhandlers = { { user_command::compressorenable, &TTrain::OnCommand_compressorenable }, { user_command::compressordisable, &TTrain::OnCommand_compressordisable }, { user_command::compressortogglelocal, &TTrain::OnCommand_compressortogglelocal }, + { user_command::motorblowerstogglefront, &TTrain::OnCommand_motorblowerstogglefront }, + { user_command::motorblowerstogglerear, &TTrain::OnCommand_motorblowerstogglerear }, + { user_command::motorblowersdisableall, &TTrain::OnCommand_motorblowersdisableall }, { user_command::motorconnectorsopen, &TTrain::OnCommand_motorconnectorsopen }, { user_command::motorconnectorsclose, &TTrain::OnCommand_motorconnectorsclose }, { user_command::motordisconnect, &TTrain::OnCommand_motordisconnect }, @@ -448,7 +451,9 @@ bool TTrain::Init(TDynamicObject *NewDynamicObject, bool e3d) PyObject *TTrain::GetTrainState() { auto const *mover = DynamicObject->MoverParameters; + PyEval_AcquireLock(); auto *dict = PyDict_New(); + PyEval_ReleaseLock(); if( ( dict == nullptr ) || ( mover == nullptr ) ) { return nullptr; @@ -2776,8 +2781,193 @@ void TTrain::OnCommand_compressortogglelocal( TTrain *Train, command_data const } } -void TTrain::OnCommand_motorconnectorsopen( TTrain *Train, command_data const &cmd ) { - command_data Command = cmd; +void TTrain::OnCommand_motorblowerstogglefront( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggMotorBlowersFrontButton.type() == TGaugeType::push ) { + // impulse switch + // currently there's no off button so we always try to turn it on + OnCommand_motorblowersenablefront( Train, Command ); + } + else { + // two-state switch + if( Command.action == GLFW_RELEASE ) { return; } + + if( false == Train->mvControlled->MotorBlowers[side::front].is_enabled ) { + // turn on + OnCommand_motorblowersenablefront( Train, Command ); + } + else { + //turn off + OnCommand_motorblowersdisablefront( Train, Command ); + } + } +} + +void TTrain::OnCommand_motorblowersenablefront( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggMotorBlowersFrontButton.type() == TGaugeType::push ) { + // impulse switch + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggMotorBlowersFrontButton.UpdateValue( 1.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( true, side::front ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + Train->ggMotorBlowersFrontButton.UpdateValue( 0.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( false, side::front ); + } + } + else { + // two-state switch, only cares about press events + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggMotorBlowersFrontButton.UpdateValue( 1.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( true, side::front ); + Train->mvControlled->MotorBlowersSwitchOff( false, side::front ); + } + } +} + +void TTrain::OnCommand_motorblowersdisablefront( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggMotorBlowersFrontButton.type() == TGaugeType::push ) { + // impulse switch + // currently there's no disable return type switch + return; + } + else { + // two-state switch, only cares about press events + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggMotorBlowersFrontButton.UpdateValue( 0.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( false, side::front ); + Train->mvControlled->MotorBlowersSwitchOff( true, side::front ); + } + } +} + +void TTrain::OnCommand_motorblowerstogglerear( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggMotorBlowersRearButton.type() == TGaugeType::push ) { + // impulse switch + // currently there's no off button so we always try to turn it on + OnCommand_motorblowersenablerear( Train, Command ); + } + else { + // two-state switch + if( Command.action == GLFW_RELEASE ) { return; } + + if( false == Train->mvControlled->MotorBlowers[ side::rear ].is_enabled ) { + // turn on + OnCommand_motorblowersenablerear( Train, Command ); + } + else { + //turn off + OnCommand_motorblowersdisablerear( Train, Command ); + } + } +} + +void TTrain::OnCommand_motorblowersenablerear( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggMotorBlowersRearButton.type() == TGaugeType::push ) { + // impulse switch + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggMotorBlowersRearButton.UpdateValue( 1.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( true, side::rear ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + Train->ggMotorBlowersRearButton.UpdateValue( 0.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( false, side::rear ); + } + } + else { + // two-state switch, only cares about press events + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggMotorBlowersRearButton.UpdateValue( 1.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( true, side::rear ); + Train->mvControlled->MotorBlowersSwitchOff( false, side::rear ); + } + } +} + +void TTrain::OnCommand_motorblowersdisablerear( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggMotorBlowersRearButton.type() == TGaugeType::push ) { + // impulse switch + // currently there's no disable return type switch + return; + } + else { + // two-state switch, only cares about press events + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggMotorBlowersRearButton.UpdateValue( 0.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitch( false, side::rear ); + Train->mvControlled->MotorBlowersSwitchOff( true, side::rear ); + } + } +} + +void TTrain::OnCommand_motorblowersdisableall( TTrain *Train, command_data const &Command ) { + + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggMotorBlowersAllOffButton.type() == TGaugeType::push ) { + // impulse switch + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggMotorBlowersAllOffButton.UpdateValue( 1.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitchOff( true, side::front ); + Train->mvControlled->MotorBlowersSwitchOff( true, side::rear ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + Train->ggMotorBlowersAllOffButton.UpdateValue( 0.f, Train->dsbSwitch ); + Train->mvControlled->MotorBlowersSwitchOff( false, side::front ); + Train->mvControlled->MotorBlowersSwitchOff( false, side::rear ); + } + } + else { + // two-state switch, only cares about press events + // NOTE: generally this switch doesn't come in two-state form + if( Command.action == GLFW_PRESS ) { + if( Train->ggMotorBlowersAllOffButton.GetDesiredValue() < 0.5f ) { + // switch is off, activate + Train->mvControlled->MotorBlowersSwitchOff( true, side::front ); + Train->mvControlled->MotorBlowersSwitchOff( true, side::rear ); + // visual feedback + Train->ggMotorBlowersRearButton.UpdateValue( 1.f, Train->dsbSwitch ); + } + else { + // deactivate + Train->mvControlled->MotorBlowersSwitchOff( false, side::front ); + Train->mvControlled->MotorBlowersSwitchOff( false, side::rear ); + // visual feedback + Train->ggMotorBlowersRearButton.UpdateValue( 0.f, Train->dsbSwitch ); + } + } + } +} + +void TTrain::OnCommand_motorconnectorsopen( TTrain *Train, command_data const &Command ) { + // TODO: don't rely on presense of 3d model to determine presence of the switch if( Train->ggStLinOffButton.SubModel == nullptr ) { if( Command.action == GLFW_PRESS ) { @@ -5055,7 +5245,7 @@ bool TTrain::Update( double const Deltatime ) ggWater1TempB.Update(); } if( ggOilPressB.SubModel ) { - ggOilPressB.UpdateValue( tmp->MoverParameters->OilPump.pressure_present ); + ggOilPressB.UpdateValue( tmp->MoverParameters->OilPump.pressure ); ggOilPressB.Update(); } } @@ -5356,7 +5546,7 @@ bool TTrain::Update( double const Deltatime ) btLampkaRearRightEndLight.Turn( ( mvOccupied->iLights[ side::rear ] & light::redmarker_right ) != 0 ); // others btLampkaMalfunction.Turn( mvControlled->dizel_heat.PA ); - btLampkaMotorBlowers.Turn( mvControlled->RventRot > 0.1 ); + btLampkaMotorBlowers.Turn( ( mvControlled->MotorBlowers[ side::front ].is_active ) && ( mvControlled->MotorBlowers[ side::rear ].is_active ) ); } else { // wylaczone @@ -5715,6 +5905,9 @@ bool TTrain::Update( double const Deltatime ) ggWaterCircuitsLinkButton.Update(); ggFuelPumpButton.Update(); ggOilPumpButton.Update(); + ggMotorBlowersFrontButton.Update(); + ggMotorBlowersRearButton.Update(); + ggMotorBlowersAllOffButton.Update(); //------ } // wyprowadzenie sygnałów dla haslera na PoKeys (zaznaczanie na taśmie) @@ -6945,6 +7138,9 @@ void TTrain::clear_cab_controls() ggWaterCircuitsLinkButton.Clear(); ggFuelPumpButton.Clear(); ggOilPumpButton.Clear(); + ggMotorBlowersFrontButton.Clear(); + ggMotorBlowersRearButton.Clear(); + ggMotorBlowersAllOffButton.Clear(); btLampkaPrzetw.Clear(); btLampkaPrzetwB.Clear(); @@ -7289,6 +7485,26 @@ void TTrain::set_cab_controls() { 1.0 : 0.0 ); } + // traction motor fans + if( ggMotorBlowersFrontButton.type() != TGaugeType::push ) { + ggMotorBlowersFrontButton.PutValue( + mvControlled->MotorBlowers[side::front].is_enabled ? + 1.0 : + 0.0 ); + } + if( ggMotorBlowersRearButton.type() != TGaugeType::push ) { + ggMotorBlowersRearButton.PutValue( + mvControlled->MotorBlowers[side::rear].is_enabled ? + 1.0 : + 0.0 ); + } + if( ggMotorBlowersAllOffButton.type() != TGaugeType::push ) { + ggMotorBlowersAllOffButton.PutValue( + ( mvControlled->MotorBlowers[side::front].is_disabled + || mvControlled->MotorBlowers[ side::front ].is_disabled ) ? + 1.0 : + 0.0 ); + } // we reset all indicators, as they're set during the update pass // TODO: when cleaning up break setting indicator state into a separate function, so we can reuse it @@ -7483,6 +7699,9 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { "fuelpump_sw:", ggFuelPumpButton }, { "oilpump_sw:", ggOilPumpButton }, { "oilpressb:", ggOilPressB }, + { "motorblowersfront_sw:", ggMotorBlowersFrontButton }, + { "motorblowersrear_sw:", ggMotorBlowersRearButton }, + { "motorblowersalloff_sw:", ggMotorBlowersAllOffButton }, { "radio_sw:", ggRadioButton }, { "radiochannel_sw:", ggRadioChannelSelector }, { "radiochannelprev_sw:", ggRadioChannelPrevious }, @@ -7648,7 +7867,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con // oil pressure auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka gauge.Load( Parser, DynamicObject, DynamicObject->mdKabina, nullptr ); - gauge.AssignFloat( &mvControlled->OilPump.pressure_present ); + gauge.AssignFloat( &mvControlled->OilPump.pressure ); } else if( Label == "oiltemp:" ) { // oil temperature diff --git a/Train.h b/Train.h index 82c9e7bc..c01cff46 100644 --- a/Train.h +++ b/Train.h @@ -252,6 +252,13 @@ class TTrain static void OnCommand_compressorenable( TTrain *Train, command_data const &Command ); static void OnCommand_compressordisable( TTrain *Train, command_data const &Command ); static void OnCommand_compressortogglelocal( TTrain *Train, command_data const &Command ); + static void OnCommand_motorblowerstogglefront( TTrain *Train, command_data const &Command ); + static void OnCommand_motorblowersenablefront( TTrain *Train, command_data const &Command ); + static void OnCommand_motorblowersdisablefront( TTrain *Train, command_data const &Command ); + static void OnCommand_motorblowerstogglerear( TTrain *Train, command_data const &Command ); + static void OnCommand_motorblowersenablerear( TTrain *Train, command_data const &Command ); + static void OnCommand_motorblowersdisablerear( TTrain *Train, command_data const &Command ); + static void OnCommand_motorblowersdisableall( TTrain *Train, command_data const &Command ); static void OnCommand_motorconnectorsopen( TTrain *Train, command_data const &Command ); static void OnCommand_motorconnectorsclose( TTrain *Train, command_data const &Command ); static void OnCommand_motordisconnect( TTrain *Train, command_data const &Command ); @@ -323,6 +330,7 @@ class TTrain static void OnCommand_cabchangebackward( TTrain *Train, command_data const &Command ); static void OnCommand_generictoggle( TTrain *Train, command_data const &Command ); + // members TDynamicObject *DynamicObject { nullptr }; // przestawia zmiana pojazdu [F5] TMoverParameters *mvControlled { nullptr }; // człon, w którym sterujemy silnikiem @@ -351,7 +359,7 @@ public: // reszta może by?publiczna TGauge ggI3B; TGauge ggItotalB; - TGauge ggOilPressB; + TGauge ggOilPressB; // other unit oil pressure indicator TGauge ggWater1TempB; // McZapkie: definicje regulatorow @@ -459,6 +467,9 @@ public: // reszta może by?publiczna TGauge ggWaterCircuitsLinkButton; TGauge ggFuelPumpButton; // fuel pump switch TGauge ggOilPumpButton; // fuel pump switch + TGauge ggMotorBlowersFrontButton; // front traction motor fan switch + TGauge ggMotorBlowersRearButton; // rear traction motor fan switch + TGauge ggMotorBlowersAllOffButton; // motor fans shutdown switch TButton btLampkaPoslizg; TButton btLampkaStyczn; diff --git a/command.cpp b/command.cpp index 31f9c248..d0f1a190 100644 --- a/command.cpp +++ b/command.cpp @@ -214,10 +214,13 @@ commanddescription_sequence Commands_descriptions = { { "generictoggle9", command_target::vehicle, command_mode::oneoff }, { "batterytoggle", command_target::vehicle, command_mode::oneoff }, { "batteryenable", command_target::vehicle, command_mode::oneoff }, - { "batterydisable", command_target::vehicle, command_mode::oneoff } + { "batterydisable", command_target::vehicle, command_mode::oneoff }, + { "motorblowerstogglefront", command_target::vehicle, command_mode::oneoff }, + { "motorblowerstogglerear", command_target::vehicle, command_mode::oneoff }, + { "motorblowersdisableall", command_target::vehicle, command_mode::oneoff } }; -} +} // simulation void command_queue::update() { diff --git a/command.h b/command.h index fe20f4da..e73ec9b4 100644 --- a/command.h +++ b/command.h @@ -209,6 +209,9 @@ enum class user_command { batterytoggle, batteryenable, batterydisable, + motorblowerstogglefront, + motorblowerstogglerear, + motorblowersdisableall, none = -1 }; diff --git a/driverkeyboardinput.cpp b/driverkeyboardinput.cpp index efe49008..ae8aab93 100644 --- a/driverkeyboardinput.cpp +++ b/driverkeyboardinput.cpp @@ -217,6 +217,10 @@ driverkeyboard_input::default_bindings() { { user_command::batterytoggle, GLFW_KEY_J }, // batteryenable, // batterydisable, + { user_command::motorblowerstogglefront, GLFW_KEY_N | keymodifier::shift }, + { user_command::motorblowerstogglerear, GLFW_KEY_M | keymodifier::shift }, + { user_command::motorblowersdisableall, GLFW_KEY_M | keymodifier::control } + }; } diff --git a/drivermode.cpp b/drivermode.cpp index 19571151..562c19e1 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -182,6 +182,11 @@ driver_mode::update() { // variable step simulation time routines + if( ( simulation::Train == nullptr ) && ( false == FreeFlyModeFlag ) ) { + // intercept cases when the driven train got removed after entering portal + InOutKey(); + } + if( Global.changeDynObj ) { // ABu zmiana pojazdu - przejście do innego ChangeDynamic(); diff --git a/drivermouseinput.cpp b/drivermouseinput.cpp index 3727304e..4b164e4f 100644 --- a/drivermouseinput.cpp +++ b/drivermouseinput.cpp @@ -499,6 +499,15 @@ drivermouse_input::default_bindings() { { "oilpump_sw:", { user_command::oilpumptoggle, user_command::none } }, + { "motorblowersfront_sw:", { + user_command::motorblowerstogglefront, + user_command::none } }, + { "motorblowersrear_sw:", { + user_command::motorblowerstogglerear, + user_command::none } }, + { "motorblowersalloff_sw:", { + user_command::motorblowersdisableall, + user_command::none } }, { "main_off_bt:", { user_command::linebreakeropen, user_command::none } }, diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 10d95c3e..02fc02f8 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -353,7 +353,10 @@ debug_panel::render() { render_section( "Vehicle AI", m_ailines ); render_section( "Vehicle Scan Table", m_scantablelines ); render_section( "Scenario", m_scenariolines ); - render_section( "Scenario Event Queue", m_eventqueuelines ); + if( true == render_section( "Scenario Event Queue", m_eventqueuelines ) ) { + // event queue filter + ImGui::Checkbox( "By This Vehicle Only", &m_eventqueueactivevehicleonly ); + } render_section( "Camera", m_cameralines ); render_section( "Gfx Renderer", m_rendererlines ); // toggles @@ -411,7 +414,7 @@ debug_panel::update_section_vehicle( std::vector &Output ) { ( mover.CompressorFlag ? 'C' : ( false == mover.CompressorAllowLocal ? '-' : ( ( mover.CompressorAllow || mover.CompressorStart == start_t::automatic ) ? 'c' : '.' ) ) ), ( mover.CompressorGovernorLock ? '!' : '.' ), std::string( isplayervehicle ? locale::strings[ locale::string::debug_vehicle_radio ] + ( mover.Radio ? std::to_string( m_input.train->RadioChannel() ) : "-" ) : "" ).c_str(), - std::string( isdieselenginepowered ? locale::strings[ locale::string::debug_vehicle_oilpressure ] + to_string( mover.OilPump.pressure_present, 2 ) : "" ).c_str(), + std::string( isdieselenginepowered ? locale::strings[ locale::string::debug_vehicle_oilpressure ] + to_string( mover.OilPump.pressure, 2 ) : "" ).c_str(), // power transfers mover.Couplers[ side::front ].power_high.voltage, mover.Couplers[ side::front ].power_high.current, @@ -437,6 +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, mover.dizel_heat.rpmw, mover.dizel_heat.rpmw2 ); @@ -759,6 +764,8 @@ debug_panel::update_section_scantable( std::vector &Output ) { if( m_input.mechanik == nullptr ) { return; } + Output.emplace_back( "Flags: Dist: Vel: Name:", Global.UITextColor ); + auto const &mechanik{ *m_input.mechanik }; std::size_t i = 0; std::size_t const speedtablesize = clamp( static_cast( mechanik.TableSize() ) - 1, 0, 30 ); @@ -768,8 +775,8 @@ debug_panel::update_section_scantable( std::vector &Output ) { Output.emplace_back( Bezogonkow( scanline ), Global.UITextColor ); ++i; } while( i < speedtablesize ); - if( Output.empty() ) { - Output.emplace_back( "(no points of interest)", Global.UITextColor ); + if( Output.size() == 1 ) { + Output.front().data = "(no points of interest)"; } } @@ -782,7 +789,8 @@ debug_panel::update_section_scenario( std::vector &Output ) { Output.emplace_back( textline, Global.UITextColor ); // current luminance level - textline = "Light level: " + to_string( Global.fLuminance, 3 ); + textline = "Cloud cover: " + to_string( Global.Overcast, 3 ); + textline += "\nLight level: " + to_string( Global.fLuminance, 3 ); if( Global.FakeLight ) { textline += "(*)"; } textline += "\nAir temperature: " + to_string( Global.AirTemperature, 1 ) + " deg C"; @@ -794,32 +802,33 @@ debug_panel::update_section_eventqueue( std::vector &Output ) { std::string textline; - // current event queue - auto const time { Timer::GetTime() }; - auto const *event { simulation::Events.begin() }; - auto eventtableindex{ 0 }; - while( ( event != nullptr ) - && ( eventtableindex < 30 ) ) { + // current event queue + auto const time { Timer::GetTime() }; + auto const *event { simulation::Events.begin() }; - if( ( false == event->m_ignored ) - && ( false == event->m_passive ) ) { + Output.emplace_back( "Delay: Event:", Global.UITextColor ); - auto const delay { " " + to_string( std::max( 0.0, event->m_launchtime - time ), 1 ) }; - textline = - "Delay: " + delay.substr( delay.length() - 6 ) - + ", Event: " + event->m_name - + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) - + ( event->m_sibling ? " (joint event)" : "" ); + while( ( event != nullptr ) + && ( Output.size() < 30 ) ) { - Output.emplace_back( textline, Global.UITextColor ); - ++eventtableindex; - } - event = event->m_next; - } - if( Output.empty() ) { - textline = "(no queued events)"; - Output.emplace_back( textline, Global.UITextColor ); - } + if( ( false == event->m_ignored ) + && ( false == event->m_passive ) + && ( ( false == m_eventqueueactivevehicleonly ) + || ( event->m_activator == m_input.vehicle ) ) ) { + + auto const delay { " " + to_string( std::max( 0.0, event->m_launchtime - time ), 1 ) }; + textline = delay.substr( delay.length() - 6 ) + + " " + event->m_name + + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) + + ( event->m_sibling ? " (joint event)" : "" ); + + Output.emplace_back( textline, Global.UITextColor ); + } + event = event->m_next; + } + if( Output.size() == 1 ) { + Output.front().data = "(no queued events)"; + } } void @@ -880,15 +889,16 @@ debug_panel::update_section_renderer( std::vector &Output ) { Output.emplace_back( GfxRenderer.info_stats(), Global.UITextColor ); } -void +bool debug_panel::render_section( std::string const &Header, std::vector const &Lines ) { - if( Lines.empty() ) { return; } - if( false == ImGui::CollapsingHeader( Header.c_str() ) ) { return; } + if( true == Lines.empty() ) { return false; } + 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() ); } + return true; } void diff --git a/driveruipanels.h b/driveruipanels.h index 08f81a8b..9df28d76 100644 --- a/driveruipanels.h +++ b/driveruipanels.h @@ -72,7 +72,7 @@ private: std::string update_vehicle_coupler( int const Side ); std::string update_vehicle_brake() const; // renders provided lines, under specified collapsing header - void render_section( std::string const &Header, std::vector const &Lines ); + bool render_section( std::string const &Header, std::vector const &Lines ); // members std::array m_buffer; input_data m_input; @@ -88,6 +88,7 @@ private: int tprev { 0 }; // poprzedni czas double VelPrev { 0.0 }; // poprzednia prędkość double Acc { 0.0 }; // przyspieszenie styczne + bool m_eventqueueactivevehicleonly { false }; }; class transcripts_panel : public ui_panel { diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp index 42f03486..8ed1ba1f 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -33,7 +33,6 @@ void gl::glsl_common_setup() layout(std140) uniform light_ubo { vec3 ambient; - float fog_density; vec3 fog_color; uint lights_count; @@ -50,6 +49,8 @@ void gl::glsl_common_setup() mat4 future; float opacity; float emission; + float fog_density; + float alpha_mult; }; layout (std140) uniform scene_ubo diff --git a/gl/ubo.h b/gl/ubo.h index 5bbd050e..bee247f4 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -58,7 +58,9 @@ namespace gl glm::mat4 future; float opacity; float emission; - UBS_PAD(12); + float fog_density; + float alpha_mult; + UBS_PAD(4); void set_modelview(const glm::mat4 &mv) { @@ -100,7 +102,7 @@ namespace gl struct light_ubs { glm::vec3 ambient; - float fog_density; + UBS_PAD(4); glm::vec3 fog_color; uint32_t lights_count; diff --git a/imgui/imgui_impl_opengl2.cpp b/imgui/imgui_impl_opengl2.cpp new file mode 100644 index 00000000..5745e739 --- /dev/null +++ b/imgui/imgui_impl_opengl2.cpp @@ -0,0 +1,201 @@ +// dear imgui: Renderer for OpenGL2 (legacy OpenGL, fixed pipeline) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in imgui_impl_opengl3.cpp** +// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read. +// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more +// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might +// confuse your GPU driver. +// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API. + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2018-08-03: OpenGL: Disabling/restoring GL_LIGHTING and GL_COLOR_MATERIAL to increase compatibility with legacy OpenGL applications. +// 2018-06-08: Misc: Extracted imgui_impl_opengl2.cpp/.h away from the old combined GLFW/SDL+OpenGL2 examples. +// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplGlfwGL2_RenderDrawData() in the .h file so you can call it yourself. +// 2017-09-01: OpenGL: Save and restore current polygon mode. +// 2016-09-10: OpenGL: Uploading font texture as RGBA32 to increase compatibility with users shaders (not ideal). +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. + +#include "imgui.h" +#include "imgui_impl_opengl2.h" +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#include + +// OpenGL Data +static GLuint g_FontTexture = 0; + +// Functions +bool ImGui_ImplOpenGL2_Init() +{ + return true; +} + +void ImGui_ImplOpenGL2_Shutdown() +{ + ImGui_ImplOpenGL2_DestroyDeviceObjects(); +} + +void ImGui_ImplOpenGL2_NewFrame() +{ + if (!g_FontTexture) + ImGui_ImplOpenGL2_CreateDeviceObjects(); +} + +// OpenGL2 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glDisable(GL_COLOR_MATERIAL); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnable(GL_TEXTURE_2D); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(draw_data->DisplayPos.x, draw_data->DisplayPos.x + draw_data->DisplaySize.x, draw_data->DisplayPos.y + draw_data->DisplaySize.y, draw_data->DisplayPos.y, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // Render command lists + ImVec2 pos = draw_data->DisplayPos; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos))); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv))); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col))); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + // User callback (registered via ImDrawList::AddCallback) + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + ImVec4 clip_rect = ImVec4(pcmd->ClipRect.x - pos.x, pcmd->ClipRect.y - pos.y, pcmd->ClipRect.z - pos.x, pcmd->ClipRect.w - pos.y); + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) + { + // Apply scissor/clipping rectangle + glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); + + // Bind texture, Draw + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); + } + } + idx_buffer += pcmd->ElemCount; + } + } + + // Restore modified state + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +bool ImGui_ImplOpenGL2_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplOpenGL2_DestroyFontsTexture() +{ + if (g_FontTexture) + { + ImGuiIO& io = ImGui::GetIO(); + glDeleteTextures(1, &g_FontTexture); + io.Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +bool ImGui_ImplOpenGL2_CreateDeviceObjects() +{ + return ImGui_ImplOpenGL2_CreateFontsTexture(); +} + +void ImGui_ImplOpenGL2_DestroyDeviceObjects() +{ + ImGui_ImplOpenGL2_DestroyFontsTexture(); +} diff --git a/imgui/imgui_impl_opengl2.h b/imgui/imgui_impl_opengl2.h new file mode 100644 index 00000000..911447a4 --- /dev/null +++ b/imgui/imgui_impl_opengl2.h @@ -0,0 +1,28 @@ +// dear imgui: Renderer for OpenGL2 (legacy OpenGL, fixed pipeline) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in imgui_impl_opengl3.cpp** +// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read. +// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more +// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might +// confuse your GPU driver. +// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API. + +IMGUI_IMPL_API bool ImGui_ImplOpenGL2_Init(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateFontsTexture(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyFontsTexture(); +IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyDeviceObjects(); diff --git a/renderer.cpp b/renderer.cpp index 71bbb955..59a87ced 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -965,7 +965,7 @@ void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mod // modelview camera.position() = Global.pCamera.Pos; Global.pCamera.SetMatrix(viewmatrix); - // projection + // projection float znear = 0.1f * Global.ZoomFactor; float zfar = Config.draw_range * Global.fDistanceFactor; camera.projection() = perspective_projection(fovy, aspect, znear, zfar); @@ -980,7 +980,7 @@ void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mod glm::dvec3 const cubefaceupvectors[6] = {{0.0, -1.0, 0.0}, {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0}, {0.0, -1.0, 0.0}, {0.0, -1.0, 0.0}}; auto const cubefaceindex = m_environmentcubetextureface; viewmatrix *= glm::lookAt(camera.position(), camera.position() + cubefacetargetvectors[cubefaceindex], cubefaceupvectors[cubefaceindex]); - // projection + // projection float znear = 0.1f * Global.ZoomFactor; float zfar = Config.draw_range * Global.fDistanceFactor; camera.projection() = perspective_projection(glm::radians(90.f), 1.f, znear, zfar); @@ -1306,10 +1306,22 @@ void opengl_renderer::Bind_Material(material_handle const Material, TSubModel *s model_ubs.param[entry.location][entry.offset + j] = src[j]; } - if (std::isnan(material.opacity)) - model_ubs.opacity = m_blendingenabled ? -1.0f : 0.5f; + if (m_blendingenabled) + { + model_ubs.opacity = -1.0f; + } + else + { + if (!std::isnan(material.opacity)) + model_ubs.opacity = material.opacity; + else + model_ubs.opacity = 0.5f; + } + + if (sm) + model_ubs.alpha_mult = sm->fVisible; else - model_ubs.opacity = m_blendingenabled ? -1.0f : material.opacity; + model_ubs.alpha_mult = 1.0f; if (GLEW_ARB_multi_bind) { @@ -2140,29 +2152,11 @@ void opengl_renderer::Render(TSubModel *Submodel) case rendermode::color: case rendermode::reflections: { -// NOTE: code disabled as normalization marking doesn't take into account scaling propagation down hierarchy chains -// for the time being we'll do with enforced worst-case scaling method, when speculars are enabled -#ifdef EU07_USE_OPTIMIZED_NORMALIZATION - switch (Submodel->m_normalizenormals) - { - case TSubModel::normalize: - { - ::glEnable(GL_NORMALIZE); - break; - } - case TSubModel::rescale: - { - ::glEnable(GL_RESCALE_NORMAL); - break; - } - default: - { - break; - } - } -#else -#endif // material configuration: + // transparency hack + if (Submodel->fVisible < 1.0f) + setup_drawing(true); + // textures... if (Submodel->m_material < 0) { // zmienialne skóry @@ -2183,28 +2177,11 @@ void opengl_renderer::Render(TSubModel *Submodel) // main draw call draw(Submodel->m_geometry); + // post-draw reset model_ubs.emission = 0.0f; + if (Submodel->fVisible < 1.0f) + setup_drawing(false); -#ifdef EU07_USE_OPTIMIZED_NORMALIZATION - switch (Submodel->m_normalizenormals) - { - case TSubModel::normalize: - { - ::glDisable(GL_NORMALIZE); - break; - } - case TSubModel::rescale: - { - ::glDisable(GL_RESCALE_NORMAL); - break; - } - default: - { - break; - } - } -#else -#endif break; } case rendermode::shadows: @@ -2264,34 +2241,53 @@ void opengl_renderer::Render(TSubModel *Submodel) float lightlevel = 1.f; // TODO, TBD: parameter to control light strength // view angle attenuation float const anglefactor = clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f); - // distance attenuation - // we're capping how much effect the distance attenuation can have, otherwise the lights get too tiny at regular distances - float const distancefactor = std::max(0.5f, (Submodel->fSquareMaxDist - TSubModel::fSquareDist) / Submodel->fSquareMaxDist); - float const precipitationfactor = std::max(1.f, Global.Overcast - 1.f); + lightlevel *= anglefactor; + float const precipitationfactor{interpolate(1.f, 0.25f, clamp(Global.Overcast * 0.75f - 0.5f, 0.f, 1.f))}; + lightlevel *= precipitationfactor; if (lightlevel > 0.f) { // material configuration: + // distance attenuation. NOTE: since it's fixed pipeline with built-in gamma correction we're using linear attenuation + // we're capping how much effect the distance attenuation can have, otherwise the lights get too tiny at regular distances + float const distancefactor{std::max(0.5f, (Submodel->fSquareMaxDist - TSubModel::fSquareDist) / Submodel->fSquareMaxDist)}; + auto const pointsize{std::max(3.f, 5.f * distancefactor * anglefactor)}; ::glEnable(GL_BLEND); ::glPushMatrix(); ::glLoadIdentity(); ::glTranslatef(lightcenter.x, lightcenter.y, lightcenter.z); // początek układu zostaje bez zmian - /* - setup_shadow_color( colors::white ); - */ - glPointSize(std::max(3.f, 5.f * distancefactor * anglefactor) * 2.0f); + + // limit impact of dense fog on the lights + model_ubs.fog_density = 1.0f / std::min(Global.fFogEnd, m_fogrange * 2); // main draw call - model_ubs.param[0] = glm::vec4(glm::vec3(Submodel->f4Diffuse), 0.0f); - model_ubs.emission = std::min(1.f, lightlevel * anglefactor * precipitationfactor); + model_ubs.emission = std::min(1.f, lightlevel * anglefactor * precipitationfactor); + m_freespot_shader->bind(); + if (Global.Overcast > 1.0f) + { + // fake fog halo + float const fogfactor{interpolate(2.f, 1.f, clamp(Global.fFogEnd / 2000, 0.f, 1.f)) * std::max(1.f, Global.Overcast)}; + glPointSize(pointsize * fogfactor * 2.0f); + model_ubs.param[0] = glm::vec4(glm::vec3(Submodel->f4Diffuse), + Submodel->fVisible * std::min(1.f, lightlevel) * 0.5f); + + glDepthMask(GL_FALSE); + draw(Submodel->m_geometry); + glDepthMask(GL_TRUE); + } + glPointSize(pointsize * 2.0f); + model_ubs.param[0] = glm::vec4(glm::vec3(Submodel->f4Diffuse), + Submodel->fVisible * std::min(1.f, lightlevel)); + draw(Submodel->m_geometry); // post-draw reset model_ubs.emission = 0.0f; + model_ubs.fog_density = 1.0f / m_fogrange; glDisable(GL_BLEND); @@ -2353,8 +2349,7 @@ void opengl_renderer::Render(TSubModel *Submodel) void opengl_renderer::Render(TTrack *Track) { - - if ((Track->m_material1 == 0) && (Track->m_material2 == 0)) + if ((Track->m_material1 == 0) && (Track->m_material2 == 0) && (Track->eType != tt_Switch || Track->SwitchExtension->m_material3 == 0)) { return; } @@ -2382,6 +2377,11 @@ void opengl_renderer::Render(TTrack *Track) Bind_Material(Track->m_material2); draw(std::begin(Track->Geometry2), std::end(Track->Geometry2)); } + if (Track->eType == tt_Switch && Track->SwitchExtension->m_material3 != 0) + { + Bind_Material(Track->SwitchExtension->m_material3); + draw(Track->SwitchExtension->Geometry3); + } setup_environment_light(); break; } @@ -2399,6 +2399,8 @@ void opengl_renderer::Render(TTrack *Track) draw(std::begin(Track->Geometry1), std::end(Track->Geometry1)); draw(std::begin(Track->Geometry2), std::end(Track->Geometry2)); + if (Track->eType == tt_Switch) + draw(Track->SwitchExtension->Geometry3); break; } case rendermode::pickcontrols: @@ -2428,6 +2430,7 @@ void opengl_renderer::Render(scene::basic_cell::path_sequence::const_iterator Fi } } + // TODO: render auto generated trackbeds together with regular trackbeds in pass 1, and all rails in pass 2 // first pass, material 1 for (auto first{First}; first != Last; ++first) { @@ -2486,7 +2489,6 @@ void opengl_renderer::Render(scene::basic_cell::path_sequence::const_iterator Fi // second pass, material 2 for (auto first{First}; first != Last; ++first) { - auto const track{*first}; if (track->m_material2 == 0) @@ -2535,6 +2537,64 @@ void opengl_renderer::Render(scene::basic_cell::path_sequence::const_iterator Fi } } } + + // third pass, material 3 + for (auto first{First}; first != Last; ++first) + { + + auto const track{*first}; + + if (track->eType != tt_Switch) + { + continue; + } + if (track->SwitchExtension->m_material3 == 0) + { + continue; + } + if (false == track->m_visible) + { + continue; + } + + switch (m_renderpass.draw_mode) + { + case rendermode::color: + case rendermode::reflections: + { + if (track->eEnvironment != e_flat) + { + setup_environment_light(track->eEnvironment); + } + Bind_Material(track->SwitchExtension->m_material3); + draw(track->SwitchExtension->Geometry3); + if (track->eEnvironment != e_flat) + { + // restore default lighting + setup_environment_light(); + } + break; + } + case rendermode::shadows: + { + if ((std::abs(track->fTexHeight1) < 0.35f) || ((track->iCategoryFlag == 1) && (track->eType != tt_Normal))) + { + // shadows are only calculated for high enough trackbeds + continue; + } + Bind_Material_Shadow(track->SwitchExtension->m_material3); + draw(track->SwitchExtension->Geometry3); + break; + } + case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach + case rendermode::pickcontrols: + default: + { + break; + } + } + } + // post-render reset switch (m_renderpass.draw_mode) { @@ -2620,7 +2680,7 @@ void opengl_renderer::Render_precipitation() model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); model_ubs.param[0] = interpolate(0.5f * (Global.DayLight.diffuse + Global.DayLight.ambient), colors::white, 0.5f * clamp(Global.fLuminance, 0.f, 1.f)); - model_ubs.param[1].x = simulation::Environment.m_precipitation.get_textureoffset(); + model_ubs.param[1].x = simulation::Environment.m_precipitation.get_textureoffset(); model_ubo->update(model_ubs); // momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside' @@ -2794,7 +2854,7 @@ void opengl_renderer::Render_Alpha(TTraction *Traction) auto const distance{static_cast(std::sqrt(distancesquared))}; auto const linealpha = 20.f * Traction->WireThickness / std::max(0.5f * Traction->radius() + 1.f, distance - (0.5f * Traction->radius())); if (m_widelines_supported) - glLineWidth(clamp(0.5f * linealpha + Traction->WireThickness * Traction->radius() / 1000.f, 1.f, 1.5f)); + glLineWidth(clamp(0.5f * linealpha + Traction->WireThickness * Traction->radius() / 1000.f, 1.f, 1.75f)); // render @@ -3014,29 +3074,6 @@ void opengl_renderer::Render_Alpha(TSubModel *Submodel) { case rendermode::color: { - -// NOTE: code disabled as normalization marking doesn't take into account scaling propagation down hierarchy chains -// for the time being we'll do with enforced worst-case scaling method, when speculars are enabled -#ifdef EU07_USE_OPTIMIZED_NORMALIZATION - switch (Submodel->m_normalizenormals) - { - case TSubModel::normalize: - { - ::glEnable(GL_NORMALIZE); - break; - } - case TSubModel::rescale: - { - ::glEnable(GL_RESCALE_NORMAL); - break; - } - default: - { - break; - } - } -#else -#endif // material configuration: // textures... if (Submodel->m_material < 0) @@ -3058,27 +3095,6 @@ void opengl_renderer::Render_Alpha(TSubModel *Submodel) draw(Submodel->m_geometry); model_ubs.emission = 0.0f; - -#ifdef EU07_USE_OPTIMIZED_NORMALIZATION - switch (Submodel->m_normalizenormals) - { - case TSubModel::normalize: - { - ::glDisable(GL_NORMALIZE); - break; - } - case TSubModel::rescale: - { - ::glDisable(GL_RESCALE_NORMAL); - break; - } - default: - { - break; - } - } -#else -#endif break; } case rendermode::cabshadows: @@ -3112,16 +3128,17 @@ void opengl_renderer::Render_Alpha(TSubModel *Submodel) static_cast(TSubModel::fSquareDist / Submodel->fSquareMaxDist)); // pozycja punktu świecącego względem kamery Submodel->fCosViewAngle = glm::dot(glm::normalize(modelview * glm::vec4(0.f, 0.f, -1.f, 1.f) - lightcenter), glm::normalize(-lightcenter)); - float glarelevel = 0.6f; // luminosity at night is at level of ~0.1, so the overall resulting transparency in clear conditions is ~0.5 at full 'brightness' if (Submodel->fCosViewAngle > Submodel->fCosFalloffAngle) { // only bother if the viewer is inside the visibility cone - auto glarelevel{clamp(0.6f - Global.fLuminance // reduce the glare in bright daylight - + std::max(0.f, Global.Overcast - 1.f), // increase the glare in rainy/foggy conditions - 0.f, 1.f)}; + // luminosity at night is at level of ~0.1, so the overall resulting transparency in clear conditions is ~0.5 at full 'brightness' + auto glarelevel{clamp(std::max(0.6f - Global.fLuminance, // reduce the glare in bright daylight + Global.Overcast - 1.f), // ensure some glare in rainy/foggy conditions + 0.f, 1.f)}; + // view angle attenuation + float const anglefactor{clamp((Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle), 0.f, 1.f)}; - // scale it down based on view angle - glarelevel *= (Submodel->fCosViewAngle - Submodel->fCosFalloffAngle) / (1.0f - Submodel->fCosFalloffAngle); + glarelevel *= anglefactor; if (glarelevel > 0.0f) { @@ -3135,7 +3152,7 @@ void opengl_renderer::Render_Alpha(TSubModel *Submodel) m_billboard_shader->bind(); Bind_Texture(0, m_glaretexture); - model_ubs.param[0] = glm::vec4(glm::vec3(Submodel->f4Diffuse), glarelevel); + model_ubs.param[0] = glm::vec4(glm::vec3(Submodel->f4Diffuse), Submodel->fVisible * glarelevel); // main draw call draw(m_billboardgeometry); @@ -3463,12 +3480,13 @@ void opengl_renderer::Update_Lights(light_array &Lights) light_ubs.fog_color = Global.FogColor; if (Global.fFogEnd > 0) { - auto const adjustedfogrange{Global.fFogEnd / std::max(1.f, Global.Overcast * 2.f)}; - light_ubs.fog_density = 1.0f / adjustedfogrange; + m_fogrange = Global.fFogEnd / std::max(1.f, Global.Overcast * 2.f); + model_ubs.fog_density = 1.0f / m_fogrange; } else - light_ubs.fog_density = 0.0f; + model_ubs.fog_density = 0.0f; + model_ubo->update(model_ubs); light_ubo->update(light_ubs); } diff --git a/renderer.h b/renderer.h index 6ace7102..3e9dc773 100644 --- a/renderer.h +++ b/renderer.h @@ -311,6 +311,8 @@ class opengl_renderer float m_specularopaquescalefactor{1.f}; float m_speculartranslucentscalefactor{1.f}; + float m_fogrange = 2000.0f; + renderpass_config m_renderpass; // parameters for current render pass section_sequence m_sectionqueue; // list of sections in current render pass cell_sequence m_cellqueue; diff --git a/scene.cpp b/scene.cpp index cb9f1ce1..e738f28a 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1174,9 +1174,31 @@ basic_region::RadioStop( glm::dvec3 const &Location ) { } } +std::vector switchtrackbedtextures { + "rozkrz8r150-1pods-new", + "rozkrz8r150-2pods-new", + "rozkrz34r150-tpbps-new2", + "rozkrz34r150-tpd1", + "rkpd34r190-tpd1", + "rkpd34r190-tpd2", + "rkpd34r190-tpd-oil2", + "zwr41r500", + "zwrot-tpd-oil1", + "zwrot34r300pods-new" }; + void basic_region::insert( shape_node Shape, scratch_data &Scratchpad, bool const Transform ) { + if( Global.CreateSwitchTrackbeds ) { + + auto const materialname{ GfxRenderer.Material( Shape.data().material ).name }; + for( auto const &switchtrackbedtexture : switchtrackbedtextures ) { + if( materialname.find( switchtrackbedtexture ) != std::string::npos ) { + // geometry with blacklisted texture, part of old switch trackbed; ignore it + return; + } + } + } // shape might need to be split into smaller pieces, so we create list of nodes instead of just single one // using deque so we can do single pass iterating and addding generated pieces without invalidating anything std::deque shapes { Shape }; diff --git a/shaders/freespot.frag b/shaders/freespot.frag index 1bf4395f..320261a2 100644 --- a/shaders/freespot.frag +++ b/shaders/freespot.frag @@ -12,7 +12,7 @@ void main() float dist2 = abs(x * x + y * y); if (dist2 > 0.5f * 0.5f) discard; - gl_FragData[0] = vec4(param[0].rgb * emission, 1.0f); + gl_FragData[0] = vec4(param[0].rgb * emission, param[0].a); #if MOTIONBLUR_ENABLED { vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; diff --git a/shaders/mat_colored.frag b/shaders/mat_colored.frag index 22699c88..4db2475a 100644 --- a/shaders/mat_colored.frag +++ b/shaders/mat_colored.frag @@ -61,7 +61,7 @@ void main() result += light.color * (part.x * param[1].x + part.y * param[1].y); } - gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a); + gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a * alpha_mult); #if MOTIONBLUR_ENABLED { vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;; diff --git a/shaders/mat_default.frag b/shaders/mat_default.frag index d330df76..5956cbd2 100644 --- a/shaders/mat_default.frag +++ b/shaders/mat_default.frag @@ -67,14 +67,14 @@ void main() result += light.color * (part.x * param[1].x + part.y * param[1].y); } - gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a); + gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a * alpha_mult); #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;; - gl_FragData[1] = vec4(a - b, 0.0f, tex_color.a); + gl_FragData[1] = vec4(a - b, 0.0f, tex_color.a * alpha_mult); } #endif } diff --git a/shaders/mat_normalmap.frag b/shaders/mat_normalmap.frag index 4d3316df..3c517177 100644 --- a/shaders/mat_normalmap.frag +++ b/shaders/mat_normalmap.frag @@ -72,7 +72,7 @@ void main() result += light.color * (part.x * param[1].x + part.y * param[1].y); } - gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a); + gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a * alpha_mult); #if MOTIONBLUR_ENABLED { diff --git a/shaders/mat_reflmap.frag b/shaders/mat_reflmap.frag index c32fc014..92cea921 100644 --- a/shaders/mat_reflmap.frag +++ b/shaders/mat_reflmap.frag @@ -70,7 +70,7 @@ void main() result += light.color * (part.x * param[1].x + part.y * param[1].y); } - gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a); + gl_FragData[0] = vec4(apply_fog(result * tex_color.rgb), tex_color.a * alpha_mult); #if MOTIONBLUR_ENABLED { diff --git a/shaders/precipitation.frag b/shaders/precipitation.frag index 7cdafa33..b12d057d 100644 --- a/shaders/precipitation.frag +++ b/shaders/precipitation.frag @@ -7,11 +7,19 @@ uniform sampler2D tex1; #include +in vec4 f_clip_pos; +in vec4 f_clip_future_pos; + void main() { vec4 tex_color = texture(tex1, vec2(f_coord.x, f_coord.y + param[1].x)); gl_FragData[0] = tex_color * param[0]; #if MOTIONBLUR_ENABLED - gl_FragData[1] = vec4(0.0f); + { + 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; + + gl_FragData[1] = vec4(a - b, 0.0f, tex_color.a * alpha_mult); + } #endif } diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index ae145348..9272c669 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -165,7 +165,15 @@ state_serializer::deserialize_atmo( cParser &Input, scene::scratch_data &Scratch std::string token { Input.getToken() }; if( token != "endatmo" ) { // optional overcast parameter - Global.Overcast = clamp( std::stof( token ), 0.f, 2.f ); + Global.Overcast = std::stof( token ); + if( Global.Overcast < 0.f ) { + // negative overcast means random value in range 0-abs(specified range) + Global.Overcast = + Random( + clamp( + std::abs( Global.Overcast ), + 0.f, 2.f ) ); + } // overcast drives weather so do a calculation here // NOTE: ugly, clean it up when we're done with world refactoring simulation::Environment.compute_weather(); @@ -436,7 +444,16 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch || ( nodedata.type == "triangle_strip" ) || ( nodedata.type == "triangle_fan" ) ) { - if( false == Scratchpad.binary.terrain ) { + auto const skip { + // all shapes will be loaded from the binary version of the file + ( true == Scratchpad.binary.terrain ) + // crude way to detect fixed switch trackbed geometry + || ( ( true == Global.CreateSwitchTrackbeds ) + && ( Input.Name().size() >= 15 ) + && ( Input.Name().substr( 0, 11 ) == "scenery/zwr" ) + && ( Input.Name().substr( Input.Name().size() - 4 ) == ".inc" ) ) }; + + if( false == skip ) { simulation::Region->insert( scene::shape_node().import( @@ -445,7 +462,6 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch true ); } else { - // all shapes were already loaded from the binary version of the file skip_until( Input, "endtri" ); } } diff --git a/translation.cpp b/translation.cpp index 966b0144..1cd4d310 100644 --- a/translation.cpp +++ b/translation.cpp @@ -54,7 +54,7 @@ init() { "Devices: %c%c%c%c%c%c%c%c%c%c%c%c%c%c%s%s\nPower transfers: %.0f@%.0f%s%s%s%.0f@%.0f", " radio: ", " oil pressure: ", - "Controllers:\n master: %d(%d), secondary: %s\nEngine output: %.1f, current: %.0f\nRevolutions:\n engine: %.0f, motors: %.0f, ventilators: %.0f, fans: %.0f+%.0f", + "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", @@ -97,6 +97,9 @@ init() { "water circuits link", "fuel pump", "oil pump", + "motor blowers A", + "motor blowers B", + "all motor blowers", "line breaker", "line breaker", "alerter", @@ -248,6 +251,9 @@ init() { u8"zawór połaczenia obiegow wody", u8"pompa paliwa", u8"pompa oleju", + u8"wentylatory silników trakcyjnych A", + u8"wentylatory silników trakcyjnych B", + u8"wszystkie wentylatory silników trakcyjnych", u8"wyłącznik szybki", u8"wyłącznik szybki", u8"czuwak", @@ -354,6 +360,9 @@ init() { "watercircuitslink_sw:", "fuelpump_sw:", "oilpump_sw:", + "motorblowersfront_sw:", + "motorblowersrear_sw:", + "motorblowersalloff_sw:", "main_off_bt:", "main_on_bt:", "security_reset_bt:", diff --git a/translation.h b/translation.h index 64bf08d9..5e58540f 100644 --- a/translation.h +++ b/translation.h @@ -86,6 +86,9 @@ enum string { cab_watercircuitslink_sw, cab_fuelpump_sw, cab_oilpump_sw, + cab_motorblowersfront_sw, + cab_motorblowersrear_sw, + cab_motorblowersalloff_sw, cab_main_off_bt, cab_main_on_bt, cab_security_reset_bt, diff --git a/uilayer.cpp b/uilayer.cpp index 4072111a..f2df3a93 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -19,7 +19,11 @@ http://mozilla.org/MPL/2.0/. #include "application.h" #include "imgui/imgui_impl_glfw.h" +#ifdef EU07_USEIMGUIIMPLOPENGL2 +#include "imgui/imgui_impl_opengl2.h" +#else #include "imgui/imgui_impl_opengl3.h" +#endif GLFWwindow *ui_layer::m_window{nullptr}; ImGuiIO *ui_layer::m_imguiio{nullptr}; @@ -129,11 +133,16 @@ bool ui_layer::init(GLFWwindow *Window) if (Global.map_enabled) m_map = std::make_unique(); - ImGui_ImplGlfw_InitForOpenGL(m_window); - ImGui_ImplOpenGL3_Init("#version 130"); ImGui::StyleColorsClassic(); - + ImGui_ImplGlfw_InitForOpenGL(m_window); +#ifdef EU07_USEIMGUIIMPLOPENGL2 + ImGui_ImplOpenGL2_Init(); + ImGui_ImplOpenGL2_NewFrame(); +#else + ImGui_ImplOpenGL3_Init("#version 130"); ImGui_ImplOpenGL3_NewFrame(); +#endif + ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); @@ -144,7 +153,11 @@ void ui_layer::shutdown() { ImGui::EndFrame(); +#ifdef EU07_USEIMGUIIMPLOPENGL2 + ImGui_ImplOpenGL2_Shutdown(); +#else ImGui_ImplOpenGL3_Shutdown(); +#endif ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); } @@ -229,9 +242,14 @@ void ui_layer::render() ImGui::Render(); Timer::subsystem.gfx_gui.stop(); +#ifdef EU07_USEIMGUIIMPLOPENGL2 + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + ImGui_ImplOpenGL2_NewFrame(); +#else ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - ImGui_ImplOpenGL3_NewFrame(); +#endif + ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); } diff --git a/version.h b/version.h index ced4b6ea..233051a6 100644 --- a/version.h +++ b/version.h @@ -1 +1 @@ -#define VERSION_INFO "M7 (GL3) 10.10.2018, based master-c7ef111, tmj-913541b" +#define VERSION_INFO "M7 (GL3) 10.10.2018, based on master-52c8b40, tmj-8a904f4"