diff --git a/DynObj.cpp b/DynObj.cpp index 092c1270..6095d838 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -989,12 +989,6 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) } // ABu 29.01.05 koniec przeklejenia ************************************* -double ABuAcos(const vector3 &calc_temp) -{ // Odpowiednik funkcji Arccos, bo cos - // mi tam nie dzialalo. - return atan2(-calc_temp.x, calc_temp.z); // Ra: tak prościej -} - TDynamicObject * TDynamicObject::ABuFindNearestObject(TTrack *Track, TDynamicObject *MyPointer, int &CouplNr) { // zwraca wskaznik do obiektu znajdujacego sie na torze (Track), którego sprzęg jest najblizszy kamerze @@ -4056,8 +4050,9 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, std::size_t i = asModel.find( ',' ); if ( i != std::string::npos ) { // Ra 2015-01: może szukać przecinka w nazwie modelu, a po przecinku była by liczba tekstur? - if (i < asModel.length()) - m_materialdata.multi_textures = asModel[i + 1] - '0'; + if( i < asModel.length() ) { + m_materialdata.multi_textures = asModel[ i + 1 ] - '0'; + } m_materialdata.multi_textures = clamp( m_materialdata.multi_textures, 0, 1 ); // na razie ustawiamy na 1 } asModel = BaseDir + asModel; // McZapkie 2002-07-20: dynamics maja swoje @@ -4092,7 +4087,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, int skinindex = 0; do { texture_handle texture = GfxRenderer.GetTextureId( Global::asCurrentTexturePath + ReplacableSkin + "," + std::to_string( skinindex + 1 ), "", Global::iDynamicFiltering, true ); - if( false == GfxRenderer.Texture( texture ).is_ready ) { + if( texture == NULL ) { break; } m_materialdata.replacable_skins[ skinindex + 1 ] = texture; @@ -4105,61 +4100,72 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, } } } - else + else { m_materialdata.replacable_skins[ 1 ] = GfxRenderer.GetTextureId( Global::asCurrentTexturePath + ReplacableSkin, "", Global::iDynamicFiltering ); - if( GfxRenderer.Texture( m_materialdata.replacable_skins[ 1 ] ).has_alpha ) - m_materialdata.textures_alpha = 0x31310031; // tekstura -1 z kanałem alfa - nie renderować w cyklu nieprzezroczystych - else - m_materialdata.textures_alpha = 0x30300030; // wszystkie tekstury nieprzezroczyste - nie renderować w cyklu przezroczystych - if( m_materialdata.replacable_skins[ 2 ] ) - if( GfxRenderer.Texture( m_materialdata.replacable_skins[ 2 ] ).has_alpha ) - m_materialdata.textures_alpha |= 0x02020002; // tekstura -2 z kanałem alfa - nie renderować w cyklu nieprzezroczystych - if( m_materialdata.replacable_skins[ 3 ] ) - if( GfxRenderer.Texture( m_materialdata.replacable_skins[ 3 ] ).has_alpha ) - m_materialdata.textures_alpha |= 0x04040004; // tekstura -3 z kanałem alfa - nie renderować w cyklu nieprzezroczystych - if( m_materialdata.replacable_skins[ 4 ] ) - if( GfxRenderer.Texture( m_materialdata.replacable_skins[ 4 ] ).has_alpha ) - m_materialdata.textures_alpha |= 0x08080008; // tekstura -4 z kanałem alfa - nie renderować w cyklu nieprzezroczystych + } + if( GfxRenderer.Texture( m_materialdata.replacable_skins[ 1 ] ).has_alpha ) { + // tekstura -1 z kanałem alfa - nie renderować w cyklu nieprzezroczystych + m_materialdata.textures_alpha = 0x31310031; + } + else { + // wszystkie tekstury nieprzezroczyste - nie renderować w cyklu przezroczystych + m_materialdata.textures_alpha = 0x30300030; + } + + if( ( m_materialdata.replacable_skins[ 2 ] ) + && ( GfxRenderer.Texture( m_materialdata.replacable_skins[ 2 ] ).has_alpha ) ) { + // tekstura -2 z kanałem alfa - nie renderować w cyklu nieprzezroczystych + m_materialdata.textures_alpha |= 0x02020002; + } + if( ( m_materialdata.replacable_skins[ 3 ] ) + && ( GfxRenderer.Texture( m_materialdata.replacable_skins[ 3 ] ).has_alpha ) ) { + // tekstura -3 z kanałem alfa - nie renderować w cyklu nieprzezroczystych + m_materialdata.textures_alpha |= 0x04040004; + } + if( ( m_materialdata.replacable_skins[ 4 ] ) + && ( GfxRenderer.Texture( m_materialdata.replacable_skins[ 4 ] ).has_alpha ) ) { + // tekstura -4 z kanałem alfa - nie renderować w cyklu nieprzezroczystych + m_materialdata.textures_alpha |= 0x08080008; + } } - if (!MoverParameters->LoadAccepted.empty()) - // if (MoverParameters->LoadAccepted!=AnsiString("")); // && - // MoverParameters->LoadType!=AnsiString("passengers")) - if (MoverParameters->EnginePowerSource.SourceType == CurrentCollector) - { // wartość niby "pantstate" - nazwa dla formalności, ważna jest ilość - if (MoverParameters->Load == 1) - MoverParameters->PantFront(true); - else if (MoverParameters->Load == 2) - MoverParameters->PantRear(true); - else if (MoverParameters->Load == 3) - { - MoverParameters->PantFront(true); - MoverParameters->PantRear(true); + if( !MoverParameters->LoadAccepted.empty() ) { + + if( MoverParameters->EnginePowerSource.SourceType == CurrentCollector ) { + // wartość niby "pantstate" - nazwa dla formalności, ważna jest ilość + if( MoverParameters->Load == 1 ) { + MoverParameters->PantFront( true ); } - else if (MoverParameters->Load == 4) - MoverParameters->DoubleTr = -1; - else if (MoverParameters->Load == 5) - { - MoverParameters->DoubleTr = -1; - MoverParameters->PantRear(true); + else if( MoverParameters->Load == 2 ) { + MoverParameters->PantRear( true ); } - else if (MoverParameters->Load == 6) - { - MoverParameters->DoubleTr = -1; - MoverParameters->PantFront(true); + else if( MoverParameters->Load == 3 ) { + MoverParameters->PantFront( true ); + MoverParameters->PantRear( true ); } - else if (MoverParameters->Load == 7) - { + else if( MoverParameters->Load == 4 ) { MoverParameters->DoubleTr = -1; - MoverParameters->PantFront(true); - MoverParameters->PantRear(true); + } + else if( MoverParameters->Load == 5 ) { + MoverParameters->DoubleTr = -1; + MoverParameters->PantRear( true ); + } + else if( MoverParameters->Load == 6 ) { + MoverParameters->DoubleTr = -1; + MoverParameters->PantFront( true ); + } + else if( MoverParameters->Load == 7 ) { + MoverParameters->DoubleTr = -1; + MoverParameters->PantFront( true ); + MoverParameters->PantRear( true ); } } - else // Ra: tu wczytywanie modelu ładunku jest w porządku - { + else { + // Ra: tu wczytywanie modelu ładunku jest w porządku if( false == asLoadName.empty() ) { mdLoad = TModelsManager::GetModel( asLoadName, true ); // ladunek } } + } Global::asCurrentTexturePath = szTexturePath; // z powrotem defaultowa sciezka do tekstur do { token = ""; @@ -4198,11 +4204,9 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, } // WriteLog("Total animations: "+AnsiString(iAnimations)); } -/* - if( nullptr == pAnimations ) -*/ - if( true == pAnimations.empty() ) - { // Ra: tworzenie tabeli animacji, jeśli jeszcze nie było + + if( true == pAnimations.empty() ) { + // Ra: tworzenie tabeli animacji, jeśli jeszcze nie było /* // disabled as default animation amounts are no longer supported if( !iAnimations ) { @@ -4217,9 +4221,6 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, iAnimType[ANIM_PANTS]=0; } */ -/* - pAnimations = new TAnim[iAnimations]; -*/ pAnimations.resize( iAnimations ); int i, j, k = 0, sm = 0; for (j = 0; j < ANIM_TYPES; ++j) @@ -4229,9 +4230,6 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, if (!pants) if (iAnimType[ANIM_PANTS]) // o ile jakieś pantografy są (a domyślnie są) pants = &pAnimations[k]; // zapamiętanie na potrzeby wyszukania submodeli -/* - pants = pAnimations + k; // zapamiętanie na potrzeby wyszukania submodeli -*/ pAnimations[k].iShift = sm; // przesunięcie do przydzielenia wskaźnika sm += pAnimations[k++].TypeSet(j); // ustawienie typu animacji i zliczanie tablicowanych submodeli } @@ -4281,8 +4279,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, if (pAnimations[i].smAnimated) { //++iAnimatedAxles; pAnimations[i].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu -/* pAnimations[i].yUpdate = UpdateAxle; // animacja osi -*/ pAnimations[ i ].yUpdate = std::bind( &TDynamicObject::UpdateAxle, this, std::placeholders::_1 ); + pAnimations[i].yUpdate = std::bind( &TDynamicObject::UpdateAxle, this, std::placeholders::_1 ); pAnimations[i].fMaxDist = 50 * MoverParameters->WheelDiameter; // nie kręcić w większej odległości pAnimations[i].fMaxDist *= pAnimations[i].fMaxDist * MoverParameters->WheelDiameter; // 50m do kwadratu, a średnica do trzeciej pAnimations[i].fMaxDist *= Global::fDistanceFactor; // współczynnik przeliczeniowy jakości ekranu @@ -4671,20 +4668,16 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, switch (MoverParameters->DoorOpenMethod) { // od razu zapinamy potrzebny typ animacji case 1: -/* pAnimations[i + j].yUpdate = UpdateDoorTranslate; -*/ pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorTranslate, this, std::placeholders::_1 ); + pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorTranslate, this, std::placeholders::_1 ); break; case 2: -/* pAnimations[i + j].yUpdate = UpdateDoorRotate; -*/ pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorRotate, this, std::placeholders::_1 ); + pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorRotate, this, std::placeholders::_1 ); break; case 3: -/* pAnimations[i + j].yUpdate = UpdateDoorFold; -*/ pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorFold, this, std::placeholders::_1 ); + pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorFold, this, std::placeholders::_1 ); break; // obrót 3 kolejnych submodeli case 4: -/* pAnimations[i + j].yUpdate = UpdateDoorPlug; -*/ pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorPlug, this, std::placeholders::_1 ); + pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorPlug, this, std::placeholders::_1 ); break; default: break; diff --git a/Gauge.cpp b/Gauge.cpp index ef0c1efa..f31138a5 100644 --- a/Gauge.cpp +++ b/Gauge.cpp @@ -150,16 +150,21 @@ double TGauge::GetValue() const { return ( fValue - fOffset ) / fScale; } -void TGauge::Update() -{ +void TGauge::Update() { + float dt = Timer::GetDeltaTime(); if( ( fFriction > 0 ) && ( dt < 0.5 * fFriction ) ) { // McZapkie-281102: zabezpieczenie przed oscylacjami dla dlugich czasow fValue += dt * ( fDesiredValue - fValue ) / fFriction; } - else + else { fValue = fDesiredValue; - if (SubModel) + } + if( std::abs( fDesiredValue - fValue ) <= 0.001 ) { + // close enough, we can stop updating the model + fValue = fDesiredValue; // set it exactly as requested just in case it matters + } + if( SubModel ) { // warunek na wszelki wypadek, gdyby się submodel nie podłączył TSubModel *sm; switch (eType) diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index bdc01fee..2a1b300b 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -113,17 +113,18 @@ double PFVd( double PH, double PL, double const S, double LIM, double const DP ) if (LIM < PH) { LIM = LIM + 1; - PH = PH + 1; // wyzsze cisnienie absolutne - PL = PL + 1; // nizsze cisnienie absolutne + PH = PH + 1.0; // wyzsze cisnienie absolutne + PL = PL + 1.0; // nizsze cisnienie absolutne + assert( PH != PL ); double sg = PL / PH; // bezwymiarowy stosunek cisnien - double FM = PH * 197 * S; // najwyzszy mozliwy przeplyw, wraz z kierunkiem + double FM = PH * 197.0 * S; // najwyzszy mozliwy przeplyw, wraz z kierunkiem if ((PH - LIM) < 0.1) FM = FM * (PH - LIM) / DP; // jesli jestesmy przy nastawieniu, to zawor sie przymyka if ((sg > 0.5)) // jesli ponizej stosunku krytycznego if ((PH - PL) < DPL) // niewielka roznica cisnien - return (PH - PL) / DPL * FM * 2 * std::sqrt((sg) * (1 - sg)); + return (PH - PL) / DPL * FM * 2.0 * std::sqrt((sg) * (1.0 - sg)); else - return FM * 2 * std::sqrt((sg) * (1 - sg)); + return FM * 2.0 * std::sqrt((sg) * (1.0 - sg)); else // powyzej stosunku krytycznego return FM; } @@ -150,7 +151,7 @@ void TReservoir::Flow(double dv) void TReservoir::Act() { - Vol = Vol + dVol; + Vol = std::max( 0.0, Vol + dVol ); dVol = 0; } @@ -2131,39 +2132,34 @@ double TFV4a::GetPF(double i_bcp, double PP, double HP, double dt, double ep) { static int const LBDelay = 100; - double LimPP; - double dpPipe; - double dpMainValve; - double ActFlowSpeed; - ep = PP; // SPKS!! - LimPP = Min0R(BPT[lround(i_bcp) + 2][1], HP); - ActFlowSpeed = BPT[lround(i_bcp) + 2][0]; + double LimPP = std::min(BPT[std::lround(i_bcp) + 2][1], HP); + double ActFlowSpeed = BPT[std::lround(i_bcp) + 2][0]; if ((i_bcp == i_bcpno)) LimPP = 2.9; - CP = CP + 20 * Min0R(abs(LimPP - CP), 0.05) * PR(CP, LimPP) * dt / 1; - RP = RP + 20 * Min0R(abs(ep - RP), 0.05) * PR(RP, ep) * dt / 2.5; + CP = CP + 20 * std::min(std::abs(LimPP - CP), 0.05) * PR(CP, LimPP) * dt / 1; + RP = RP + 20 * std::min(std::abs(ep - RP), 0.05) * PR(RP, ep) * dt / 2.5; LimPP = CP; - dpPipe = Min0R(HP, LimPP); + double dpPipe = std::min(HP, LimPP); - dpMainValve = PF(dpPipe, PP, ActFlowSpeed / LBDelay) * dt; + double dpMainValve = PF(dpPipe, PP, ActFlowSpeed / LBDelay) * dt; if ((CP > RP + 0.05)) - dpMainValve = PF(Min0R(CP + 0.1, HP), PP, 1.1 * ActFlowSpeed / LBDelay) * dt; + dpMainValve = PF(std::min(CP + 0.1, HP), PP, 1.1 * ActFlowSpeed / LBDelay) * dt; if ((CP < RP - 0.05)) dpMainValve = PF(CP - 0.1, PP, 1.1 * ActFlowSpeed / LBDelay) * dt; if (lround(i_bcp) == -1) { - CP = CP + 5 * Min0R(abs(LimPP - CP), 0.2) * PR(CP, LimPP) * dt / 2; + CP = CP + 5 * std::min(std::abs(LimPP - CP), 0.2) * PR(CP, LimPP) * dt / 2; if ((CP < RP + 0.03)) if ((TP < 5)) TP = TP + dt; // if(cp+0.03<5.4)then if ((RP + 0.03 < 5.4) || (CP + 0.03 < 5.4)) // fala - dpMainValve = PF(Min0R(HP, 17.1), PP, ActFlowSpeed / LBDelay) * dt; + dpMainValve = PF(std::min(HP, 17.1), PP, ActFlowSpeed / LBDelay) * dt; // dpMainValve:=20*Min0R(abs(ep-7.1),0.05)*PF(HP,pp,ActFlowSpeed/LBDelay)*dt; else { @@ -2183,9 +2179,9 @@ double TFV4a::GetPF(double i_bcp, double PP, double HP, double dt, double ep) TP = TP - dt / 12 / 2; } if ((CP > RP + 0.1) && (CP <= 5)) - dpMainValve = PF(Min0R(CP + 0.25, HP), PP, 2 * ActFlowSpeed / LBDelay) * dt; + dpMainValve = PF(std::min(CP + 0.25, HP), PP, 2 * ActFlowSpeed / LBDelay) * dt; else if (CP > 5) - dpMainValve = PF(Min0R(CP, HP), PP, 2 * ActFlowSpeed / LBDelay) * dt; + dpMainValve = PF(std::min(CP, HP), PP, 2 * ActFlowSpeed / LBDelay) * dt; else dpMainValve = PF(dpPipe, PP, ActFlowSpeed / LBDelay) * dt; } @@ -2208,8 +2204,8 @@ void TFV4a::Init(double Press) double TFV4aM::GetPF(double i_bcp, double PP, double HP, double dt, double ep) { - static int const LBDelay = 100; - static double const xpM = 0.3; // mnoznik membrany komory pod + int const LBDelay { 100 }; + double const xpM { 0.3 }; // mnoznik membrany komory pod ep = (PP / 2.0) * 1.5 + (ep / 2.0) * 0.5; // SPKS!! @@ -2293,13 +2289,10 @@ double TFV4aM::GetPF(double i_bcp, double PP, double HP, double dt, double ep) double const ActFlowSpeed = BPT[ std::lround( i_bcp ) + 2 ][ 0 ]; - double dpMainValve; - if( dpPipe > PP ) { - dpMainValve = -PFVa( HP, PP, ActFlowSpeed / LBDelay, dpPipe, 0.4 ); - } - else { - dpMainValve = PFVd( PP, 0, ActFlowSpeed / LBDelay, dpPipe, 0.4 ); - } + double dpMainValve = ( + dpPipe > PP ? + -PFVa( HP, PP, ActFlowSpeed / LBDelay, dpPipe, 0.4 ) : + PFVd( PP, 0, ActFlowSpeed / LBDelay, dpPipe, 0.4 ) ); if (EQ(i_bcp, -1)) { diff --git a/Model3d.cpp b/Model3d.cpp index 8313cc44..38345bb6 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -106,7 +106,7 @@ int TSubModel::SeekFaceNormal(std::vector const &Masks, int const // pętla po trójkątach, od trójkąta (f) if( Masks[ faceidx ] & Mask ) { // jeśli wspólna maska powierzchni - for( int vertexidx = 0; vertexidx < 2; ++vertexidx ) { + for( int vertexidx = 0; vertexidx < 3; ++vertexidx ) { if( Vertices[ 3 * faceidx + vertexidx ].position == Position ) { return 3 * faceidx + vertexidx; } @@ -374,11 +374,27 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic // transformation matrix fMatrix = new float4x4(); readMatrix(parser, *fMatrix); // wczytanie transform - if (!fMatrix->IdentityIs()) - iFlags |= 0x8000; // transform niejedynkowy - trzeba go przechować - if( std::abs( Det( *fMatrix ) - 1.0f ) > 0.01f ) { - ErrorLog( "Bad model: transformation matrix for sub-model \"" + pName + "\" imposes geometry scaling (factor: " + to_string( Det( *fMatrix ), 2 ) + ")" ); - m_normalizenormals = true; + if( !fMatrix->IdentityIs() ) { + iFlags |= 0x8000; // transform niejedynkowy - trzeba go przechować + // check the scaling + auto const matrix = glm::make_mat4( fMatrix->readArray() ); + glm::vec3 const scale{ + glm::length( glm::vec3( glm::column( matrix, 0 ) ) ), + glm::length( glm::vec3( glm::column( matrix, 1 ) ) ), + glm::length( glm::vec3( glm::column( matrix, 2 ) ) ) }; + if( ( std::abs( scale.x - 1.0f ) > 0.01 ) + || ( std::abs( scale.y - 1.0f ) > 0.01 ) + || ( std::abs( scale.z - 1.0f ) > 0.01 ) ) { + ErrorLog( + "Bad model: transformation matrix for sub-model \"" + pName + "\" imposes geometry scaling (factors: " + + to_string( scale.x, 2 ) + ", " + + to_string( scale.y, 2 ) + ", " + + to_string( scale.z, 2 ) + ")" ); + m_normalizenormals = ( + ( ( std::abs( scale.x - scale.y ) < 0.01f ) && ( std::abs( scale.y - scale.z ) < 0.01f ) ) ? + rescale : + normalize ); + } } if (eType < TP_ROTATOR) { // wczytywanie wierzchołków @@ -1539,7 +1555,7 @@ void TModel3d::deserialize(std::istream &s, size_t size, bool dynamic) auto normallength = glm::length2( vertex.normal ); if( ( false == submodel.m_normalizenormals ) && ( std::abs( normallength - 1.0f ) > 0.01f ) ) { - submodel.m_normalizenormals = true; + submodel.m_normalizenormals = TSubModel::normalize; // we don't know if uniform scaling would suffice WriteLog( "Bad model: non-unit normal vector(s) encountered during sub-model geometry deserialization" ); } } @@ -1695,13 +1711,26 @@ void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, iFlags &= ~0x0200; // wczytano z pliku binarnego (nie jest właścicielem tablic) - if( ( fMatrix != nullptr ) - && ( std::abs( Det( *fMatrix ) - 1.0f ) > 0.01f ) ) { - // check whether we need to enable normal vectors normalization for this submodel - ErrorLog( "Bad model: transformation matrix for sub-model \"" + pName + "\" imposes geometry scaling (factor: " + to_string( Det( *fMatrix ), 2 ) + ")" ); - m_normalizenormals = true; + if( fMatrix != nullptr ) { + auto const matrix = glm::make_mat4( fMatrix->readArray() ); + glm::vec3 const scale { + glm::length( glm::vec3( glm::column( matrix, 0 ) ) ), + glm::length( glm::vec3( glm::column( matrix, 1 ) ) ), + glm::length( glm::vec3( glm::column( matrix, 2 ) ) ) }; + if( ( std::abs( scale.x - 1.0f ) > 0.01 ) + || ( std::abs( scale.y - 1.0f ) > 0.01 ) + || ( std::abs( scale.z - 1.0f ) > 0.01 ) ) { + ErrorLog( + "Bad model: transformation matrix for sub-model \"" + pName + "\" imposes geometry scaling (factors: " + + to_string( scale.x, 2 ) + ", " + + to_string( scale.y, 2 ) + ", " + + to_string( scale.z, 2 ) + ")" ); + m_normalizenormals = ( + ( ( std::abs( scale.x - scale.y ) < 0.01f ) && ( std::abs( scale.y - scale.z ) < 0.01f ) ) ? + rescale : + normalize ); + } } - }; void TModel3d::LoadFromBinFile(std::string const &FileName, bool dynamic) diff --git a/Model3d.h b/Model3d.h index 9916091f..1aa313f4 100644 --- a/Model3d.h +++ b/Model3d.h @@ -58,6 +58,13 @@ class TSubModel friend class TModel3d; // temporary workaround. TODO: clean up class content/hierarchy friend class TDynamicObject; // temporary etc +public: + enum normalization { + none = 0, + rescale, + normalize + }; + private: int iNext{ NULL }; int iChild{ NULL }; @@ -96,7 +103,7 @@ private: f4Diffuse { 1.0f,1.0f,1.0f,1.0f }, f4Specular { 0.0f,0.0f,0.0f,1.0f }, f4Emision { 1.0f,1.0f,1.0f,1.0f }; - bool m_normalizenormals { false }; // indicates vectors need to be normalized due to scaling etc + normalization m_normalizenormals { normalization::none }; // indicates vectors need to be normalized due to scaling etc float fWireSize { 0.0f }; // nie używane, ale wczytywane float fSquareMaxDist { 10000.0f * 10000.0f }; float fSquareMinDist { 0.0f }; diff --git a/ResourceManager.cpp b/ResourceManager.cpp index 12d44b38..adbf4bf4 100644 --- a/ResourceManager.cpp +++ b/ResourceManager.cpp @@ -8,6 +8,8 @@ http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" + +/* #include "ResourceManager.h" #include "Logs.h" @@ -84,3 +86,4 @@ void ResourceManager::Sweep(double currentTime) _lastUpdate = currentTime; }; +*/ \ No newline at end of file diff --git a/ResourceManager.h b/ResourceManager.h index 93a73480..50e65600 100644 --- a/ResourceManager.h +++ b/ResourceManager.h @@ -7,14 +7,11 @@ obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef RESOURCEMANAGER_H -#define RESOURCEMANAGER_H 1 - +#pragma once +/* #include #include -#pragma hdrstop - class Resource { @@ -57,5 +54,63 @@ class ResourceManager static Resources _resources; }; +*/ -#endif +template +class garbage_collector { + +public: +// constructor: + garbage_collector( Container_ &Container, int const Secondstolive, int const Sweepsize, std::string const Resourcename = "resource" ) : + m_container( Container ), + m_unusedresourcetimetolive { std::chrono::seconds( Secondstolive ) }, + m_unusedresourcesweepsize( Sweepsize ), + m_resourcename( Resourcename ) + {} + +// methods: + // performs resource sweep. returns: number of released resources + int + sweep() { + m_resourcetimestamp = std::chrono::steady_clock::now(); + // garbage collection sweep is limited to a number of records per call, to reduce impact on framerate + auto const sweeplastindex = + std::min( + m_resourcesweepindex + m_unusedresourcesweepsize, + m_container.size() ); + auto const blanktimestamp { std::chrono::steady_clock::time_point() }; + int releasecount{ 0 }; + for( auto resourceindex = m_resourcesweepindex; resourceindex < sweeplastindex; ++resourceindex ) { + if( ( m_container[ resourceindex ].second != blanktimestamp ) + && ( m_resourcetimestamp - m_container[ resourceindex ].second > m_unusedresourcetimetolive ) ) { + + m_container[ resourceindex ].first->release(); + m_container[ resourceindex ].second = blanktimestamp; + ++releasecount; + } + } +/* + if( releasecount ) { + WriteLog( "Resource garbage sweep released " + std::to_string( releasecount ) + " " + ( releasecount == 1 ? m_resourcename : m_resourcename + "s" ) ); + } +*/ + m_resourcesweepindex = ( + m_resourcesweepindex + m_unusedresourcesweepsize >= m_container.size() ? + 0 : // if the next sweep chunk is beyond actual data, so start anew + m_resourcesweepindex + m_unusedresourcesweepsize ); + + return releasecount; } + + std::chrono::steady_clock::time_point + timestamp() const { + return m_resourcetimestamp; } + +private: +// members: + std::chrono::nanoseconds const m_unusedresourcetimetolive; + typename Container_::size_type const m_unusedresourcesweepsize; + std::string const m_resourcename; + typename Container_ &m_container; + typename Container_::size_type m_resourcesweepindex { 0 }; + std::chrono::steady_clock::time_point m_resourcetimestamp { std::chrono::steady_clock::now() }; +}; diff --git a/Texture.cpp b/Texture.cpp index fee02ad5..7ed9ffa3 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -24,10 +24,12 @@ http://mozilla.org/MPL/2.0/. #include "logs.h" #include "sn_utils.h" +#define EU07_DEFERRED_TEXTURE_UPLOAD + texture_manager::texture_manager() { // since index 0 is used to indicate no texture, we put a blank entry in the first texture slot - m_textures.emplace_back( opengl_texture() ); + m_textures.emplace_back( new opengl_texture(), std::chrono::steady_clock::time_point() ); } // loads texture data from specified file @@ -518,12 +520,12 @@ opengl_texture::bind() { return data_state; } -resource_state +bool opengl_texture::create() { if( data_state != resource_state::good ) { // don't bother until we have useful texture data - return data_state; + return false; } // TODO: consider creating and storing low-res version of the texture if it's ever unloaded from the gfx card, @@ -590,15 +592,44 @@ opengl_texture::create() { } } - data.resize( 0 ); // TBD, TODO: keep the texture data if we start doing some gpu data cleaning down the road + data.swap( std::vector() ); // TBD, TODO: keep the texture data if we start doing some gpu data cleaning down the road /* data_state = resource_state::none; */ - data_state = resource_state::good; + data_state = resource_state::none; is_ready = true; } - return data_state; + return true; +} + +// releases resources allocated on the opengl end, storing local copy if requested +void +opengl_texture::release( bool const Backup ) { + + if( id == -1 ) { return; } + + assert( is_ready ); + + if( true == Backup ) { + // query texture details needed to perform the backup... + ::glBindTexture( GL_TEXTURE_2D, id ); + ::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, (GLint *)&data_format ); + GLint datasize; + ::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint *)&datasize ); + data.resize( datasize ); + // ...fetch the data... + ::glGetCompressedTexImage( GL_TEXTURE_2D, 0, &data[ 0 ] ); + // ...and update texture object state + data_mapcount = 1; // we keep copy of only top mipmap level + data_state = resource_state::good; + } + // release opengl resources + ::glDeleteTextures( 1, &id ); + id = -1; + is_ready = false; + + return; } void @@ -773,8 +804,8 @@ texture_manager::create( std::string Filename, std::string const &Dir, int const return npos; } - opengl_texture texture; - texture.name = filename; + auto texture = new opengl_texture(); + texture->name = filename; if( ( Filter > 0 ) && ( Filter < 10 ) ) { // temporary. TODO, TBD: check how it's used and possibly get rid of it traits += std::to_string( ( Filter < 4 ? Filter + 4 : Filter ) ); @@ -783,9 +814,9 @@ texture_manager::create( std::string Filename, std::string const &Dir, int const // temporary code for legacy assets -- textures with names beginning with # are to be sharpened traits += '#'; } - texture.traits = traits; + texture->traits = traits; auto const textureindex = (texture_handle)m_textures.size(); - m_textures.emplace_back( texture ); + m_textures.emplace_back( texture, std::chrono::steady_clock::time_point() ); m_texturemappings.emplace( filename, textureindex ); WriteLog( "Created texture object for \"" + filename + "\"" ); @@ -796,7 +827,7 @@ texture_manager::create( std::string Filename, std::string const &Dir, int const #ifndef EU07_DEFERRED_TEXTURE_UPLOAD texture_manager::texture( textureindex ).create(); // texture creation binds a different texture, force a re-bind on next use - m_activetexture = 0; + m_activetexture = -1; #endif } @@ -806,7 +837,9 @@ texture_manager::create( std::string Filename, std::string const &Dir, int const void texture_manager::bind( texture_handle const Texture ) { - if( Texture == m_activetexture ) { + m_textures[ Texture ].second = m_garbagecollector.timestamp(); + + if( ( Texture != 0 ) && ( Texture == m_activetexture ) ) { // don't bind again what's already active return; } @@ -815,7 +848,7 @@ texture_manager::bind( texture_handle const Texture ) { #ifndef EU07_DEFERRED_TEXTURE_UPLOAD // NOTE: we could bind dedicated 'error' texture here if the id isn't valid ::glBindTexture( GL_TEXTURE_2D, texture(Texture).id ); - m_activetexture = Texture; + m_activetexture = Texture; #else if( texture( Texture ).bind() == resource_state::good ) { m_activetexture = Texture; @@ -838,10 +871,20 @@ void texture_manager::delete_textures() { for( auto const &texture : m_textures ) { // usunięcie wszyskich tekstur (bez usuwania struktury) - if( ( texture.id > 0 ) - && ( texture.id != -1 ) ) { - ::glDeleteTextures( 1, &texture.id ); + if( ( texture.first->id > 0 ) + && ( texture.first->id != -1 ) ) { + ::glDeleteTextures( 1, &(texture.first->id) ); } + delete texture.first; + } +} + +// performs a resource sweep +void +texture_manager::update() { + + if( m_garbagecollector.sweep() > 0 ) { + m_activetexture = -1; } } @@ -859,13 +902,13 @@ texture_manager::info() const { for( auto const& texture : m_textures ) { - totaltexturesize += texture.size; + totaltexturesize += texture.first->size; #ifdef EU07_DEFERRED_TEXTURE_UPLOAD - if( texture.is_ready ) { + if( texture.first->is_ready ) { ++readytexturecount; - readytexturesize += texture.size; + readytexturesize += texture.first->size; } #endif } diff --git a/Texture.h b/Texture.h index 17da9bca..69119271 100644 --- a/Texture.h +++ b/Texture.h @@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include "GL/glew.h" +#include "ResourceManager.h" enum class resource_state { none, @@ -27,12 +28,16 @@ struct opengl_texture { static DDPIXELFORMAT deserialize_ddpf(std::istream&); static DDSCAPS2 deserialize_ddscaps(std::istream&); - // methods - void load(); +// methods + void + load(); resource_state bind(); - resource_state + bool create(); + // releases resources allocated on the opengl end, storing local copy if requested + void + release( bool const Backup = true ); inline int width() const { @@ -41,7 +46,7 @@ struct opengl_texture { int height() const { return data_height; } - // members +// members GLuint id{ (GLuint)-1 }; // associated GL resource bool has_alpha{ false }; // indicates the texture has alpha channel bool is_ready{ false }; // indicates the texture was processed and is ready for use @@ -50,7 +55,7 @@ struct opengl_texture { std::size_t size{ 0 }; // size of the texture data, in kb private: - // methods +// methods void load_BMP(); void load_DDS(); void load_TEX(); @@ -58,7 +63,7 @@ private: void set_filtering(); void downsize( GLuint const Format ); - // members +// members std::vector data; // texture data resource_state data_state{ resource_state::none }; // current state of texture data int data_width{ 0 }, @@ -76,9 +81,6 @@ typedef int texture_handle; class texture_manager { -private: - typedef std::vector opengltexture_array; - public: texture_manager(); ~texture_manager() { delete_textures(); } @@ -88,14 +90,25 @@ public: void bind( texture_handle const Texture ); opengl_texture & - texture( texture_handle const Texture ) { return m_textures[ Texture ]; } + texture( texture_handle const Texture ) { return *(m_textures[ Texture ].first); } + // performs a resource sweep + void + update(); // debug performance string std::string info() const; private: +// types: + typedef std::pair< + opengl_texture *, + std::chrono::steady_clock::time_point > texturetimepoint_pair; + + typedef std::vector< texturetimepoint_pair > texturetimepointpair_sequence; + typedef std::unordered_map index_map; +// methods: // checks whether specified texture is in the texture bank. returns texture id, or npos. texture_handle find_in_databank( std::string const &Texturename ) const; @@ -105,10 +118,12 @@ private: void delete_textures(); - static const texture_handle npos{ 0 }; // should be -1, but the rest of the code uses -1 for something else - opengltexture_array m_textures; +// members: + texture_handle const npos { 0 }; // should be -1, but the rest of the code uses -1 for something else + texturetimepointpair_sequence m_textures; index_map m_texturemappings; - texture_handle m_activetexture{ 0 }; // last i.e. currently bound texture + garbage_collector m_garbagecollector { m_textures, 600, 60, "texture" }; + texture_handle m_activetexture { 0 }; // last i.e. currently bound texture }; // reduces provided data image to half of original size, using basic 2x2 average diff --git a/Train.cpp b/Train.cpp index d37ddb84..1bc9832d 100644 --- a/Train.cpp +++ b/Train.cpp @@ -1225,24 +1225,28 @@ void TTrain::OnCommand_pantographtogglefront( TTrain *Train, command_data const // sound feedback Train->play_sound( Train->dsbSwitch ); // visual feedback - if( Train->ggPantFrontButton.SubModel ) { - Train->ggPantFrontButton.UpdateValue( 1.0 ); - } - if( Train->ggPantFrontButtonOff.SubModel != nullptr ) { - // pantograph control can have two-button setup - Train->ggPantFrontButtonOff.UpdateValue( 0.0 ); - } + Train->ggPantFrontButton.UpdateValue( 1.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedButton.UpdateValue( 1.0 ); + // pantograph control can have two-button setup + Train->ggPantFrontButtonOff.UpdateValue( 0.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedDownButton.UpdateValue( 0.0 ); } } } else { // ...or turn off - if( ( Train->mvOccupied->PantSwitchType == "impulse" ) - && ( Train->ggPantFrontButtonOff.SubModel == nullptr ) ) { - // with impulse buttons we expect a dedicated switch to lower the pantograph, and if the cabin lacks it - // then another control has to be used (like pantographlowerall) - // TODO: we should have a way to define presense of cab controls without having to bind these to 3d submodels - return; + if( Train->mvOccupied->PantSwitchType == "impulse" ) { + if( ( Train->ggPantFrontButtonOff.SubModel == nullptr ) + && ( Train->ggPantSelectedDownButton.SubModel == nullptr ) ) { + // with impulse buttons we expect a dedicated switch to lower the pantograph, and if the cabin lacks it + // then another control has to be used (like pantographlowerall) + // TODO: we should have a way to define presense of cab controls without having to bind these to 3d submodels + return; + } } Train->mvControlled->PantFrontSP = false; @@ -1252,8 +1256,14 @@ void TTrain::OnCommand_pantographtogglefront( TTrain *Train, command_data const Train->play_sound( Train->dsbSwitch ); // visual feedback Train->ggPantFrontButton.UpdateValue( 0.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedButton.UpdateValue( 0.0 ); // pantograph control can have two-button setup Train->ggPantFrontButtonOff.UpdateValue( 1.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedDownButton.UpdateValue( 1.0 ); } } } @@ -1264,9 +1274,10 @@ void TTrain::OnCommand_pantographtogglefront( TTrain *Train, command_data const if( Train->ggPantFrontButton.GetValue() > 0.35 ) { Train->play_sound( Train->dsbSwitch ); } - if( Train->ggPantFrontButton.SubModel ) { - Train->ggPantFrontButton.UpdateValue( 0.0 ); - } + Train->ggPantFrontButton.UpdateValue( 0.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedButton.UpdateValue( 0.0 ); // also the switch off button, in cabs which have it if( Train->ggPantFrontButtonOff.GetValue() > 0.35 ) { Train->play_sound( Train->dsbSwitch ); @@ -1274,6 +1285,11 @@ void TTrain::OnCommand_pantographtogglefront( TTrain *Train, command_data const if( Train->ggPantFrontButtonOff.SubModel ) { Train->ggPantFrontButtonOff.UpdateValue( 0.0 ); } + if( Train->ggPantSelectedDownButton.SubModel ) { + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedDownButton.UpdateValue( 0.0 ); + } } } } @@ -1290,24 +1306,28 @@ void TTrain::OnCommand_pantographtogglerear( TTrain *Train, command_data const & // sound feedback Train->play_sound( Train->dsbSwitch ); // visual feedback - if( Train->ggPantRearButton.SubModel ) { - Train->ggPantRearButton.UpdateValue( 1.0 ); - } - if( Train->ggPantRearButtonOff.SubModel != nullptr ) { - // pantograph control can have two-button setup - Train->ggPantRearButtonOff.UpdateValue( 0.0 ); - } + Train->ggPantRearButton.UpdateValue( 1.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedButton.UpdateValue( 1.0 ); + // pantograph control can have two-button setup + Train->ggPantRearButtonOff.UpdateValue( 0.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedDownButton.UpdateValue( 0.0 ); } } } else { // ...or turn off - if( ( Train->mvOccupied->PantSwitchType == "impulse" ) - && ( Train->ggPantRearButtonOff.SubModel == nullptr ) ) { - // with impulse buttons we expect a dedicated switch to lower the pantograph, and if the cabin lacks it - // then another control has to be used (like pantographlowerall) - // TODO: we should have a way to define presense of cab controls without having to bind these to 3d submodels - return; + if( Train->mvOccupied->PantSwitchType == "impulse" ) { + if( ( Train->ggPantRearButtonOff.SubModel == nullptr ) + && ( Train->ggPantSelectedDownButton.SubModel == nullptr ) ) { + // with impulse buttons we expect a dedicated switch to lower the pantograph, and if the cabin lacks it + // then another control has to be used (like pantographlowerall) + // TODO: we should have a way to define presense of cab controls without having to bind these to 3d submodels + return; + } } Train->mvControlled->PantRearSP = false; @@ -1317,8 +1337,14 @@ void TTrain::OnCommand_pantographtogglerear( TTrain *Train, command_data const & Train->play_sound( Train->dsbSwitch ); // visual feedback Train->ggPantRearButton.UpdateValue( 0.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedButton.UpdateValue( 0.0 ); // pantograph control can have two-button setup Train->ggPantRearButtonOff.UpdateValue( 1.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedDownButton.UpdateValue( 1.0 ); } } } @@ -1329,9 +1355,10 @@ void TTrain::OnCommand_pantographtogglerear( TTrain *Train, command_data const & if( Train->ggPantRearButton.GetValue() > 0.35 ) { Train->play_sound( Train->dsbSwitch ); } - if( Train->ggPantRearButton.SubModel ) { - Train->ggPantRearButton.UpdateValue( 0.0 ); - } + Train->ggPantRearButton.UpdateValue( 0.0 ); + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedButton.UpdateValue( 0.0 ); // also the switch off button, in cabs which have it if( Train->ggPantRearButtonOff.GetValue() > 0.35 ) { Train->play_sound( Train->dsbSwitch ); @@ -1339,6 +1366,11 @@ void TTrain::OnCommand_pantographtogglerear( TTrain *Train, command_data const & if( Train->ggPantRearButtonOff.SubModel ) { Train->ggPantRearButtonOff.UpdateValue( 0.0 ); } + if( Train->ggPantSelectedDownButton.SubModel ) { + // NOTE: currently we animate the selectable pantograph control based on standard key presses + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + Train->ggPantSelectedDownButton.UpdateValue( 0.0 ); + } } } } @@ -1400,7 +1432,8 @@ void TTrain::OnCommand_pantographcompressoractivate( TTrain *Train, command_data void TTrain::OnCommand_pantographlowerall( TTrain *Train, command_data const &Command ) { - if( Train->ggPantAllDownButton.SubModel == nullptr ) { + if( ( Train->ggPantAllDownButton.SubModel == nullptr ) + && ( Train->ggPantSelectedDownButton.SubModel == nullptr ) ) { // TODO: expand definition of cab controls so we can know if the control is present without testing for presence of 3d switch if( Command.action == GLFW_PRESS ) { WriteLog( "Lower All Pantographs switch is missing, or wasn't defined" ); @@ -1423,6 +1456,9 @@ void TTrain::OnCommand_pantographlowerall( TTrain *Train, command_data const &Co } // visual feedback Train->ggPantAllDownButton.UpdateValue( 1.0 ); + if( Train->ggPantSelectedDownButton.SubModel != nullptr ) { + Train->ggPantSelectedDownButton.UpdateValue( 1.0 ); + } } else if( Command.action == GLFW_RELEASE ) { // release the button @@ -1436,6 +1472,9 @@ void TTrain::OnCommand_pantographlowerall( TTrain *Train, command_data const &Co */ // visual feedback Train->ggPantAllDownButton.UpdateValue( 0.0 ); + if( Train->ggPantSelectedDownButton.SubModel != nullptr ) { + Train->ggPantSelectedDownButton.UpdateValue( 0.0 ); + } } } @@ -1485,8 +1524,8 @@ void TTrain::OnCommand_linebreakertoggle( TTrain *Train, command_data const &Com Train->ggMainButton.UpdateValue( 1.0 ); } // keep track of period the button is held down, to determine when/if circuit closes - if( ( false == ( ( Train->mvControlled->EngineType == ElectricSeriesMotor ) - || ( Train->mvControlled->EngineType == ElectricInductionMotor ) ) ) + if( ( ( ( Train->mvControlled->EngineType != ElectricSeriesMotor ) + && ( Train->mvControlled->EngineType != ElectricInductionMotor ) ) ) || ( Train->fHVoltage > 0.5 * Train->mvControlled->EnginePowerSource.MaxVoltage ) ) { // prevent the switch from working if there's no power // TODO: consider whether it makes sense for diesel engines and such @@ -4830,15 +4869,10 @@ bool TTrain::Update( double const Deltatime ) // NBMX wrzesien 2003 - drzwi ggDoorLeftButton.Update(); ggDoorRightButton.Update(); - ggDepartureSignalButton.Update(); + ggDoorSignallingButton.Update(); // NBMX dzwignia sprezarki ggCompressorButton.Update(); ggCompressorLocalButton.Update(); - ggMainButton.Update(); - ggRadioButton.Update(); - ggConverterButton.Update(); - ggConverterLocalButton.Update(); - ggConverterOffButton.Update(); #ifdef EU07_USE_OLD_COMMAND_SYSTEM if( ( ( DynamicObject->iLights[ 0 ] ) == 0 ) && ( ( DynamicObject->iLights[ 1 ] ) == 0 ) ) @@ -4989,15 +5023,6 @@ bool TTrain::Update( double const Deltatime ) } ggDimHeadlightsButton.Update(); //--------- - // Winger 010304 - pantografy - // NOTE: shouldn't the pantograph updates check whether it's front or rear cabin? - ggPantFrontButton.Update(); - ggPantRearButton.Update(); - ggPantFrontButtonOff.Update(); - ggTrainHeatingButton.Update(); - ggSignallingButton.Update(); - ggDoorSignallingButton.Update(); - // Winger 020304 - ogrzewanie // hunter-080812: poprawka na ogrzewanie w elektrykach - usuniete uzaleznienie od przetwornicy if ((((mvControlled->EngineType == ElectricSeriesMotor) && (mvControlled->Mains == true) && (mvControlled->ConvOvldFlag == false)) || @@ -5813,10 +5838,15 @@ bool TTrain::Update( double const Deltatime ) ggStLinOffButton.Update(); ggRadioButton.Update(); ggDepartureSignalButton.Update(); + ggPantFrontButton.Update(); ggPantRearButton.Update(); + ggPantSelectedButton.Update(); ggPantFrontButtonOff.Update(); ggPantRearButtonOff.Update(); + ggPantSelectedDownButton.Update(); + ggPantAllDownButton.Update(); + ggUpperLightButton.Update(); ggLeftLightButton.Update(); ggRightLightButton.Update(); @@ -5829,8 +5859,8 @@ bool TTrain::Update( double const Deltatime ) ggRearLeftEndLightButton.Update(); ggRearRightEndLightButton.Update(); //------------ - ggPantAllDownButton.Update(); ggConverterButton.Update(); + ggConverterLocalButton.Update(); ggConverterOffButton.Update(); ggTrainHeatingButton.Update(); ggSignallingButton.Update(); @@ -6630,7 +6660,6 @@ void TTrain::SetLights() // clears state of all cabin controls void TTrain::clear_cab_controls() { - ggMainCtrl.Clear(); ggMainCtrlAct.Clear(); ggScndCtrl.Clear(); @@ -6670,7 +6699,10 @@ void TTrain::clear_cab_controls() ggConverterButton.Clear(); ggPantFrontButton.Clear(); ggPantRearButton.Clear(); + ggPantSelectedButton.Clear(); ggPantFrontButtonOff.Clear(); + ggPantRearButtonOff.Clear(); + ggPantSelectedDownButton.Clear(); ggPantAllDownButton.Clear(); ggZbS.Clear(); ggI1B.Clear(); @@ -6787,6 +6819,16 @@ void TTrain::set_cab_controls() { ( mvControlled->PantFrontUp ? 0.0 : 1.0 ) ); + // NOTE: currently we animate the selectable pantograph control for both pantographs + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + ggPantSelectedButton.PutValue( + ( mvControlled->PantFrontUp ? + 1.0 : + 0.0 ) ); + ggPantSelectedDownButton.PutValue( + ( mvControlled->PantFrontUp ? + 0.0 : + 1.0 ) ); } if( mvOccupied->PantSwitchType != "impulse" ) { ggPantRearButton.PutValue( @@ -6797,6 +6839,16 @@ void TTrain::set_cab_controls() { ( mvControlled->PantRearUp ? 0.0 : 1.0 ) ); + // NOTE: currently we animate the selectable pantograph control for both pantographs + // TODO: implement actual selection control, and refactor handling this control setup in a separate method + ggPantSelectedButton.PutValue( + ( mvControlled->PantRearUp ? + 1.0 : + 0.0 ) ); + ggPantSelectedDownButton.PutValue( + ( mvControlled->PantRearUp ? + 0.0 : + 1.0 ) ); } // converter if( mvOccupied->ConvSwitchType != "impulse" ) { @@ -7397,16 +7449,22 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con ggPantFrontButtonOff.Load(Parser, DynamicObject->mdKabina); } else if( Label == "pantrearoff_sw:" ) { - // patyk przedni w dol + // rear pant down ggPantRearButtonOff.Load( Parser, DynamicObject->mdKabina ); } - else if( Label == "pantalloff_sw:" ) - { - // patyk przedni w dol + else if( Label == "pantalloff_sw:" ) { + // both pantographs down ggPantAllDownButton.Load(Parser, DynamicObject->mdKabina); } - else if (Label == "trainheating_sw:") - { + else if( Label == "pantselected_sw:" ) { + // operate selected pantograph(s) + ggPantSelectedButton.Load( Parser, DynamicObject->mdKabina ); + } + else if( Label == "pantselectedoff_sw:" ) { + // operate selected pantograph(s) + ggPantSelectedDownButton.Load( Parser, DynamicObject->mdKabina ); + } + else if (Label == "trainheating_sw:") { // grzanie skladu ggTrainHeatingButton.Load(Parser, DynamicObject->mdKabina); } diff --git a/Train.h b/Train.h index c4b6b574..2c70dc70 100644 --- a/Train.h +++ b/Train.h @@ -285,6 +285,8 @@ public: // reszta może by?publiczna TGauge ggPantFrontButtonOff; // EZT TGauge ggPantRearButtonOff; TGauge ggPantAllDownButton; + TGauge ggPantSelectedButton; + TGauge ggPantSelectedDownButton; // Winger 020304 - wlacznik ogrzewania TGauge ggTrainHeatingButton; TGauge ggSignallingButton; diff --git a/World.cpp b/World.cpp index 7ffeb680..7d988403 100644 --- a/World.cpp +++ b/World.cpp @@ -1251,7 +1251,9 @@ void TWorld::Update_Environment() { void TWorld::ResourceSweep() { +/* ResourceManager::Sweep( Timer::GetSimulationTime() ); +*/ }; // rendering kabiny gdy jest oddzielnym modelem i ma byc wyswietlana @@ -1284,7 +1286,7 @@ TWorld::Render_Cab() { glMultMatrixd( dynamic->mMatrix.getArray() ); // ta macierz nie ma przesunięcia */ ::glPushMatrix(); - auto const originoffset = dynamic->GetPosition() - Global::pCameraPosition; + auto const originoffset = dynamic->GetPosition() - GfxRenderer.m_camera.position(); ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); ::glMultMatrixd( dynamic->mMatrix.getArray() ); @@ -1657,7 +1659,7 @@ TWorld::Update_UI() { uitextline1 += " (slowmotion " + to_string( Global::iSlowMotion ) + ")"; } - uitextline2 = GfxRenderer.Info(); + // dump last opengl error, if any GLenum glerror = ::glGetError(); diff --git a/openglgeometrybank.cpp b/openglgeometrybank.cpp index a727cb14..c20bf4e6 100644 --- a/openglgeometrybank.cpp +++ b/openglgeometrybank.cpp @@ -357,25 +357,7 @@ opengl_dlgeometrybank::delete_list( geometry_handle const &Geometry ) { void geometrybank_manager::update() { - m_resourcetimestamp = std::chrono::steady_clock::now(); - // garbage collection sweep is limited to a number of records per call, to reduce impact on framerate - auto const sweeplastindex = - std::min( - m_resourcesweepindex + geometrybank_manager::unusedresourcesweepsize, - m_geometrybanks.size() ); - auto const blanktimestamp { std::chrono::steady_clock::time_point() }; - for( auto bankindex = m_resourcesweepindex; bankindex < sweeplastindex; ++bankindex ) { - if( ( m_geometrybanks[ bankindex ].second != blanktimestamp ) - && ( m_resourcetimestamp - m_geometrybanks[ bankindex ].second > geometrybank_manager::unusedresourcetimetolive ) ) { - - m_geometrybanks[ bankindex ].first->release(); - m_geometrybanks[ bankindex ].second = blanktimestamp; - } - } - m_resourcesweepindex = ( - m_resourcesweepindex + geometrybank_manager::unusedresourcesweepsize >= m_geometrybanks.size() ? - 0 : // if the next sweep chunk is beyond actual data, so start anew - m_resourcesweepindex + geometrybank_manager::unusedresourcesweepsize ); + m_garbagecollector.sweep(); } // creates a new geometry bank. returns: handle to the bank or NULL @@ -418,7 +400,7 @@ geometrybank_manager::draw( geometry_handle const &Geometry, unsigned int const auto &bankrecord = bank( Geometry ); - bankrecord.second = m_resourcetimestamp; + bankrecord.second = m_garbagecollector.timestamp(); bankrecord.first->draw( Geometry, Streams ); } diff --git a/openglgeometrybank.h b/openglgeometrybank.h index 4349cd20..e1656bf9 100644 --- a/openglgeometrybank.h +++ b/openglgeometrybank.h @@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/. #ifdef _WINDOWS #include "GL/wglew.h" #endif +#include "ResourceManager.h" struct basic_vertex { @@ -276,9 +277,9 @@ public: template void draw( Iterator_ First, Iterator_ Last, unsigned int const Streams = basic_streams ) { - while( First != Last ) { - draw( *First, Streams ); - ++First; } } + while( First != Last ) { + draw( *First, Streams ); + ++First; } } // provides direct access to vertex data of specfied chunk vertex_array const & vertices( geometry_handle const &Geometry ) const; @@ -291,12 +292,9 @@ private: typedef std::deque< geometrybanktimepoint_pair > geometrybanktimepointpair_sequence; -// members: - std::chrono::nanoseconds const unusedresourcetimetolive { std::chrono::seconds { 60 } }; - geometrybanktimepointpair_sequence::size_type const unusedresourcesweepsize { 300 }; + // members: geometrybanktimepointpair_sequence m_geometrybanks; - geometrybanktimepointpair_sequence::size_type m_resourcesweepindex { 0 }; - std::chrono::steady_clock::time_point m_resourcetimestamp { std::chrono::steady_clock::now() }; + garbage_collector m_garbagecollector { m_geometrybanks, 60, 120, "geometry buffer" }; // methods inline diff --git a/renderer.cpp b/renderer.cpp index 6389697b..ce8aa487 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -179,11 +179,17 @@ opengl_renderer::Render() { glDisable(GL_FRAMEBUFFER_SRGB); glViewport(0, 0, Global::shadowtune.map_size, Global::shadowtune.map_size); + glm::mat4 coordmove( + 0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0 + ); glm::mat4 depthproj = glm::ortho(-Global::shadowtune.width, Global::shadowtune.width, -Global::shadowtune.width, Global::shadowtune.width, 0.0f, Global::shadowtune.depth); glm::vec3 playerpos = glm::vec3(World.Camera.Pos.x, World.Camera.Pos.y, World.Camera.Pos.z); glm::vec3 shadoweye = playerpos - Global::daylight.direction * Global::shadowtune.distance; - Global::SetCameraPosition(Math3D::vector3(0.0f, 0.0f, 0.0f)); + m_camera.position() = shadoweye; glm::mat4 depthcam = glm::lookAt(shadoweye, playerpos, glm::vec3(0.0f, 1.0f, 0.0f)); @@ -191,13 +197,15 @@ opengl_renderer::Render() { glMatrixMode(GL_PROJECTION); glLoadMatrixf(glm::value_ptr(depthproj)); glMatrixMode(GL_MODELVIEW); - glMultMatrixf(glm::value_ptr(depthcam)); + glMultMatrixd(glm::value_ptr(glm::mat4(glm::mat3(depthcam)))); glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo); glClear(GL_DEPTH_BUFFER_BIT); + glCullFace(GL_FRONT); active_shader = &depth_shader; depth_shader.bind(); Render(&World.Ground); active_shader = nullptr; depth_shader.unbind(); + glCullFace(GL_BACK); glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, Global::ScreenWidth, Global::ScreenHeight); @@ -210,19 +218,19 @@ opengl_renderer::Render() { glBindTexture(GL_TEXTURE_2D, depth_tex); glActiveTexture(GL_TEXTURE0); - glm::dmat4 worldcamera; - World.Camera.SetMatrix( worldcamera ); + glm::dmat4 worldcamera; + World.Camera.SetMatrix(worldcamera); m_camera.update_frustum( OpenGLMatrices.data( GL_PROJECTION ), worldcamera); - ::glMultMatrixd(glm::value_ptr(worldcamera)); + m_camera.position() = glm::make_vec3(Global::pCameraPosition.getArray()); + glMultMatrixd(glm::value_ptr(glm::mat4(glm::mat3(worldcamera)))); shader.bind(); active_shader = &shader; + shader.set_lightview(coordmove * depthproj * depthcam * glm::inverse(glm::mat4(worldcamera))); glDebug("rendering environment"); glDisable(GL_FRAMEBUFFER_SRGB); Render( &World.Environment ); glDebug("rendering world"); glEnable(GL_FRAMEBUFFER_SRGB); - Global::SetCameraPosition(Math3D::vector3(0.0f, 0.0f, 0.0f)); - shader.set_lightview(depthproj * depthcam * glm::inverse(glm::mat4(worldcamera))); Render( &World.Ground ); glDebug("rendering cab"); @@ -251,7 +259,7 @@ opengl_renderer::Render( world_environment *Environment ) { return false; } - Bind( 0 ); + Bind( NULL ); ::glDisable( GL_LIGHTING ); ::glDisable( GL_DEPTH_TEST ); @@ -413,7 +421,7 @@ opengl_renderer::Bind( texture_handle const Texture ) { m_textures.bind( Texture ); } -opengl_texture & +opengl_texture const & opengl_renderer::Texture( texture_handle const Texture ) { return m_textures.texture( Texture ); @@ -442,7 +450,7 @@ opengl_renderer::Render( TGround *Ground ) { node->RenderHidden(); } - glm::vec3 const cameraposition( Global::pCameraPosition.x, Global::pCameraPosition.y, Global::pCameraPosition.z ); + glm::vec3 const cameraposition { m_camera.position() }; int const camerax = static_cast( std::floor( cameraposition.x / 1000.0f ) + iNumRects / 2 ); int const cameraz = static_cast( std::floor( cameraposition.z / 1000.0f ) + iNumRects / 2 ); int const segmentcount = 2 * static_cast(std::ceil( m_drawrange * Global::fDistanceFactor / 1000.0f )); @@ -515,7 +523,7 @@ opengl_renderer::Render( TGroundRect *Groundcell ) { if( subcell->iNodeCount ) { // o ile są jakieś obiekty, bo po co puste sektory przelatywać m_drawqueue.emplace_back( - ( Global::pCameraPosition - glm::dvec3( subcell->m_area.center ) ).LengthSquared(), + glm::length2( m_camera.position() - glm::dvec3( subcell->m_area.center ) ), subcell ); } } @@ -570,7 +578,7 @@ opengl_renderer::Render( TGroundNode *Node ) { { // obiekty renderowane niezależnie od odległości case TP_SUBMODEL: ::glPushMatrix(); - auto const originoffset = Node->pCenter - Global::pCameraPosition; + auto const originoffset = Node->pCenter - m_camera.position(); ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); TSubModel::fSquareDist = 0; Render( Node->smTerrain ); @@ -578,13 +586,13 @@ opengl_renderer::Render( TGroundNode *Node ) { return true; } - double const distancesquared = SquareMagnitude( ( Node->pCenter - Global::pCameraPosition ) / Global::ZoomFactor ); + double const distancesquared = SquareMagnitude( ( Node->pCenter - m_camera.position() ) / Global::ZoomFactor ); if( ( distancesquared > ( Node->fSquareRadius * Global::fDistanceFactor ) ) || ( distancesquared < ( Node->fSquareMinRadius / Global::fDistanceFactor ) ) ) { return false; } - auto const originoffset = Node->m_rootposition - Global::pCameraPosition; + auto const originoffset = Node->m_rootposition - m_camera.position(); active_shader->set_mv(glm::translate(OpenGLMatrices.data(GL_MODELVIEW), glm::vec3(originoffset.x, originoffset.y, originoffset.z))); switch (Node->iType) { @@ -596,7 +604,7 @@ opengl_renderer::Render( TGroundNode *Node ) { } case TP_MODEL: { - Node->Model->Render( Node->pCenter - Global::pCameraPosition ); + Node->Model->Render( Node->pCenter - m_camera.position() ); return true; } @@ -672,7 +680,7 @@ opengl_renderer::Render( TDynamicObject *Dynamic ) { // setup TSubModel::iInstance = ( size_t )this; //żeby nie robić cudzych animacji - auto const originoffset = Dynamic->vPosition - Global::pCameraPosition; + auto const originoffset = Dynamic->vPosition - m_camera.position(); double const squaredistance = SquareMagnitude( originoffset / Global::ZoomFactor ); Dynamic->ABuLittleUpdate( squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu ::glPushMatrix(); @@ -1013,13 +1021,13 @@ opengl_renderer::Render_Alpha( TSubRect *Groundsubcell ) { bool opengl_renderer::Render_Alpha( TGroundNode *Node ) { - double const distancesquared = SquareMagnitude( ( Node->pCenter - Global::pCameraPosition ) / Global::ZoomFactor ); + double const distancesquared = SquareMagnitude( ( Node->pCenter - m_camera.position() ) / Global::ZoomFactor ); if( ( distancesquared > ( Node->fSquareRadius * Global::fDistanceFactor ) ) || ( distancesquared < ( Node->fSquareMinRadius / Global::fDistanceFactor ) ) ) { return false; } - auto const originoffset = Node->m_rootposition - Global::pCameraPosition; + auto const originoffset = Node->m_rootposition - m_camera.position(); active_shader->set_mv(glm::translate(OpenGLMatrices.data(GL_MODELVIEW), glm::vec3(originoffset.x, originoffset.y, originoffset.z))); switch (Node->iType) @@ -1062,7 +1070,7 @@ opengl_renderer::Render_Alpha( TGroundNode *Node ) { } } case TP_MODEL: { - Node->Model->RenderAlpha( Node->pCenter - Global::pCameraPosition ); + Node->Model->RenderAlpha( Node->pCenter - m_camera.position() ); return true; } @@ -1135,7 +1143,7 @@ opengl_renderer::Render_Alpha( TDynamicObject *Dynamic ) { // setup TSubModel::iInstance = ( size_t )this; //żeby nie robić cudzych animacji - auto const originoffset = Dynamic->vPosition - Global::pCameraPosition; + auto const originoffset = Dynamic->vPosition - m_camera.position(); double const squaredistance = SquareMagnitude( originoffset / Global::ZoomFactor ); Dynamic->ABuLittleUpdate( squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu ::glPushMatrix(); @@ -1407,6 +1415,7 @@ opengl_renderer::Update ( double const Deltatime ) { // TODO: add garbage collection and other less frequent works here m_geometry.update(); + m_textures.update(); if( true == DebugModeFlag ) { m_debuginfo = m_textures.info(); @@ -1441,14 +1450,14 @@ opengl_renderer::Update_Lights( light_array const &Lights ) { // all lights past this one are bound to be off break; } - if( ( Global::pCameraPosition - scenelight.position ).Length() > 1000.0f ) { + if( ( m_camera.position() - scenelight.position ).Length() > 1000.0f ) { // we don't care about lights past arbitrary limit of 1 km. // but there could still be weaker lights which are closer, so keep looking continue; } // if the light passed tests so far, it's good enough - Math3D::vector3 pos = scenelight.position - Global::pCameraPosition; + Math3D::vector3 pos = scenelight.position - m_camera.position(); auto const luminance = Global::fLuminance; // TODO: adjust this based on location, e.g. for tunnels glm::vec3 position(pos.x, pos.y, pos.z); diff --git a/renderer.h b/renderer.h index b931a8ab..b5d79b20 100644 --- a/renderer.h +++ b/renderer.h @@ -50,10 +50,17 @@ public: visible( bounding_area const &Area ) const; bool visible( TDynamicObject const *Dynamic ) const; + inline + glm::dvec3 const & + position() const { return m_position; } + inline + glm::dvec3 & + position() { return m_position; } private: // members: cFrustum m_frustum; + glm::dvec3 m_position; }; // bare-bones render controller, in lack of anything better yet @@ -64,6 +71,7 @@ public: gl_program_mvp depth_shader; gl_program_mvp *active_shader = nullptr; GLuint depth_tex, depth_fbo; + opengl_camera m_camera; // types @@ -122,7 +130,7 @@ public: GetTextureId( std::string Filename, std::string const &Dir, int const Filter = -1, bool const Loadnow = true ); void Bind( texture_handle const Texture ); - opengl_texture & + opengl_texture const & Texture( texture_handle const Texture ); // members @@ -170,7 +178,6 @@ private: // members geometrybank_manager m_geometry; texture_manager m_textures; - opengl_camera m_camera; rendermode renderpass { rendermode::color }; float m_drawrange { 2500.0f }; // current drawing range float m_drawtime { 1000.0f / 30.0f * 20.0f }; // start with presumed 'neutral' average of 30 fps diff --git a/shaders/blinnphong.frag b/shaders/blinnphong.frag index 0786134c..13eaff77 100644 --- a/shaders/blinnphong.frag +++ b/shaders/blinnphong.frag @@ -39,50 +39,15 @@ uniform float specular; uniform light_s lights[8]; uniform uint lights_count; -vec2 poissonDisk[16] = vec2[]( - vec2( -0.94201624, -0.39906216 ), - vec2( 0.94558609, -0.76890725 ), - vec2( -0.094184101, -0.92938870 ), - vec2( 0.34495938, 0.29387760 ), - vec2( -0.91588581, 0.45771432 ), - vec2( -0.81544232, -0.87912464 ), - vec2( -0.38277543, 0.27676845 ), - vec2( 0.97484398, 0.75648379 ), - vec2( 0.44323325, -0.97511554 ), - vec2( 0.53742981, -0.47373420 ), - vec2( -0.26496911, -0.41893023 ), - vec2( 0.79197514, 0.19090188 ), - vec2( -0.24188840, 0.99706507 ), - vec2( -0.81409955, 0.91437590 ), - vec2( 0.19984126, 0.78641367 ), - vec2( 0.14383161, -0.14100790 ) -); - -float random(vec3 seed, int i) -{ - vec4 seed4 = vec4(seed,i); - float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673)); - return fract(sin(dot_product) * 43758.5453); -} - float calc_shadow() { - vec3 coords = f_light_pos.xyz;// / f_light_pos.w; - coords = coords * 0.5 + 0.5; - float bias = 0.005; + vec3 coords = f_light_pos.xyz; + float bias = clamp(0.0025*tan(acos(clamp(dot(f_normal, -lights[0].dir), 0.0, 1.0))), 0, 0.01); - //PCF + //sampler PCF //float shadow = texture(shadowmap, vec3(coords.xy, coords.z - bias)); - - //PCF + stratified poisson sampling - //float shadow = 1.0; - //for (int i=0;i<4;i++) - //{ - // int index = int(16.0*random(gl_FragCoord.xyy, i))%16; - // shadow -= 0.25*(1.0-texture(shadowmap, vec3(coords.xy + poissonDisk[index]/5000.0, coords.z - bias))); - //} - //PCF + PCF + //sampler PCF + PCF float shadow = 0.0; vec2 texel = 1.0 / textureSize(shadowmap, 0); for (float y = -1.5; y <= 1.5; y += 1.0) @@ -159,8 +124,10 @@ void main() part = calc_point_light(light); else if (light.type == LIGHT_DIR) part = calc_dir_light(light); - - result += light.color * part * shadow; + + if (i == 0U) + part *= shadow; + result += light.color * part; } vec4 tex_color = texture(tex, f_coord); diff --git a/shaders/empty.frag b/shaders/empty.frag index 67abb5f7..563d9412 100644 --- a/shaders/empty.frag +++ b/shaders/empty.frag @@ -1,7 +1,10 @@ #version 330 -in vec3 f_pos; +in vec2 f_coord; + +uniform sampler2D tex; void main() { + gl_FragDepth = gl_FragCoord.z + (1.0 - texture(tex, f_coord).w); } \ No newline at end of file diff --git a/shaders/shadowmap.vert b/shaders/shadowmap.vert index 6911bde9..48aea27f 100644 --- a/shaders/shadowmap.vert +++ b/shaders/shadowmap.vert @@ -1,8 +1,9 @@ #version 330 layout (location = 0) in vec3 v_vert; +layout (location = 2) in vec2 v_coord; -out vec3 f_pos; +out vec2 f_coord; uniform mat4 modelview; uniform mat4 projection; @@ -10,4 +11,5 @@ uniform mat4 projection; void main() { gl_Position = (projection * modelview) * vec4(v_vert, 1.0f); + f_coord = v_coord; } \ No newline at end of file diff --git a/stdafx.h b/stdafx.h index c7067555..21f43b35 100644 --- a/stdafx.h +++ b/stdafx.h @@ -76,6 +76,7 @@ #include #include #include +#include #include #include #include diff --git a/version.h b/version.h index 063844fb..3be4407c 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 17 -#define VERSION_MINOR 628 +#define VERSION_MINOR 701 #define VERSION_REVISION 0