From e1b20a025ab6c038532575f68d4f48fd6d5f44b7 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 23 Sep 2018 19:13:33 +0200 Subject: [PATCH 01/10] build 180923. basic precipitation visualization system, minor bug fixes --- Driver.cpp | 88 ++++++++------- Driver.h | 4 +- DynObj.cpp | 10 +- DynObj.h | 2 + Globals.h | 1 + Train.cpp | 49 ++++----- drivermode.cpp | 3 + maszyna.vcxproj.filters | 6 ++ material.cpp | 4 + precipitation.cpp | 197 ++++++++++++++++++++++++++++++++++ precipitation.h | 59 ++++++++++ renderer.cpp | 71 +++++++++++- renderer.h | 2 + scene.cpp | 35 +++++- scene.h | 4 + simulationenvironment.cpp | 21 ++++ simulationenvironment.h | 6 ++ simulationstateserializer.cpp | 10 +- version.h | 2 +- 19 files changed, 495 insertions(+), 79 deletions(-) create mode 100644 precipitation.cpp create mode 100644 precipitation.h diff --git a/Driver.cpp b/Driver.cpp index 9462ce99..be6c926e 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -238,9 +238,16 @@ void TSpeedPos::CommandCheck() case TCommandType::cm_PassengerStopPoint: // nie ma dostępu do rozkładu // przystanek, najwyżej AI zignoruje przy analizie tabelki -// if ((iFlags & spPassengerStopPoint) == 0) - fVelNext = 0.0; // TrainParams->IsStop()?0.0:-1.0; //na razie tak + fVelNext = 0.0; iFlags |= spPassengerStopPoint; // niestety nie da się w tym miejscu współpracować z rozkładem +/* + // NOTE: not used for now as it might be unnecessary + // special case, potentially override any set speed limits if requested + // NOTE: we test it here because for the time being it's only used for passenger stops + if( TestFlag( iFlags, spDontApplySpeedLimit ) ) { + fVelNext = -1; + } +*/ break; case TCommandType::cm_SetProximityVelocity: // musi zostać gdyż inaczej nie działają manewry @@ -879,7 +886,6 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // TableClear(); //aby od nowa sprawdziło W4 z inną nazwą już - to nie // jest dobry pomysł sSpeedTable[i].iFlags = 0; // nie liczy się już - sSpeedTable[i].fVelNext = -1; // jechać continue; // nie analizować prędkości } } // koniec obsługi przelotu na W4 @@ -1035,9 +1041,12 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN iDrivigFlags |= moveStopCloser; // do następnego W4 podjechać blisko (z dociąganiem) sSpeedTable[i].iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity) sSpeedTable[i].fVelNext = -1; // można jechać za W4 + if( ( sSpeedTable[ i ].fDist <= 0.0 ) && ( eSignNext == sSpeedTable[ i ].evEvent ) ) { + // sanity check, if we're held by this stop point, let us go + VelSignalLast = -1; + } if (go == TCommandType::cm_Unknown) // jeśli nie było komendy wcześniej - go = TCommandType::cm_Ready; // gotów do odjazdu z W4 (semafor może - // zatrzymać) + go = TCommandType::cm_Ready; // gotów do odjazdu z W4 (semafor może zatrzymać) if( false == tsGuardSignal.empty() ) { // jeśli mamy głos kierownika, to odegrać iDrivigFlags |= moveGuardSignal; @@ -1061,14 +1070,15 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // if the consist can change direction through a simple cab change it doesn't need fiddling with recognition of passenger stops iDrivigFlags &= ~( moveStopPoint ); } + fLastStopExpDist = -1.0f; // nie ma rozkładu, nie ma usuwania stacji sSpeedTable[i].iFlags = 0; // W4 nie liczy się już (nie wyśle SetVelocity) sSpeedTable[i].fVelNext = -1; // można jechać za W4 - fLastStopExpDist = -1.0f; // nie ma rozkładu, nie ma usuwania stacji - // wykonanie kolejnego rozkazu (Change_direction albo Shunt) - // FIX: don't automatically advance if there's disconnect procedure in progress - if( false == TestFlag( OrderCurrentGet(), Disconnect ) ) { - JumpToNextOrder(); + if( ( sSpeedTable[ i ].fDist <= 0.0 ) && ( eSignNext == sSpeedTable[ i ].evEvent ) ) { + // sanity check, if we're held by this stop point, let us go + VelSignalLast = -1; } + // wykonanie kolejnego rozkazu (Change_direction albo Shunt) + JumpToNextOrder(); // ma się nie ruszać aż do momentu podania sygnału iDrivigFlags |= moveStopHere | moveStartHorn; continue; // nie analizować prędkości @@ -1094,36 +1104,42 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN } else if (sSpeedTable[i].iFlags & spEvent) // W4 może się deaktywować { // jeżeli event, może być potrzeba wysłania komendy, aby ruszył - // sprawdzanie eventów pasywnych miniętych - if( (sSpeedTable[ i ].fDist < 0.0) && (SemNextIndex == i) ) - { - if( Global.iWriteLogEnabled & 8 ) { - WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextIndex ].GetName() ); + if( sSpeedTable[ i ].fDist < 0.0 ) { + // sprawdzanie eventów pasywnych miniętych +/* + if( ( eSignNext != nullptr ) && ( sSpeedTable[ i ].evEvent == eSignNext ) ) { + VelSignalLast = sSpeedTable[ i ].fVelNext; } - SemNextIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód - } - if( (sSpeedTable[ i ].fDist < 0.0) && (SemNextStopIndex == i) ) - { - if( Global.iWriteLogEnabled & 8 ) { - WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextStopIndex ].GetName() ); - } - SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód - } - if (sSpeedTable[i].fDist > 0.0 && - sSpeedTable[i].IsProperSemaphor(OrderCurrentGet())) - { - if( SemNextIndex == -1 ) { - // jeśli jest mienięty poprzedni semafor a wcześniej - // byl nowy to go dorzucamy do zmiennej, żeby cały czas widział najbliższy - SemNextIndex = i; +*/ + if( SemNextIndex == i ) { if( Global.iWriteLogEnabled & 8 ) { - WriteLog( "Speed table update for " + OwnerName() + ", next semaphor is " + sSpeedTable[ SemNextIndex ].GetName() ); + WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextIndex ].GetName() ); } + SemNextIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód } - if( ( SemNextStopIndex == -1 ) - || ( ( sSpeedTable[SemNextStopIndex].fVelNext != 0 ) - && ( sSpeedTable[ i ].fVelNext == 0 ) ) ) { - SemNextStopIndex = i; + if( SemNextStopIndex == i ) { + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextStopIndex ].GetName() ); + } + SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód + } + } + if( sSpeedTable[ i ].fDist > 0.0 ) { + // check signals ahead + if( sSpeedTable[ i ].IsProperSemaphor( OrderCurrentGet() ) ) { + if( SemNextIndex == -1 ) { + // jeśli jest mienięty poprzedni semafor a wcześniej + // byl nowy to go dorzucamy do zmiennej, żeby cały czas widział najbliższy + SemNextIndex = i; + if( Global.iWriteLogEnabled & 8 ) { + WriteLog( "Speed table update for " + OwnerName() + ", next semaphor is " + sSpeedTable[ SemNextIndex ].GetName() ); + } + } + if( ( SemNextStopIndex == -1 ) + || ( ( sSpeedTable[ SemNextStopIndex ].fVelNext != 0 ) + && ( sSpeedTable[ i ].fVelNext == 0 ) ) ) { + SemNextStopIndex = i; + } } } if (sSpeedTable[i].iFlags & spOutsideStation) diff --git a/Driver.h b/Driver.h index 33afad06..b86f7fd4 100644 --- a/Driver.h +++ b/Driver.h @@ -119,8 +119,8 @@ enum TSpeedPosFlag spSemaphor = 0x4000, // semafor pociągowy spRoadVel = 0x8000, // zadanie prędkości drogowej spSectionVel = 0x20000, // odcinek z ograniczeniem - spProximityVelocity = 0x40000, // odcinek z ograniczeniem i podaną jego długościa - spEndOfTable = 0x10000 // zatkanie tabelki + spProximityVelocity = 0x40000 // odcinek z ograniczeniem i podaną jego długościa +// spDontApplySpeedLimit = 0x10000 // this point won't apply its speed limit. potentially set by the scanning vehicle }; class TSpeedPos diff --git a/DynObj.cpp b/DynObj.cpp index 1c8b4267..f9fc47c2 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -2014,7 +2014,7 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" auto const indexstart { 1 }; auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) }; auto const huntingchance { std::atoi( ActPar.substr( indexstart, indexend ).c_str() ) }; - MoverParameters->TruckHunting = ( Random( 0, 100 ) <= huntingchance ); + MoverParameters->TruckHunting = ( Random( 0, 100 ) < huntingchance ); ActPar.erase( 0, indexend ); break; } @@ -2396,12 +2396,12 @@ void TDynamicObject::Move(double fDistance) // normalizacja potrzebna z powodu pochylenia (vFront) vUp = CrossProduct(vFront, vLeft); // wektor w górę, będzie jednostkowy modelRot.z = atan2(-vFront.x, vFront.z); // kąt obrotu pojazdu [rad]; z ABuBogies() - double a = ((Axle1.GetRoll() + Axle0.GetRoll())); // suma przechyłek - if (a != 0.0) + auto const roll { Roll() }; // suma przechyłek + if (roll != 0.0) { // wyznaczanie przechylenia tylko jeśli jest przechyłka // można by pobrać wektory normalne z toru... mMatrix.Identity(); // ta macierz jest potrzebna głównie do wyświetlania - mMatrix.Rotation(a * 0.5, vFront); // obrót wzdłuż osi o przechyłkę + mMatrix.Rotation(roll * 0.5, vFront); // obrót wzdłuż osi o przechyłkę vUp = mMatrix * vUp; // wektor w górę pojazdu (przekręcenie na przechyłce) // vLeft=mMatrix*DynamicObject->vLeft; // vUp=CrossProduct(vFront,vLeft); //wektor w górę @@ -2838,7 +2838,7 @@ bool TDynamicObject::Update(double dt, double dt1) // TTrackParam tp; tp.Width = MyTrack->fTrackWidth; // McZapkie-250202 - tp.friction = MyTrack->fFriction * Global.fFriction; + tp.friction = MyTrack->fFriction * Global.fFriction * Global.FrictionWeatherFactor; tp.CategoryFlag = MyTrack->iCategoryFlag & 15; tp.DamageFlag = MyTrack->iDamageFlag; tp.QualityFlag = MyTrack->iQualityFlag; diff --git a/DynObj.h b/DynObj.h index 0976e11e..8c899e52 100644 --- a/DynObj.h +++ b/DynObj.h @@ -546,6 +546,8 @@ private: return iAxleFirst ? Axle1.pPosition : Axle0.pPosition; }; + inline double Roll() { + return ( ( Axle1.GetRoll() + Axle0.GetRoll() ) ); } /* // TODO: check if scanning takes into account direction when selecting axle // if it does, replace the version above diff --git a/Globals.h b/Globals.h index 7d230f4a..8caf793a 100644 --- a/Globals.h +++ b/Globals.h @@ -65,6 +65,7 @@ struct global_settings { bool RealisticControlMode{ false }; // controls ability to steer the vehicle from outside views bool bEnableTraction{ true }; float fFriction{ 1.f }; // mnożnik tarcia - KURS90 + float FrictionWeatherFactor { 1.f }; bool bLiveTraction{ true }; float Overcast{ 0.1f }; // NOTE: all this weather stuff should be moved elsewhere glm::vec3 FogColor = { 0.6f, 0.7f, 0.8f }; diff --git a/Train.cpp b/Train.cpp index 16a8e5d0..974f2cd2 100644 --- a/Train.cpp +++ b/Train.cpp @@ -527,9 +527,9 @@ PyObject *TTrain::GetTrainState() { PyDict_SetItemString( dict, "unit_no", PyGetInt( iUnitNo ) ); for( int i = 0; i < 20; i++ ) { - PyDict_SetItemString( dict, ( "doors_" + std::to_string( i + 1 ) ).c_str(), PyGetFloatS( bDoors[ i ][ 0 ] ) ); - PyDict_SetItemString( dict, ( "doors_r_" + std::to_string( i + 1 ) ).c_str(), PyGetFloatS( bDoors[ i ][ 1 ] ) ); - PyDict_SetItemString( dict, ( "doors_l_" + std::to_string( i + 1 ) ).c_str(), PyGetFloatS( bDoors[ i ][ 2 ] ) ); + PyDict_SetItemString( dict, ( "doors_" + std::to_string( i + 1 ) ).c_str(), PyGetBool( bDoors[ i ][ 0 ] ) ); + PyDict_SetItemString( dict, ( "doors_r_" + std::to_string( i + 1 ) ).c_str(), PyGetBool( bDoors[ i ][ 1 ] ) ); + PyDict_SetItemString( dict, ( "doors_l_" + std::to_string( i + 1 ) ).c_str(), PyGetBool( bDoors[ i ][ 2 ] ) ); PyDict_SetItemString( dict, ( "doors_no_" + std::to_string( i + 1 ) ).c_str(), PyGetInt( iDoorNo[ i ] ) ); PyDict_SetItemString( dict, ( "code_" + std::to_string( i + 1 ) ).c_str(), PyGetString( ( std::to_string( iUnits[ i ] ) + cCode[ i ] ).c_str() ) ); PyDict_SetItemString( dict, ( "car_name" + std::to_string( i + 1 ) ).c_str(), PyGetString( asCarName[ i ].c_str() ) ); @@ -4529,31 +4529,14 @@ void TTrain::UpdateMechPosition(double dt) 2 : DynamicObject->MoverParameters->ActiveCab ); if( !DebugModeFlag ) { // sprawdzaj więzy //Ra: nie tu! - if( pMechPosition.x < Cabine[ iCabn ].CabPos1.x ) - pMechPosition.x = Cabine[ iCabn ].CabPos1.x; - if( pMechPosition.x > Cabine[ iCabn ].CabPos2.x ) - pMechPosition.x = Cabine[ iCabn ].CabPos2.x; - if( pMechPosition.z < Cabine[ iCabn ].CabPos1.z ) - pMechPosition.z = Cabine[ iCabn ].CabPos1.z; - if( pMechPosition.z > Cabine[ iCabn ].CabPos2.z ) - pMechPosition.z = Cabine[ iCabn ].CabPos2.z; - if( pMechPosition.y > Cabine[ iCabn ].CabPos1.y + 1.8 ) - pMechPosition.y = Cabine[ iCabn ].CabPos1.y + 1.8; - if( pMechPosition.y < Cabine[ iCabn ].CabPos1.y + 0.5 ) - pMechPosition.y = Cabine[ iCabn ].CabPos2.y + 0.5; - if( pMechOffset.x < Cabine[ iCabn ].CabPos1.x ) - pMechOffset.x = Cabine[ iCabn ].CabPos1.x; - if( pMechOffset.x > Cabine[ iCabn ].CabPos2.x ) - pMechOffset.x = Cabine[ iCabn ].CabPos2.x; - if( pMechOffset.z < Cabine[ iCabn ].CabPos1.z ) - pMechOffset.z = Cabine[ iCabn ].CabPos1.z; - if( pMechOffset.z > Cabine[ iCabn ].CabPos2.z ) - pMechOffset.z = Cabine[ iCabn ].CabPos2.z; - if( pMechOffset.y > Cabine[ iCabn ].CabPos1.y + 1.8 ) - pMechOffset.y = Cabine[ iCabn ].CabPos1.y + 1.8; - if( pMechOffset.y < Cabine[ iCabn ].CabPos1.y + 0.5 ) - pMechOffset.y = Cabine[ iCabn ].CabPos2.y + 0.5; + pMechPosition.x = clamp( pMechPosition.x, Cabine[ iCabn ].CabPos1.x, Cabine[ iCabn ].CabPos2.x ); + pMechPosition.y = clamp( pMechPosition.y, Cabine[ iCabn ].CabPos1.y + 0.5, Cabine[ iCabn ].CabPos2.y + 1.8 ); + pMechPosition.z = clamp( pMechPosition.z, Cabine[ iCabn ].CabPos1.z, Cabine[ iCabn ].CabPos2.z ); + + pMechOffset.x = clamp( pMechOffset.x, Cabine[ iCabn ].CabPos1.x, Cabine[ iCabn ].CabPos2.x ); + pMechOffset.y = clamp( pMechOffset.y, Cabine[ iCabn ].CabPos1.y + 0.5, Cabine[ iCabn ].CabPos2.y + 1.8 ); + pMechOffset.z = clamp( pMechOffset.z, Cabine[ iCabn ].CabPos1.z, Cabine[ iCabn ].CabPos2.z ); } }; @@ -5863,7 +5846,7 @@ TTrain::update_sounds( double const Deltatime ) { dsbSlipAlarm.stop(); } } - +/* // szum w czasie jazdy if( ( false == FreeFlyModeFlag ) && ( false == Global.CabWindowOpen ) @@ -5875,6 +5858,7 @@ TTrain::update_sounds( double const Deltatime ) { // don't play the optional ending sound if the listener switches views rsRunningNoise.stop( true == FreeFlyModeFlag ); } + */ // hunting oscillation noise if( ( false == FreeFlyModeFlag ) && ( false == Global.CabWindowOpen ) @@ -5882,6 +5866,15 @@ TTrain::update_sounds( double const Deltatime ) { && ( IsHunting ) ) { update_sounds_runningnoise( rsHuntingNoise ); + // modify calculated sound volume by hunting amount + auto const huntingamount = + interpolate( + 0.0, 1.0, + clamp( + ( mvOccupied->Vel - HuntingShake.fadein_begin ) / ( HuntingShake.fadein_end - HuntingShake.fadein_begin ), + 0.0, 1.0 ) ); + + rsHuntingNoise.gain( rsHuntingNoise.gain() * huntingamount ); } else { // don't play the optional ending sound if the listener switches views diff --git a/drivermode.cpp b/drivermode.cpp index 05488a7f..77dd143c 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -224,6 +224,8 @@ driver_mode::update() { update_camera( deltarealtime ); + simulation::Environment.update_precipitation(); // has to be launched after camera step to work properly + Timer::subsystem.sim_total.stop(); simulation::Region->update_sounds(); @@ -951,6 +953,7 @@ driver_mode::InOutKey( bool const Near ) train->Dynamic()->ABuSetModelShake( { 0, 0, 0 } ); train->MechStop(); FollowView(); // na pozycję mecha + train->UpdateMechPosition( m_secondaryupdaterate ); } else FreeFlyModeFlag = true; // nadal poza kabiną diff --git a/maszyna.vcxproj.filters b/maszyna.vcxproj.filters index 665a7b90..761c958b 100644 --- a/maszyna.vcxproj.filters +++ b/maszyna.vcxproj.filters @@ -330,6 +330,9 @@ Source Files\imgui + + Source Files + @@ -608,6 +611,9 @@ Header Files\application\mode_editor + + Header Files + diff --git a/material.cpp b/material.cpp index e3d2daed..d5c03784 100644 --- a/material.cpp +++ b/material.cpp @@ -43,12 +43,16 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c if( key == Global.Weather ) { // weather textures override generic (pri 0) and seasonal (pri 1) textures // seasonal weather textures (pri 1+2=3) override generic weather (pri 2) textures + // skip the opening bracket + auto const value { Input.getToken( true, "\n\r\t ;" ) }; while( true == deserialize_mapping( Input, Priority + 2, Loadnow ) ) { ; // all work is done in the header } } else if( key == Global.Season ) { // seasonal textures override generic textures + // skip the opening bracket + auto const value { Input.getToken( true, "\n\r\t ;" ) }; while( true == deserialize_mapping( Input, 1, Loadnow ) ) { ; // all work is done in the header } diff --git a/precipitation.cpp b/precipitation.cpp new file mode 100644 index 00000000..54264bc2 --- /dev/null +++ b/precipitation.cpp @@ -0,0 +1,197 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "precipitation.h" + +#include "globals.h" +#include "openglmatrixstack.h" +#include "renderer.h" +#include "timer.h" +#include "simulation.h" +#include "train.h" + +basic_precipitation::~basic_precipitation() { + // TODO: release allocated resources +} + +void +basic_precipitation::create( int const Tesselation ) { + + auto const heightfactor { 10.f }; // height-to-radius factor + m_moverate *= heightfactor; + auto const verticaltexturestretchfactor { 1.5f }; // crude motion blur + + // create geometry chunk + auto const latitudes { 3 }; // just a cylinder with end cones + auto const longitudes { Tesselation }; + auto const longitudehalfstep { 0.5f * static_cast( 2.0 * M_PI * 1.f / longitudes ) }; // for crude uv correction + + std::uint16_t index = 0; + +// auto const radius { 25.f }; // cylinder radius + std::vector radii { 25.f, 10.f, 5.f, 1.f }; + for( auto radius : radii ) { + + for( int i = 0; i <= latitudes; ++i ) { + + auto const latitude{ static_cast( M_PI * ( -0.5f + (float)( i ) / latitudes ) ) }; + auto const z{ std::sin( latitude ) }; + auto const zr{ std::cos( latitude ) }; + + for( int j = 0; j <= longitudes; ++j ) { + // NOTE: for the first and last row half of the points we create end up unused but, eh + auto const longitude{ static_cast( 2.0 * M_PI * (float)( j ) / longitudes ) }; + auto const x{ std::cos( longitude ) }; + auto const y{ std::sin( longitude ) }; + // NOTE: cartesian to opengl swap would be: -x, -z, -y + m_vertices.emplace_back( glm::vec3( -x * zr, -z * heightfactor, -y * zr ) * radius ); + // uvs + // NOTE: first and last row receives modified u values to deal with limitation of mapping onto triangles + auto u = ( + i == 0 ? longitude + longitudehalfstep : + i == latitudes ? longitude - longitudehalfstep : + longitude ); + m_uvs.emplace_back( + u / ( 2.0 * M_PI ) * radius, + 1.f - (float)( i ) / latitudes * radius * heightfactor * 0.5f / verticaltexturestretchfactor ); + + if( ( i == 0 ) || ( j == 0 ) ) { + // initial edge of the dome, don't start indices yet + ++index; + } + else { + // the end cones are built from one triangle of each quad, the middle rows use both + if( i < latitudes ) { + m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); + m_indices.emplace_back( index - 1 ); + m_indices.emplace_back( index ); + } + if( i > 1 ) { + m_indices.emplace_back( index ); + m_indices.emplace_back( index - ( longitudes + 1 ) ); + m_indices.emplace_back( index - 1 - ( longitudes + 1 ) ); + } + ++index; + } + } // longitude + } // latitude + } // radius +} + +bool +basic_precipitation::init() { + + create( 18 ); + + // TODO: select texture based on current overcast level + if( Global.Weather == "rain:" ) { + m_texture = GfxRenderer.Fetch_Texture( "fx/rain_medium" ); + m_moverateweathertypefactor = 2.f; + } + else if( Global.Weather == "snow:" ) { + m_texture = GfxRenderer.Fetch_Texture( "fx/snow_medium" ); + m_moverateweathertypefactor = 1.25f; + } + + return true; +} + +void +basic_precipitation::update() { + + auto const timedelta { static_cast( ( DebugModeFlag ? Timer::GetDeltaTime() : Timer::GetDeltaTime() ) ) }; + + if( timedelta == 0.0 ) { return; } + + m_textureoffset += m_moverate * m_moverateweathertypefactor * timedelta; + m_textureoffset = clamp_circular( m_textureoffset, 1.f ); + + auto cameramove { glm::dvec3{ Global.pCamera.Pos - m_camerapos} }; + cameramove.y = 0.0; // vertical movement messes up vector calculation + + m_camerapos = Global.pCamera.Pos; + + // intercept sudden user-induced camera jumps + if( m_freeflymode != FreeFlyModeFlag ) { + m_freeflymode = FreeFlyModeFlag; + if( true == m_freeflymode ) { + // cache last precipitation vector in the cab + m_cabcameramove = m_cameramove; + // don't carry previous precipitation vector to a new unrelated location + m_cameramove = glm::dvec3{ 0.0 }; + } + else { + // restore last cached precipitation vector + m_cameramove = m_cabcameramove; + } + cameramove = glm::dvec3{ 0.0 }; + } + if( m_windowopen != Global.CabWindowOpen ) { + m_windowopen = Global.CabWindowOpen; + cameramove = glm::dvec3{ 0.0 }; + } + if( ( simulation::Train != nullptr ) && ( simulation::Train->iCabn != m_activecab ) ) { + m_activecab = simulation::Train->iCabn; + cameramove = glm::dvec3{ 0.0 }; + } + if( glm::length( cameramove ) > 100.0 ) { + cameramove = glm::dvec3{ 0.0 }; + } + + m_cameramove = m_cameramove * std::max( 0.0, 1.0 - 5.0 * timedelta ) + cameramove * ( 30.0 * timedelta ); + if( std::abs( m_cameramove.x ) < 0.001 ) { m_cameramove.x = 0.0; } + if( std::abs( m_cameramove.y ) < 0.001 ) { m_cameramove.y = 0.0; } + if( std::abs( m_cameramove.z ) < 0.001 ) { m_cameramove.z = 0.0; } +} + +void +basic_precipitation::render() { + + GfxRenderer.Bind_Texture( m_texture ); + + // cache entry state + ::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + + if( m_vertexbuffer == -1 ) { + // build the buffers + ::glGenBuffers( 1, &m_vertexbuffer ); + ::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer ); + ::glBufferData( GL_ARRAY_BUFFER, m_vertices.size() * sizeof( glm::vec3 ), m_vertices.data(), GL_STATIC_DRAW ); + + ::glGenBuffers( 1, &m_uvbuffer ); + ::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer ); + ::glBufferData( GL_ARRAY_BUFFER, m_uvs.size() * sizeof( glm::vec2 ), m_uvs.data(), GL_STATIC_DRAW ); + + ::glGenBuffers( 1, &m_indexbuffer ); + ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer ); + ::glBufferData( GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof( unsigned short ), m_indices.data(), GL_STATIC_DRAW ); + // NOTE: vertex and index source data is superfluous past this point, but, eh + } + // positions + ::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer ); + ::glVertexPointer( 3, GL_FLOAT, sizeof( glm::vec3 ), reinterpret_cast( 0 ) ); + ::glEnableClientState( GL_VERTEX_ARRAY ); + // uvs + ::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer ); + ::glClientActiveTexture( m_textureunit ); + ::glTexCoordPointer( 2, GL_FLOAT, sizeof( glm::vec2 ), reinterpret_cast( 0 ) ); + ::glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + // uv transformation matrix + ::glMatrixMode( GL_TEXTURE ); + ::glLoadIdentity(); + ::glTranslatef( 0.f, m_textureoffset, 0.f ); + // indices + ::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer ); + ::glDrawElements( GL_TRIANGLES, static_cast( m_indices.size() ), GL_UNSIGNED_SHORT, reinterpret_cast( 0 ) ); + // cleanup + ::glLoadIdentity(); + ::glMatrixMode( GL_MODELVIEW ); + ::glPopClientAttrib(); +} diff --git a/precipitation.h b/precipitation.h new file mode 100644 index 00000000..f2f2fcd8 --- /dev/null +++ b/precipitation.h @@ -0,0 +1,59 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include "texture.h" + +// based on "Rendering Falling Rain and Snow" +// by Niniane Wang, Bretton Wade + +class basic_precipitation { + +public: +// constructors + basic_precipitation() = default; +// destructor + ~basic_precipitation(); +// methods + inline + void + set_unit( GLint const Textureunit ) { + m_textureunit = Textureunit; } + bool + init(); + void + update(); + void + render(); + + glm::dvec3 m_cameramove{ 0.0 }; + +private: +// methods + void create( int const Tesselation ); + +// members + std::vector m_vertices; + std::vector m_uvs; + std::vector m_indices; + GLuint m_vertexbuffer { (GLuint)-1 }; + GLuint m_uvbuffer { (GLuint)-1 }; + GLuint m_indexbuffer { (GLuint)-1 }; + GLint m_textureunit { 0 }; + texture_handle m_texture { -1 }; + float m_textureoffset { 0.f }; + float m_moverate { 30 * 0.001f }; + float m_moverateweathertypefactor { 1.f }; // medium-dependent; 1.0 for snow, faster for rain + glm::dvec3 m_camerapos { 0.0 }; + bool m_freeflymode { true }; + bool m_windowopen { true }; + int m_activecab{ 0 }; + glm::dvec3 m_cabcameramove{ 0.0 }; +}; diff --git a/renderer.cpp b/renderer.cpp index c2f4adfc..28e9d0e0 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -129,6 +129,7 @@ opengl_renderer::Init( GLFWwindow *Window ) { std::vector{ m_normaltextureunit, m_diffusetextureunit } ); m_textures.assign_units( m_helpertextureunit, m_shadowtextureunit, m_normaltextureunit, m_diffusetextureunit ); // TODO: add reflections unit ui_layer::set_unit( m_diffusetextureunit ); + simulation::Environment.m_precipitation.set_unit( m_diffusetextureunit ); select_unit( m_diffusetextureunit ); ::glDepthFunc( GL_LEQUAL ); @@ -550,7 +551,9 @@ opengl_renderer::Render_pass( rendermode const Mode ) { ::glEnable( GL_TEXTURE_2D ); } #endif - if( false == FreeFlyModeFlag ) { + // without rain/snow we can render the cab early to limit the overdraw + if( ( false == FreeFlyModeFlag ) + && ( Global.Overcast <= 1.f ) ) { // precipitation happens when overcast is in 1-2 range switch_units( true, true, false ); setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix ); // cache shadow colour in case we need to account for cab light @@ -570,8 +573,10 @@ opengl_renderer::Render_pass( rendermode const Mode ) { // ...translucent parts setup_drawing( true ); Render_Alpha( simulation::Region ); + // precipitation; done at the end, only before cab render + Render_precipitation(); + // cab render if( false == FreeFlyModeFlag ) { - // cab render is performed without shadows, due to low resolution and number of models without windows :| switch_units( true, true, false ); setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix ); // cache shadow colour in case we need to account for cab light @@ -580,6 +585,12 @@ opengl_renderer::Render_pass( rendermode const Mode ) { if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( glm::min( colors::white, shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) ); } + if( Global.Overcast > 1.f ) { + // with active precipitation draw the opaque cab parts here to mask rain/snow placed 'inside' the cab + setup_drawing( false ); + Render_cab( vehicle, false ); + setup_drawing( true ); + } Render_cab( vehicle, true ); if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( shadowcolor ); @@ -590,7 +601,8 @@ opengl_renderer::Render_pass( rendermode const Mode ) { // restore default texture matrix for reflections cube map select_unit( m_helpertextureunit ); ::glMatrixMode( GL_TEXTURE ); - ::glPopMatrix(); +// ::glPopMatrix(); + ::glLoadIdentity(); select_unit( m_diffusetextureunit ); ::glMatrixMode( GL_MODELVIEW ); } @@ -1011,7 +1023,8 @@ opengl_renderer::setup_matrices() { // special case, for colour render pass setup texture matrix for reflections cube map select_unit( m_helpertextureunit ); ::glMatrixMode( GL_TEXTURE ); - ::glPushMatrix(); +// ::glPushMatrix(); + ::glLoadIdentity(); ::glMultMatrixf( glm::value_ptr( glm::inverse( glm::mat4{ glm::mat3{ m_renderpass.camera.modelview() } } ) ) ); select_unit( m_diffusetextureunit ); } @@ -1459,7 +1472,7 @@ opengl_renderer::Render( world_environment *Environment ) { // sun { Bind_Texture( m_suntexture ); - ::glColor4f( suncolor.x, suncolor.y, suncolor.z, 1.0f ); + ::glColor4f( suncolor.x, suncolor.y, suncolor.z, clamp( 1.5f - Global.Overcast, 0.f, 1.f ) ); auto const sunvector = Environment->m_sun.getDirection(); auto const sunposition = modelview * glm::vec4( sunvector.x, sunvector.y, sunvector.z, 1.0f ); @@ -2807,6 +2820,54 @@ opengl_renderer::Render( TMemCell *Memcell ) { ::glPopMatrix(); } +void +opengl_renderer::Render_precipitation() { + + if( Global.Overcast <= 1.f ) { return; } + + switch_units( true, false, false ); + +// ::glColor4fv( glm::value_ptr( glm::vec4( glm::min( glm::vec3( Global.fLuminance ), glm::vec3( 1 ) ), 1 ) ) ); + ::glColor4fv( + glm::value_ptr( + interpolate( + 0.5f * ( Global.DayLight.diffuse + Global.DayLight.ambient ), + colors::white, + 0.5f * clamp( Global.fLuminance, 0.f, 1.f ) ) ) ); + ::glPushMatrix(); + + auto const velocity { simulation::Environment.m_precipitation.m_cameramove * -1.0 }; + if( glm::length2( velocity ) > 0.0 ) { + auto const forward{ glm::normalize( velocity ) }; + if( false == FreeFlyModeFlag ) { + // counter potential vehicle roll + auto const roll { simulation::Train->Dynamic()->Roll() }; + if( roll != 0.0 ) { + ::glRotated( roll, forward.x, 0.0, forward.z ); + } + } + auto left { glm::cross( forward, {0.0,1.0,0.0} ) }; + auto const rotationangle { + std::min( + 45.0, + ( FreeFlyModeFlag ? + 5 * glm::length( velocity ) : + simulation::Train->Dynamic()->GetVelocity() * 0.2 ) ) }; + ::glRotated( rotationangle, left.x, 0.0, left.z ); + } + + // TBD: leave lighting on to allow vehicle lights to affect it? + ::glDisable( GL_LIGHTING ); + // momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside' + ::glDepthMask( GL_FALSE ); + + simulation::Environment.m_precipitation.render(); + + ::glDepthMask( GL_TRUE ); + ::glEnable( GL_LIGHTING ); + ::glPopMatrix(); +} + void opengl_renderer::Render_Alpha( scene::basic_region *Region ) { diff --git a/renderer.h b/renderer.h index b9ac02a9..34c73e10 100644 --- a/renderer.h +++ b/renderer.h @@ -294,6 +294,8 @@ private: Render_cab( TDynamicObject const *Dynamic, bool const Alpha = false ); void Render( TMemCell *Memcell ); + void + Render_precipitation(); void Render_Alpha( scene::basic_region *Region ); void diff --git a/scene.cpp b/scene.cpp index 82022b1b..45558a94 100644 --- a/scene.cpp +++ b/scene.cpp @@ -986,6 +986,37 @@ basic_region::update_traction( TDynamicObject *Vehicle, int const Pantographinde } } +// checks whether specified file is a valid region data file +bool +basic_region::is_scene( std::string const &Scenariofile ) const { + + auto filename { Scenariofile }; + while( filename[ 0 ] == '$' ) { + // trim leading $ char rainsted utility may add to the base name for modified .scn files + filename.erase( 0, 1 ); + } + erase_extension( filename ); + filename = Global.asCurrentSceneryPath + filename; + filename += EU07_FILEEXTENSION_REGION; + + if( false == FileExists( filename ) ) { + return false; + } + // file type and version check + std::ifstream input( filename, std::ios::binary ); + + uint32_t headermain{ sn_utils::ld_uint32( input ) }; + uint32_t headertype{ sn_utils::ld_uint32( input ) }; + + if( ( headermain != EU07_FILEHEADER + || ( headertype != EU07_FILEVERSION_REGION ) ) ) { + // wrong file type + return false; + } + + return true; +} + // stores content of the class in file with specified name void basic_region::serialize( std::string const &Scenariofile ) const { @@ -1062,7 +1093,9 @@ basic_region::deserialize( std::string const &Scenariofile ) { // section index, followed by section data size, followed by section data auto *§ion { m_sections[ sn_utils::ld_uint32( input ) ] }; auto const sectionsize { sn_utils::ld_uint32( input ) }; - section = new basic_section(); + if( section == nullptr ) { + section = new basic_section(); + } section->deserialize( input ); } diff --git a/scene.h b/scene.h index 8a0745e3..926629ce 100644 --- a/scene.h +++ b/scene.h @@ -53,6 +53,7 @@ struct scratch_data { bool is_open { false }; } trainset; + std::string name; bool initialized { false }; }; @@ -321,6 +322,9 @@ public: // legacy method, updates sounds around camera void update_sounds(); + // checks whether specified file is a valid region data file + bool + is_scene( std::string const &Scenariofile ) const; // stores content of the class in file with specified name void serialize( std::string const &Scenariofile ) const; diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index 5cef14eb..5602f7af 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -76,6 +76,9 @@ world_environment::init() { m_moon.init(); m_stars.init(); m_clouds.Init(); + m_precipitation.init(); + + m_precipitationsound.deserialize( "rain-sound-loop", sound_type::single ); } void @@ -147,6 +150,24 @@ world_environment::update() { // but quite effective way to make the distant items blend with background better // NOTE: base brightness calculation provides scaled up value, so we bring it back to 'real' one here Global.FogColor = m_skydome.GetAverageHorizonColor(); + + // weather-related simulation factors + // TODO: dynamic change of air temperature and overcast levels + if( Global.Weather == "rain:" ) { + // reduce friction in rain + Global.FrictionWeatherFactor = 0.85f; + m_precipitationsound.play( sound_flags::exclusive | sound_flags::looping ); + } + else if( Global.Weather == "snow:" ) { + // reduce friction due to snow + Global.FrictionWeatherFactor = 0.75f; + } +} + +void +world_environment::update_precipitation() { + + m_precipitation.update(); } void diff --git a/simulationenvironment.h b/simulationenvironment.h index ce3b83a4..2f69f631 100644 --- a/simulationenvironment.h +++ b/simulationenvironment.h @@ -14,6 +14,8 @@ http://mozilla.org/MPL/2.0/. #include "moon.h" #include "stars.h" #include "skydome.h" +#include "precipitation.h" +#include "sound.h" class opengl_renderer; @@ -26,6 +28,7 @@ public: // methods void init(); void update(); + void update_precipitation(); void time( int const Hour = -1, int const Minute = -1, int const Second = -1 ); // switches between static and dynamic daylight calculation void toggle_daylight(); @@ -41,6 +44,9 @@ private: cSun m_sun; cMoon m_moon; TSky m_clouds; + basic_precipitation m_precipitation; + + sound_source m_precipitationsound { sound_placement::external, -1 }; }; namespace simulation { diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index 49410389..ba327225 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -37,9 +37,11 @@ state_serializer::deserialize( std::string const &Scenariofile ) { // TODO: check first for presence of serialized binary files // if this fails, fall back on the legacy text format scene::scratch_data importscratchpad; + importscratchpad.name = Scenariofile; if( Scenariofile != "$.scn" ) { // compilation to binary file isn't supported for rainsted-created overrides - importscratchpad.binary.terrain = Region->deserialize( Scenariofile ); + // NOTE: we postpone actual loading of the scene until we process time, season and weather data + importscratchpad.binary.terrain = Region->is_scene( Scenariofile ) ; } // NOTE: for the time being import from text format is a given, since we don't have full binary serialization cParser scenarioparser( Scenariofile, cParser::buffer_FILE, Global.asCurrentSceneryPath, Global.bLoadTraction ); @@ -266,6 +268,12 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc if( true == Scratchpad.initialized ) { return; } + if( true == Scratchpad.binary.terrain ) { + // at this stage it should be safe to import terrain from the binary scene file + // TBD: postpone loading furter and only load required blocks during the simulation? + Region->deserialize( Scratchpad.name ); + } + simulation::Paths.InitTracks(); simulation::Traction.InitTraction(); simulation::Events.InitEvents(); diff --git a/version.h b/version.h index 48c50999..2140e45d 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 18 -#define VERSION_MINOR 919 +#define VERSION_MINOR 923 #define VERSION_REVISION 0 From e378965ef3101a404f5be4edf60b1a289d49b923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 23 Sep 2018 16:32:14 +0200 Subject: [PATCH 02/10] Adding BrakeDelayFlag to Python dictionary --- Train.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Train.cpp b/Train.cpp index 974f2cd2..9972622a 100644 --- a/Train.cpp +++ b/Train.cpp @@ -477,6 +477,7 @@ PyObject *TTrain::GetTrainState() { else bPN = false; PyDict_SetItemString( dict, "indir_brake", PyGetBool( bPN ) ); + PyDict_SetItemString( dict, "brake_delay_flag", PyGetInt( mvControlled->BrakeDelayFlag )); // other controls PyDict_SetItemString( dict, "ca", PyGetBool( TestFlag( mvOccupied->SecuritySystem.Status, s_aware ) ) ); PyDict_SetItemString( dict, "shp", PyGetBool( TestFlag( mvOccupied->SecuritySystem.Status, s_active ) ) ); From 8b4be0a6456d8ec460c76bf17d1f0e191c98b0fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 23 Sep 2018 16:44:17 +0200 Subject: [PATCH 03/10] Added BrakeOpModeFlag to Python dictionary --- Train.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Train.cpp b/Train.cpp index 9972622a..3add1665 100644 --- a/Train.cpp +++ b/Train.cpp @@ -478,6 +478,7 @@ PyObject *TTrain::GetTrainState() { bPN = false; PyDict_SetItemString( dict, "indir_brake", PyGetBool( bPN ) ); PyDict_SetItemString( dict, "brake_delay_flag", PyGetInt( mvControlled->BrakeDelayFlag )); + PyDict_SetItemString( dict, "brake_op_mode_flag", PyGetInt( mvControlled->BrakeOpModeFlag )); // other controls PyDict_SetItemString( dict, "ca", PyGetBool( TestFlag( mvOccupied->SecuritySystem.Status, s_aware ) ) ); PyDict_SetItemString( dict, "shp", PyGetBool( TestFlag( mvOccupied->SecuritySystem.Status, s_active ) ) ); From 001d202055c81d8b4e594f3edca3339ccd348be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=B3lik=20Uszasty?= Date: Sun, 23 Sep 2018 16:55:28 +0200 Subject: [PATCH 04/10] Added cab control for BrakeOpModeFlag --- Train.cpp | 9 +++++++++ Train.h | 1 + 2 files changed, 10 insertions(+) diff --git a/Train.cpp b/Train.cpp index 3add1665..b0353901 100644 --- a/Train.cpp +++ b/Train.cpp @@ -5494,6 +5494,7 @@ bool TTrain::Update( double const Deltatime ) ggBrakeProfileCtrl.Update(); ggBrakeProfileG.Update(); ggBrakeProfileR.Update(); + ggBrakeOperationModeCtrl.Update(); ggMaxCurrentCtrl.Update(); // NBMX wrzesien 2003 - drzwi ggDoorLeftButton.Update(); @@ -6715,6 +6716,7 @@ void TTrain::clear_cab_controls() ggBrakeProfileCtrl.Clear(); ggBrakeProfileG.Clear(); ggBrakeProfileR.Clear(); + ggBrakeOperationModeCtrl.Clear(); ggMaxCurrentCtrl.Clear(); ggMainOffButton.Clear(); ggMainOnButton.Clear(); @@ -7081,6 +7083,12 @@ void TTrain::set_cab_controls() { 1.0 : 0.0 ); } + if (ggBrakeOperationModeCtrl.SubModel != nullptr) { + ggBrakeOperationModeCtrl.PutValue( + (mvOccupied->BrakeOpModeFlag > 0 ? + log2(mvOccupied->BrakeOpModeFlag) : + 0)); + } // alarm chain ggAlarmChain.PutValue( mvControlled->AlarmChainFlag ? @@ -7272,6 +7280,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { "brakeprofile_sw:", ggBrakeProfileCtrl }, { "brakeprofileg_sw:", ggBrakeProfileG }, { "brakeprofiler_sw:", ggBrakeProfileR }, + { "brakeopmode_sw:", ggBrakeOperationModeCtrl }, { "maxcurrent_sw:", ggMaxCurrentCtrl }, { "main_off_bt:", ggMainOffButton }, { "main_on_bt:", ggMainOnButton }, diff --git a/Train.h b/Train.h index f0049c24..4c19b5d2 100644 --- a/Train.h +++ b/Train.h @@ -366,6 +366,7 @@ public: // reszta może by?publiczna TGauge ggBrakeProfileCtrl; // nastawiacz GPR - przelacznik obrotowy TGauge ggBrakeProfileG; // nastawiacz GP - hebelek towarowy TGauge ggBrakeProfileR; // nastawiacz PR - hamowanie dwustopniowe + TGauge ggBrakeOperationModeCtrl; //przełącznik trybu pracy PS/PN/EP/MED TGauge ggMaxCurrentCtrl; From 3d64d7fb9f6f1f9f5dd0bde4031755d7e2dc1b44 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 23 Sep 2018 21:37:29 +0200 Subject: [PATCH 05/10] minor bug fixes --- Event.cpp | 4 +++- Train.cpp | 14 +++++++++----- scene.cpp | 11 ++++++----- version.h | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Event.cpp b/Event.cpp index 9eb39692..498d231f 100644 --- a/Event.cpp +++ b/Event.cpp @@ -1017,7 +1017,9 @@ logvalues_event::export_as_text_( std::ostream &Output ) const { void multi_event::init() { - init_targets( simulation::Memory, "memory cell" ); + auto const conditiontchecksmemcell { m_conditions.flags & ( flags::text | flags::value_1 | flags::value_2 ) }; + // not all multi-events have memory cell checks, for the ones which don't we can keep quiet about it + init_targets( simulation::Memory, "memory cell", ( false == conditiontchecksmemcell ) ); if( m_ignored ) { // legacy compatibility behaviour, instead of disabling the event we disable the memory cell comparison test m_conditions.flags &= ~( flags::text | flags::value_1 | flags::value_2 ); diff --git a/Train.cpp b/Train.cpp index b0353901..42a378eb 100644 --- a/Train.cpp +++ b/Train.cpp @@ -1325,9 +1325,11 @@ void TTrain::OnCommand_trainbrakeoperationmodeincrease(TTrain *Train, command_da // audio feedback Train->dsbPneumaticSwitch.play(); // visual feedback - // NOTE: there's no button for brake operation mode switch - // TBD, TODO: add brake operation mode switch? - } + Train->ggBrakeOperationModeCtrl.UpdateValue( + Train->mvOccupied->BrakeOpModeFlag > 0 ? + std::log2( Train->mvOccupied->BrakeOpModeFlag ) : + 0 ); + } } } @@ -1341,8 +1343,10 @@ void TTrain::OnCommand_trainbrakeoperationmodedecrease(TTrain *Train, command_da // audio feedback Train->dsbPneumaticSwitch.play(); // visual feedback - // NOTE: there's no button for brake operation mode switch - // TBD, TODO: add brake operation mode switch? + Train->ggBrakeOperationModeCtrl.UpdateValue( + Train->mvOccupied->BrakeOpModeFlag > 0 ? + std::log2( Train->mvOccupied->BrakeOpModeFlag ) : + 0 ); } } } diff --git a/scene.cpp b/scene.cpp index 45558a94..29da186f 100644 --- a/scene.cpp +++ b/scene.cpp @@ -233,7 +233,8 @@ basic_cell::deserialize( std::istream &Input ) { } // cell activation flag m_active = ( - ( false == m_shapesopaque.empty() ) + ( true == m_active ) + || ( false == m_shapesopaque.empty() ) || ( false == m_shapestranslucent.empty() ) || ( false == m_lines.empty() ) ); } @@ -1091,12 +1092,12 @@ basic_region::deserialize( std::string const &Scenariofile ) { auto sectioncount { sn_utils::ld_uint32( input ) }; while( sectioncount-- ) { // section index, followed by section data size, followed by section data - auto *§ion { m_sections[ sn_utils::ld_uint32( input ) ] }; + auto const sectionindex { sn_utils::ld_uint32( input ) }; auto const sectionsize { sn_utils::ld_uint32( input ) }; - if( section == nullptr ) { - section = new basic_section(); + if( m_sections[ sectionindex ] == nullptr ) { + m_sections[ sectionindex ] = new basic_section(); } - section->deserialize( input ); + m_sections[ sectionindex ]->deserialize( input ); } return true; diff --git a/version.h b/version.h index 2140e45d..944d092c 100644 --- a/version.h +++ b/version.h @@ -2,4 +2,4 @@ #define VERSION_MAJOR 18 #define VERSION_MINOR 923 -#define VERSION_REVISION 0 +#define VERSION_REVISION 1 From 308bea337d6fbdfad0ad2392857fc01d4d7879a5 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Mon, 24 Sep 2018 22:01:14 +0200 Subject: [PATCH 06/10] virtual opengl color, overcast level based tweaks to light glare and freespot strength, precipitation based fog density adjustment, overcast level included in the light activation threshold --- AnimModel.cpp | 4 +-- Segment.cpp | 5 +-- Segment.h | 2 ++ maszyna.vcxproj.filters | 6 ++++ openglcolor.cpp | 13 ++++++++ openglcolor.h | 69 +++++++++++++++++++++++++++++++++++++++++ openglgeometrybank.cpp | 1 + renderer.cpp | 29 ++++++++--------- stdafx.h | 1 + uilayer.cpp | 2 +- 10 files changed, 113 insertions(+), 19 deletions(-) create mode 100644 openglcolor.cpp create mode 100644 openglcolor.h diff --git a/AnimModel.cpp b/AnimModel.cpp index b77987ce..be263b5b 100644 --- a/AnimModel.cpp +++ b/AnimModel.cpp @@ -584,7 +584,7 @@ void TAnimModel::RaPrepare() case ls_Dark: { // zapalone, gdy ciemno state = ( - Global.fLuminance <= ( + Global.fLuminance - std::max( 0.f, Global.Overcast - 1.f ) <= ( lsLights[ i ] == static_cast( ls_Dark ) ? DefaultDarkThresholdLevel : ( lsLights[ i ] - static_cast( ls_Dark ) ) ) ); @@ -594,7 +594,7 @@ void TAnimModel::RaPrepare() // like ls_dark but off late at night auto const simulationhour { simulation::Time.data().wHour }; state = ( - Global.fLuminance <= ( + Global.fLuminance - std::max( 0.f, Global.Overcast - 1.f ) <= ( lsLights[ i ] == static_cast( ls_Home ) ? DefaultDarkThresholdLevel : ( lsLights[ i ] - static_cast( ls_Home ) ) ) ); diff --git a/Segment.cpp b/Segment.cpp index 5f2e16b4..97b5b877 100644 --- a/Segment.cpp +++ b/Segment.cpp @@ -524,7 +524,8 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori } return true; }; - +/* +// NOTE: legacy leftover, potentially usable (but not really) void TSegment::Render() { Math3D::vector3 pt; @@ -572,5 +573,5 @@ void TSegment::Render() glEnd(); } } - +*/ //--------------------------------------------------------------------------- diff --git a/Segment.h b/Segment.h index 81970cd9..2304a62d 100644 --- a/Segment.h +++ b/Segment.h @@ -117,8 +117,10 @@ public: 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); +/* void Render(); +*/ inline double GetLength() const { diff --git a/maszyna.vcxproj.filters b/maszyna.vcxproj.filters index 761c958b..bdbf15fc 100644 --- a/maszyna.vcxproj.filters +++ b/maszyna.vcxproj.filters @@ -333,6 +333,9 @@ Source Files + + Source Files + @@ -614,6 +617,9 @@ Header Files + + Header Files + diff --git a/openglcolor.cpp b/openglcolor.cpp new file mode 100644 index 00000000..65e36d51 --- /dev/null +++ b/openglcolor.cpp @@ -0,0 +1,13 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "openglcolor.h" + +opengl_color OpenGLColor; diff --git a/openglcolor.h b/openglcolor.h new file mode 100644 index 00000000..a40f0732 --- /dev/null +++ b/openglcolor.h @@ -0,0 +1,69 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +// encapsulation of the fixed pipeline opengl color +class opengl_color { + +public: +// constructors: + opengl_color() = default; + +// methods: + inline + void + color3( glm::vec3 const &Color ) { + return color4( glm::vec4{ Color, 1.f } ); } + inline + void + color3( float const Red, float const Green, float const Blue ) { + return color3( glm::vec3 { Red, Green, Blue } ); } + inline + void + color3( float const *Value ) { + return color3( glm::make_vec3( Value ) ); } + inline + void + color4( glm::vec4 const &Color ) { + if( Color != m_color ) { + m_color = Color; + ::glColor4fv( glm::value_ptr( m_color ) ); } } + inline + void + color4( float const Red, float const Green, float const Blue, float const Alpha ) { + return color4( glm::vec4{ Red, Green, Blue, Alpha } ); } + inline + void + color4( float const *Value ) { + return color4( glm::make_vec4( Value ) ); + } + inline + glm::vec4 const & + data() const { + return m_color; } + inline + float const * + data_array() const { + return glm::value_ptr( m_color ); } + +private: +// members: + glm::vec4 m_color { -1 }; +}; + +extern opengl_color OpenGLColor; + +// NOTE: standard opengl calls re-definitions +#define glColor3f OpenGLColor.color3 +#define glColor3fv OpenGLColor.color3 +#define glColor4f OpenGLColor.color4 +#define glColor4fv OpenGLColor.color4 + +//--------------------------------------------------------------------------- diff --git a/openglgeometrybank.cpp b/openglgeometrybank.cpp index 02fde9db..ba29170e 100644 --- a/openglgeometrybank.cpp +++ b/openglgeometrybank.cpp @@ -341,6 +341,7 @@ opengl_dlgeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stream_ auto const &chunk = gfx::geometry_bank::chunk( Geometry ); ::glNewList( chunkrecord.list, GL_COMPILE ); + ::glColor3f( -1.f, -1.f, -1.f ); // HACK: force the opengl color wrapper to include color call in the display list regardless of currently active color ::glBegin( chunk.type ); for( auto const &vertex : chunk.vertices ) { if( Streams & gfx::stream::normal ) { ::glNormal3fv( glm::value_ptr( vertex.normal ) ); } diff --git a/renderer.cpp b/renderer.cpp index 28e9d0e0..9a0adf7d 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -1058,8 +1058,9 @@ opengl_renderer::setup_drawing( bool const Alpha ) { // setup fog if( Global.fFogEnd > 0 ) { // fog setup + auto const adjustedfogrange { Global.fFogEnd / std::max( 1.f, Global.Overcast * 2.f ) }; ::glFogfv( GL_FOG_COLOR, glm::value_ptr( Global.FogColor ) ); - ::glFogf( GL_FOG_DENSITY, static_cast( 1.0 / Global.fFogEnd ) ); + ::glFogf( GL_FOG_DENSITY, static_cast( 1.0 / adjustedfogrange ) ); ::glEnable( GL_FOG ); } else { ::glDisable( GL_FOG ); } @@ -1461,7 +1462,7 @@ opengl_renderer::Render( world_environment *Environment ) { Environment->m_moon.render(); } // render actual sun and moon - ::glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT | GL_COLOR_BUFFER_BIT ); + ::glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT ); ::glDisable( GL_LIGHTING ); ::glDisable( GL_ALPHA_TEST ); @@ -2529,15 +2530,16 @@ opengl_renderer::Render( TSubModel *Submodel ) { 0.f, 1.f ); // 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 ); + float const distancefactor { std::max( 0.5f, ( Submodel->fSquareMaxDist - TSubModel::fSquareDist ) / Submodel->fSquareMaxDist ) }; + float const precipitationfactor { std::max( 1.f, Global.Overcast - 1.f ) }; if( lightlevel > 0.f ) { // material configuration: - ::glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT | GL_COLOR_BUFFER_BIT | GL_POINT_BIT ); + ::glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_POINT_BIT ); Bind_Material( null_handle ); ::glPointSize( std::max( 3.f, 5.f * distancefactor * anglefactor ) ); - ::glColor4f( Submodel->f4Diffuse[ 0 ], Submodel->f4Diffuse[ 1 ], Submodel->f4Diffuse[ 2 ], lightlevel * anglefactor ); + ::glColor4f( Submodel->f4Diffuse[ 0 ], Submodel->f4Diffuse[ 1 ], Submodel->f4Diffuse[ 2 ], std::min( 1.f, lightlevel * anglefactor * precipitationfactor ) ); ::glDisable( GL_LIGHTING ); ::glEnable( GL_BLEND ); @@ -2580,7 +2582,7 @@ opengl_renderer::Render( TSubModel *Submodel ) { if( Global.fLuminance < Submodel->fLight ) { // material configuration: - ::glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT ); + ::glPushAttrib( GL_ENABLE_BIT ); Bind_Material( null_handle ); ::glDisable( GL_LIGHTING ); @@ -3352,7 +3354,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { } else if( Submodel->eType == TP_FREESPOTLIGHT ) { - if( Global.fLuminance < Submodel->fLight ) { + if( ( Global.fLuminance < Submodel->fLight ) || ( Global.Overcast > 1.f ) ) { // NOTE: we're forced here to redo view angle calculations etc, because this data isn't instanced but stored along with the single mesh // TODO: separate instance data from reusable geometry auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW ); @@ -3367,18 +3369,17 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { 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 - if( Global.Overcast > 1.0 ) { - // increase the glare in rainy/foggy conditions - glarelevel += std::max( 0.f, 0.5f * ( Global.Overcast - 1.f ) ); - } + 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 ) }; // scale it down based on view angle glarelevel *= ( Submodel->fCosViewAngle - Submodel->fCosFalloffAngle ) / ( 1.0f - Submodel->fCosFalloffAngle ); - // reduce the glare in bright daylight - glarelevel = clamp( glarelevel - static_cast(Global.fLuminance), 0.f, 1.f ); if( glarelevel > 0.0f ) { // setup - ::glPushAttrib( GL_CURRENT_BIT | GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_ENABLE_BIT ); + ::glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_ENABLE_BIT ); Bind_Texture( m_glaretexture ); ::glColor4f( Submodel->f4Diffuse[ 0 ], Submodel->f4Diffuse[ 1 ], Submodel->f4Diffuse[ 2 ], glarelevel ); diff --git a/stdafx.h b/stdafx.h index 145bed1f..c59fc775 100644 --- a/stdafx.h +++ b/stdafx.h @@ -104,6 +104,7 @@ #include #include "openglmatrixstack.h" +#include "openglcolor.h" // imgui.h comes with its own operator new which gets wrecked by dbg_new, so we temporarily disable the latter #ifdef DBG_NEW diff --git a/uilayer.cpp b/uilayer.cpp index c5ed4262..94ccfaea 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -161,7 +161,7 @@ ui_layer::render() { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT | GL_COLOR_BUFFER_BIT ); // blendfunc included since 3rd party gui doesn't play nice + glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT ); // blendfunc included since 3rd party gui doesn't play nice glDisable( GL_LIGHTING ); glDisable( GL_DEPTH_TEST ); glDisable( GL_ALPHA_TEST ); From 7c60829f54404074c129e44373384d2f127f1f31 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Fri, 28 Sep 2018 19:16:41 +0200 Subject: [PATCH 07/10] python interpreter refactored to a task queue --- Classes.h | 2 + PyInt.cpp | 817 +++++++++++++++------------------------------- PyInt.h | 137 ++++---- ResourceManager.h | 2 - Train.cpp | 51 ++- Train.h | 8 +- application.cpp | 68 ++-- application.h | 11 +- stdafx.h | 2 + utilities.h | 57 ++++ 10 files changed, 485 insertions(+), 670 deletions(-) diff --git a/Classes.h b/Classes.h index ba1bb3ac..1fe63ed0 100644 --- a/Classes.h +++ b/Classes.h @@ -70,4 +70,6 @@ enum class TCommandType cm_Command // komenda pobierana z komórki }; +using material_handle = int; + #endif diff --git a/PyInt.cpp b/PyInt.cpp index 4ce82105..7a201cfc 100644 --- a/PyInt.cpp +++ b/PyInt.cpp @@ -1,24 +1,56 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + #include "stdafx.h" #include "PyInt.h" #include "globals.h" -#include "parser.h" +#include "application.h" #include "renderer.h" -#include "Model3d.h" -#include "Train.h" #include "Logs.h" -TPythonInterpreter *TPythonInterpreter::_instance = NULL; +void render_task::run() { + // call the renderer + auto *output { PyObject_CallMethod( m_renderer, "render", "O", m_input ) }; + Py_DECREF( m_input ); -//#define _PY_INT_MORE_LOG + if( output != nullptr ) { + auto *outputwidth { PyObject_CallMethod( m_renderer, "get_width", nullptr ) }; + auto *outputheight { PyObject_CallMethod( m_renderer, "get_height", nullptr ) }; + // upload texture data + if( ( outputwidth != nullptr ) + && ( outputheight != nullptr ) ) { -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wwrite-strings" -#endif + GfxRenderer.Bind_Material( m_target ); + // setup texture parameters + ::glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); + ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + ::glTexEnvf( GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.0 ); + // build texture + ::glTexImage2D( + GL_TEXTURE_2D, 0, + GL_RGBA8, + PyInt_AsLong( outputwidth ), PyInt_AsLong( outputheight ), 0, + GL_RGB, GL_UNSIGNED_BYTE, reinterpret_cast( PyString_AsString( output ) ) ); + } + Py_DECREF( outputheight ); + Py_DECREF( outputwidth ); + Py_DECREF( output ); + } + // clean up after yourself + delete this; +} + +// initializes the module. returns true on success +auto python_taskqueue::init() -> bool { -TPythonInterpreter::TPythonInterpreter() -{ - WriteLog("Loading Python ..."); #ifdef _WIN32 if (sizeof(void*) == 8) Py_SetPythonHome("python64"); @@ -31,568 +63,253 @@ TPythonInterpreter::TPythonInterpreter() Py_SetPythonHome("linuxpython"); #endif Py_Initialize(); - _main = PyImport_ImportModule("__main__"); - if (_main == NULL) - { - WriteLog("Cannot import Python module __main__"); + PyEval_InitThreads(); + // save a pointer to the main PyThreadState object + m_mainthread = PyThreadState_Get(); + // do the setup work while we hold the lock + m_main = PyImport_ImportModule("__main__"); + if (m_main == nullptr) { + ErrorLog( "Python Interpreter: __main__ module is missing" ); + goto release_and_exit; } - PyObject *cStringModule = PyImport_ImportModule("cStringIO"); - _stdErr = NULL; - if (cStringModule == NULL) - return; - PyObject *cStringClassName = PyObject_GetAttrString(cStringModule, "StringIO"); - if (cStringClassName == NULL) - return; - PyObject *cString = PyObject_CallObject(cStringClassName, NULL); - if (cString == NULL) - return; - if (PySys_SetObject("stderr", cString) != 0) - return; - _stdErr = cString; + + auto *stringiomodule { PyImport_ImportModule( "cStringIO" ) }; + auto *stringioclassname { ( + stringiomodule != nullptr ? + PyObject_GetAttrString( stringiomodule, "StringIO" ) : + nullptr ) }; + auto *stringioobject { ( + stringioclassname != nullptr ? + PyObject_CallObject( stringioclassname, nullptr ) : + nullptr ) }; + m_error = { ( + stringioobject == nullptr ? nullptr : + PySys_SetObject( "stderr", stringioobject ) != 0 ? nullptr : + stringioobject ) }; + + if( m_error == nullptr ) { goto release_and_exit; } + + if( false == run_file( "abstractscreenrenderer" ) ) { goto release_and_exit; } + + // release the lock + PyEval_ReleaseLock(); + + WriteLog( "Python Interpreter setup complete" ); + + // init workers + for( auto &worker : m_workers ) { + + auto *openglcontextwindow { Application.window( -1 ) }; + worker = + std::make_unique( + &python_taskqueue::run, this, + openglcontextwindow, std::ref( m_tasks ), std::ref( m_condition ), std::ref( m_exit ) ); + + if( worker == nullptr ) { return false; } + } + + return true; + +release_and_exit: + PyEval_ReleaseLock(); + return false; } -TPythonInterpreter *TPythonInterpreter::getInstance() -{ - if (!_instance) - { - _instance = new TPythonInterpreter(); +// shuts down the module +void python_taskqueue::exit() { + // let the workers know we're done with them + m_exit = true; + m_condition.notify_all(); + // let them free up their shit before we proceed + for( auto const &worker : m_workers ) { + worker->join(); } - return _instance; + // get rid of the leftover tasks + // with the workers dead we don't have to worry about concurrent access anymore + for( auto *task : m_tasks.data ) { + delete task; + } + // take a bow + PyEval_AcquireLock(); + PyThreadState_Swap( m_mainthread ); + Py_Finalize(); } -void -TPythonInterpreter::killInstance() { +// adds specified task along with provided collection of data to the work queue. returns true on success +auto python_taskqueue::insert( task_request const &Task ) -> bool { - delete _instance; -} + if( ( Task.renderer.empty() ) + || ( Task.input == nullptr ) + || ( Task.target == null_handle ) ) { return false; } -bool TPythonInterpreter::loadClassFile( std::string const &lookupPath, std::string const &className ) -{ - std::set::const_iterator it = _classes.find(className); - if (it == _classes.end()) + auto *renderer { fetch_renderer( Task.renderer ) }; + if( renderer == nullptr ) { return false; } + // acquire a lock on the task queue and add a new task { - FILE *sourceFile = _getFile(lookupPath, className); - if (sourceFile != nullptr) - { - fseek(sourceFile, 0, SEEK_END); - auto const fsize = ftell(sourceFile); - char *buffer = (char *)calloc(fsize + 1, sizeof(char)); - fseek(sourceFile, 0, SEEK_SET); - auto const freaded = fread(buffer, sizeof(char), fsize, sourceFile); - buffer[freaded] = 0; // z jakiegos powodu czytamy troche mniej i trzczeba dodac konczace -// zero do bufora (mimo ze calloc teoretycznie powiniene zwrocic -// wyzerowana pamiec) -#ifdef _PY_INT_MORE_LOG - char buf[255]; - sprintf(buf, "readed %d / %d characters for %s", freaded, fsize, className); - WriteLog(buf); -#endif // _PY_INT_MORE_LOG - fclose(sourceFile); - if (PyRun_SimpleString(buffer) != 0) - { - handleError(); - return false; - } - _classes.insert( className ); -/* - char *classNameToRemember = (char *)calloc(strlen(className) + 1, sizeof(char)); - strcpy(classNameToRemember, className); - _classes.insert(classNameToRemember); -*/ - free(buffer); - return true; - } - return false; + std::lock_guard lock( m_tasks.mutex ); + m_tasks.data.emplace_back( new render_task( renderer, Task.input, Task.target ) ); } + // potentially wake a worker to handle the new task + m_condition.notify_one(); + // all done return true; } -PyObject *TPythonInterpreter::newClass( std::string const &className ) -{ - return newClass(className, NULL); +// executes python script stored in specified file. returns true on success +auto python_taskqueue::run_file( std::string const &File, std::string const &Path ) -> bool { + + auto const lookup { FileExists( { Path + File, "python/local/" + File }, { ".py" } ) }; + if( lookup.first.empty() ) { return false; } + + std::ifstream inputfile { lookup.first + lookup.second }; + std::string input; + input.assign( std::istreambuf_iterator( inputfile ), std::istreambuf_iterator() ); + + if( PyRun_SimpleString( input.c_str() ) != 0 ) { + error(); + return false; + } + + return true; } -FILE *TPythonInterpreter::_getFile( std::string const &lookupPath, std::string const &className ) -{ - if( false == lookupPath.empty() ) { - std::string const sourcefilepath = lookupPath + className + ".py"; - FILE *file = fopen( sourcefilepath.c_str(), "r" ); -#ifdef _PY_INT_MORE_LOG - WriteLog( sourceFilePath ); -#endif // _PY_INT_MORE_LOG - if( nullptr != file ) { return file; } - } - std::string sourcefilepath = "python/local/" + className + ".py"; - FILE *file = fopen( sourcefilepath.c_str(), "r" ); -#ifdef _PY_INT_MORE_LOG - WriteLog( sourceFilePath ); -#endif // _PY_INT_MORE_LOG - return file; // either the file, or a nullptr on fail -/* - char *sourceFilePath; - if (lookupPath != NULL) - { - sourceFilePath = (char *)calloc(strlen(lookupPath) + strlen(className) + 4, sizeof(char)); - strcat(sourceFilePath, lookupPath); - strcat(sourceFilePath, className); - strcat(sourceFilePath, ".py"); +auto python_taskqueue::fetch_renderer( std::string const Renderer ) ->PyObject * { - FILE *file = fopen(sourceFilePath, "r"); -#ifdef _PY_INT_MORE_LOG - WriteLog(sourceFilePath); -#endif // _PY_INT_MORE_LOG - free(sourceFilePath); - if (file != NULL) - { - return file; + auto const lookup { m_renderers.find( Renderer ) }; + if( lookup != std::end( m_renderers ) ) { + return lookup->second; + } + // try to load specified renderer class + auto const path { substr_path( Renderer ) }; + auto const file { Renderer.substr( path.size() ) }; + PyObject *renderer { nullptr }; + if( m_main == nullptr ) { + ErrorLog( "Python Renderer: __main__ module is missing" ); + goto cache_and_return; + } + + PyEval_AcquireLock(); + { + PyObject *rendererarguments{ nullptr }; + if( false == run_file( file, path ) ) { + goto cache_and_return; + } + auto *renderername{ PyObject_GetAttrString( m_main, file.c_str() ) }; + if( renderername == nullptr ) { + ErrorLog( "Python Renderer: class \"" + file + "\" not defined" ); + goto cache_and_return; + } + rendererarguments = Py_BuildValue( "(s)", path.c_str() ); + if( rendererarguments == nullptr ) { + ErrorLog( "Python Renderer: failed to create initialization arguments" ); + goto cache_and_return; + } + renderer = PyObject_CallObject( renderername, rendererarguments ); + + if( PyErr_Occurred() != nullptr ) { + error(); + renderer = nullptr; + } + +cache_and_return: + // clean up after yourself + if( rendererarguments != nullptr ) { + Py_DECREF( rendererarguments ); } } - char *basePath = "python/local/"; - sourceFilePath = (char *)calloc(strlen(basePath) + strlen(className) + 4, sizeof(char)); - strcat(sourceFilePath, basePath); - strcat(sourceFilePath, className); - strcat(sourceFilePath, ".py"); - - FILE *file = fopen(sourceFilePath, "r"); -#ifdef _PY_INT_MORE_LOG - WriteLog(sourceFilePath); -#endif // _PY_INT_MORE_LOG - free(sourceFilePath); - if (file != NULL) - { - return file; - } - return NULL; -*/ + PyEval_ReleaseLock(); + // cache the failures as well so we don't try again on subsequent requests + m_renderers.emplace( Renderer, renderer ); + return renderer; } -void TPythonInterpreter::handleError() -{ -#ifdef _PY_INT_MORE_LOG - WriteLog("Python Error occured"); -#endif // _PY_INT_MORE_LOG - if (_stdErr != NULL) - { // std err pythona jest buforowane +void python_taskqueue::run( GLFWwindow *Context, rendertask_sequence &Tasks, threading::condition_variable &Condition, std::atomic &Exit ) { + + glfwMakeContextCurrent( Context ); + // create a state object for this thread + PyEval_AcquireLock(); + auto *threadstate { PyThreadState_New( m_mainthread->interp ) }; + PyEval_ReleaseLock(); + + render_task *task { nullptr }; + + while( false == Exit.load() ) { + // regardless of the reason we woke up prime the spurious wakeup flag for the next time + Condition.spurious( true ); + // keep working as long as there's any scheduled tasks + do { + task = nullptr; + // acquire a lock on the task queue and potentially grab a task from it + { + std::lock_guard lock( Tasks.mutex ); + if( false == Tasks.data.empty() ) { + // fifo + task = Tasks.data.front(); + Tasks.data.pop_front(); + } + } + if( task != nullptr ) { + // swap in my thread state + PyEval_AcquireLock(); + PyThreadState_Swap( threadstate ); + // execute python code + task->run(); + error(); + // clear the thread state + PyThreadState_Swap( nullptr ); + PyEval_ReleaseLock(); + } + // TBD, TODO: add some idle time between tasks in case we're on a single thread cpu? + } while( task != nullptr ); + // if there's nothing left to do wait until there is + // but check every now and then on your own to minimize potential deadlock situations + Condition.wait_for( std::chrono::seconds( 5 ) ); + } + // clean up thread state data + PyEval_AcquireLock(); + PyThreadState_Swap( nullptr ); + PyThreadState_Clear( threadstate ); + PyThreadState_Delete( threadstate ); + PyEval_ReleaseLock(); +} + +void +python_taskqueue::error() { + + if( PyErr_Occurred() == nullptr ) { return; } + + if( m_error != nullptr ) { + // std err pythona jest buforowane PyErr_Print(); - PyObject *bufferContent = PyObject_CallMethod(_stdErr, "getvalue", NULL); - PyObject_CallMethod(_stdErr, "truncate", "i", 0); // czyscimy bufor na kolejne bledy - WriteLog(PyString_AsString(bufferContent)); + auto *errortext { PyObject_CallMethod( m_error, "getvalue", nullptr ) }; + ErrorLog( PyString_AsString( errortext ) ); + // czyscimy bufor na kolejne bledy + PyObject_CallMethod( m_error, "truncate", "i", 0 ); } - else - { // nie dziala buffor pythona - if (PyErr_Occurred() != NULL) - { - PyObject *ptype, *pvalue, *ptraceback; - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - if (ptype == NULL) - { - WriteLog("Don't konw how to handle NULL exception"); - } - PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); - if (ptype == NULL) - { - WriteLog("Don't konw how to handle NULL exception"); - } - PyObject *pStrType = PyObject_Str(ptype); - if (pStrType != NULL) - { - WriteLog(PyString_AsString(pStrType)); - } - WriteLog(PyString_AsString(pvalue)); - PyObject *pStrTraceback = PyObject_Str(ptraceback); - if (pStrTraceback != NULL) - { - WriteLog(PyString_AsString(pStrTraceback)); - } - else - { - WriteLog("Python Traceback cannot be shown"); - } + else { + // nie dziala buffor pythona + PyObject *type, *value, *traceback; + PyErr_Fetch( &type, &value, &traceback ); + if( type == nullptr ) { + ErrorLog( "Python Interpreter: don't know how to handle null exception" ); } - else - { -#ifdef _PY_INT_MORE_LOG - WriteLog("Called python error handler when no error occured!"); -#endif // _PY_INT_MORE_LOG + PyErr_NormalizeException( &type, &value, &traceback ); + if( type == nullptr ) { + ErrorLog( "Python Interpreter: don't know how to handle null exception" ); + } + auto *typetext { PyObject_Str( type ) }; + if( typetext != nullptr ) { + ErrorLog( PyString_AsString( typetext ) ); + } + if( value != nullptr ) { + ErrorLog( PyString_AsString( value ) ); + } + auto *tracebacktext { PyObject_Str( traceback ) }; + if( tracebacktext != nullptr ) { + WriteLog( PyString_AsString( tracebacktext ) ); + } + else { + WriteLog( "Python Interpreter: failed to retrieve the stack traceback" ); } } } -PyObject *TPythonInterpreter::newClass(std::string const &className, PyObject *argsTuple) -{ - if (_main == NULL) - { - WriteLog("main turned into null"); - return NULL; - } - PyObject *classNameObj = PyObject_GetAttrString(_main, className.c_str()); - if (classNameObj == NULL) - { -#ifdef _PY_INT_MORE_LOG - char buf[255]; - sprintf(buf, "Python class %s not defined!", className); - WriteLog(buf); -#endif // _PY_INT_MORE_LOG - return NULL; - } - PyObject *object = PyObject_CallObject(classNameObj, argsTuple); - - if (PyErr_Occurred() != NULL) - { - handleError(); - return NULL; - } - return object; -} - -TPythonScreenRenderer::TPythonScreenRenderer(int textureId, PyObject *renderer) -{ - _textureId = textureId; - _pyRenderer = renderer; -} - -void TPythonScreenRenderer::updateTexture() -{ - int width, height; - if (_pyWidth == NULL || _pyHeight == NULL) - { - WriteLog("Unknown python texture size!"); - return; - } - width = PyInt_AsLong(_pyWidth); - height = PyInt_AsLong(_pyHeight); - if (_pyTexture != NULL) - { - char *textureData = PyString_AsString(_pyTexture); - if (textureData != NULL) - { -#ifdef _PY_INT_MORE_LOG - char buff[255]; - sprintf(buff, "Sending texture id: %d w: %d h: %d", _textureId, width, height); - WriteLog(buff); -#endif // _PY_INT_MORE_LOG -/* - glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); - glPixelStorei( GL_PACK_ALIGNMENT, 1 ); -*/ - GfxRenderer.Bind_Material(_textureId); - // setup texture parameters - glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); - glTexEnvf( GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.0 ); - // build texture - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData ); - -#ifdef _PY_INT_MORE_LOG - GLenum status = glGetError(); - switch (status) - { - case GL_INVALID_ENUM: - WriteLog("An unacceptable value is specified for an enumerated argument. The " - "offending function is ignored, having no side effect other than to set " - "the error flag."); - break; - case GL_INVALID_VALUE: - WriteLog("A numeric argument is out of range. The offending function is ignored, " - "having no side effect other than to set the error flag."); - break; - case GL_INVALID_OPERATION: - WriteLog("The specified operation is not allowed in the current state. The " - "offending function is ignored, having no side effect other than to set " - "the error flag."); - break; - case GL_NO_ERROR: - WriteLog("No error has been recorded. The value of this symbolic constant is " - "guaranteed to be zero."); - break; - case GL_STACK_OVERFLOW: - WriteLog("This function would cause a stack overflow. The offending function is " - "ignored, having no side effect other than to set the error flag."); - break; - case GL_STACK_UNDERFLOW: - WriteLog("This function would cause a stack underflow. The offending function is " - "ignored, having no side effect other than to set the error flag."); - break; - case GL_OUT_OF_MEMORY: - WriteLog("There is not enough memory left to execute the function. The state of " - "OpenGL is undefined, except for the state of the error flags, after this " - "error is recorded."); - break; - }; -#endif // _PY_INT_MORE_LOG - } - else - { - WriteLog("RAW python texture data is NULL!"); - } - } - else - { - WriteLog("Python texture object is NULL!"); - } -} - -void TPythonScreenRenderer::render(PyObject *trainState) -{ -#ifdef _PY_INT_MORE_LOG - WriteLog("Python rendering texture ..."); -#endif // _PY_INT_MORE_LOG - _pyTexture = PyObject_CallMethod(_pyRenderer, "render", "O", trainState); - - if (_pyTexture == NULL) - { - TPythonInterpreter::getInstance()->handleError(); - } - else - { - _pyWidth = PyObject_CallMethod(_pyRenderer, "get_width", NULL); - if (_pyWidth == NULL) - { - TPythonInterpreter::getInstance()->handleError(); - } - _pyHeight = PyObject_CallMethod(_pyRenderer, "get_height", NULL); - if (_pyHeight == NULL) - { - TPythonInterpreter::getInstance()->handleError(); - } - } -} - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -TPythonScreenRenderer::~TPythonScreenRenderer() -{ -#ifdef _PY_INT_MORE_LOG - WriteLog("PythonScreenRenderer descturctor called"); -#endif // _PY_INT_MORE_LOG - if (_pyRenderer != NULL) - { - Py_CLEAR(_pyRenderer); - } - cleanup(); -#ifdef _PY_INT_MORE_LOG - WriteLog("PythonScreenRenderer desctructor finished"); -#endif // _PY_INT_MORE_LOG -} - -void TPythonScreenRenderer::cleanup() -{ - if (_pyTexture != NULL) - { - Py_CLEAR(_pyTexture); - _pyTexture = NULL; - } - if (_pyWidth != NULL) - { - Py_CLEAR(_pyWidth); - _pyWidth = NULL; - } - if (_pyHeight != NULL) - { - Py_CLEAR(_pyHeight); - _pyHeight = NULL; - } -} - -void TPythonScreens::reset(void *train) -{ - _terminationFlag = true; - if (_thread != NULL) - { -// WriteLog("Awaiting python thread to end"); - _thread->join(); - delete _thread; - _thread = nullptr; - } - _terminationFlag = false; - _cleanupReadyFlag = false; - _renderReadyFlag = false; - for (std::vector::iterator i = _screens.begin(); i != _screens.end(); - ++i) - { - delete *i; - } -#ifdef _PY_INT_MORE_LOG - WriteLog("Clearing renderer vector"); -#endif // _PY_INT_MORE_LOG - _screens.clear(); - _train = train; -} - -void TPythonScreens::init(cParser &parser, TModel3d *model, std::string const &name, int const cab) -{ - std::string asSubModelName, asPyClassName; - parser.getTokens( 2, false ); - parser - >> asSubModelName - >> asPyClassName; - std::string subModelName = ToLower( asSubModelName ); - std::string pyClassName = ToLower( asPyClassName ); - TSubModel *subModel = model->GetFromName(subModelName); - if (subModel == NULL) - { - WriteLog( "Python Screen: submodel " + subModelName + " not found - Ignoring screen" ); - return; // nie ma takiego sub modelu w danej kabinie pomijamy - } - auto textureId = subModel->GetMaterial(); - if (textureId <= 0) - { - WriteLog( "Python Screen: invalid texture id " + std::to_string(textureId) + " - Ignoring screen" ); - return; // sub model nie posiada tekstury lub tekstura wymienna - nie obslugiwana - } - TPythonInterpreter *python = TPythonInterpreter::getInstance(); - python->loadClassFile(_lookupPath, pyClassName); - PyObject *args = Py_BuildValue("(ssi)", _lookupPath.c_str(), name.c_str(), cab); - if (args == NULL) - { - WriteLog("Python Screen: cannot create __init__ arguments"); - return; - } - PyObject *pyRenderer = python->newClass(pyClassName, args); - Py_CLEAR(args); - if (pyRenderer == NULL) - { - WriteLog( "Python Screen: null renderer for " + pyClassName + " - Ignoring screen" ); - return; // nie mozna utworzyc obiektu Pythonowego - } - m_updaterate = Global.PythonScreenUpdateRate; - TPythonScreenRenderer *renderer = new TPythonScreenRenderer(textureId, pyRenderer); - _screens.push_back(renderer); - WriteLog( "Created python screen " + pyClassName + " on submodel " + subModelName + " (" + std::to_string(textureId) + ")" ); -} - -void TPythonScreens::update() -{ - if (!_renderReadyFlag) - { - return; - } - _renderReadyFlag = false; - for (std::vector::iterator i = _screens.begin(); i != _screens.end(); - ++i) - { - (*i)->updateTexture(); - } - _cleanupReadyFlag = true; -} - -void TPythonScreens::setLookupPath(std::string const &path) -{ - _lookupPath = path; - replace_slashes( _lookupPath ); -} - -TPythonScreens::TPythonScreens() -{ - TPythonInterpreter::getInstance()->loadClassFile("", "abstractscreenrenderer"); -} - -TPythonScreens::~TPythonScreens() -{ -#ifdef _PY_INT_MORE_LOG - WriteLog("Called python sceeens destructor"); -#endif // _PY_INT_MORE_LOG - reset(NULL); -/* - if (_lookupPath != NULL) - { -#ifdef _PY_INT_MORE_LOG - WriteLog("Freeing lookup path"); -#endif // _PY_INT_MORE_LOG - free(_lookupPath); - } -*/ -} - -void TPythonScreens::run() -{ - while (1) - { - m_updatestopwatch.start(); - if (_terminationFlag) - { - return; - } - TTrain *train = (TTrain *)_train; - _trainState = train->GetTrainState(); - if (_terminationFlag) - { - _freeTrainState(); - return; - } - for (std::vector::iterator i = _screens.begin(); - i != _screens.end(); ++i) - { - (*i)->render(_trainState); - } - _freeTrainState(); - if (_terminationFlag) - { - _cleanup(); - return; - } - _renderReadyFlag = true; - m_updatestopwatch.stop(); - while (!_cleanupReadyFlag && !_terminationFlag) - { - auto const sleeptime { - std::max( - 100, - m_updaterate - static_cast( m_updatestopwatch.average() ) ) }; -#ifdef _WIN32 - Sleep( sleeptime ); -#elif __linux__ - usleep( sleeptime * 1000 ); -#endif - } - if (_terminationFlag) - { - return; - } - _cleanup(); - } -} - -void TPythonScreens::finish() -{ - // nothing to do here, proper clean up takes place afterwards -} - -void ScreenRendererThread(TPythonScreens* renderer) -{ - renderer->run(); - renderer->finish(); -#ifdef _PY_INT_MORE_LOG - WriteLog("Python Screen Renderer Thread Ends"); -#endif // _PY_INT_MORE_LOG -} - -void TPythonScreens::start() -{ - if (_screens.size() > 0) - { - _thread = new std::thread(ScreenRendererThread, this); - } -} - -void TPythonScreens::_cleanup() -{ - _cleanupReadyFlag = false; - for (std::vector::iterator i = _screens.begin(); i != _screens.end(); - ++i) - { - (*i)->cleanup(); - } -} - -void TPythonScreens::_freeTrainState() -{ - if (_trainState != NULL) - { - PyDict_Clear(_trainState); - Py_CLEAR(_trainState); - _trainState = NULL; - } -} \ No newline at end of file diff --git a/PyInt.h b/PyInt.h index bb4f8281..1bf215b9 100644 --- a/PyInt.h +++ b/PyInt.h @@ -1,13 +1,13 @@ -#ifndef PyIntH -#define PyIntH +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ -#ifdef _POSIX_C_SOURCE -#undef _POSIX_C_SOURCE -#endif - -#ifdef _XOPEN_SOURCE -#undef _XOPEN_SOURCE -#endif +#pragma once #ifdef _DEBUG #undef _DEBUG // bez tego macra Py_DECREF powoduja problemy przy linkowaniu @@ -17,78 +17,69 @@ #include "Python.h" #endif #include "Classes.h" -#include "timer.h" #define PyGetFloat(param) PyFloat_FromDouble(param >= 0 ? param : -param) #define PyGetFloatS(param) PyFloat_FromDouble(param) #define PyGetInt(param) PyInt_FromLong(param) -#define PyGetFloatS(param) PyFloat_FromDouble(param) #define PyGetBool(param) param ? Py_True : Py_False #define PyGetString(param) PyString_FromString(param) -class TPythonInterpreter -{ - protected: - TPythonInterpreter(); - ~TPythonInterpreter() {} - static TPythonInterpreter *_instance; - std::set _classes; - PyObject *_main; - PyObject *_stdErr; - FILE *_getFile( std::string const &lookupPath, std::string const &className ); +// TODO: extract common base and inherit specialization from it +class render_task { - public: - static TPythonInterpreter *getInstance(); - static void killInstance(); - bool loadClassFile( std::string const &lookupPath, std::string const &className ); - PyObject *newClass( std::string const &className ); - PyObject *newClass( std::string const &className, PyObject *argsTuple ); - void handleError(); -}; - -class TPythonScreenRenderer -{ - protected: - PyObject *_pyRenderer; - PyObject *_pyTexture; - int _textureId; - PyObject *_pyWidth; - PyObject *_pyHeight; - - public: - TPythonScreenRenderer(int textureId, PyObject *renderer); - ~TPythonScreenRenderer(); - void render(PyObject *trainState); - void cleanup(); - void updateTexture(); -}; - -class TPythonScreens -{ - protected: - bool _cleanupReadyFlag{ false }; - bool _renderReadyFlag{ false }; - bool _terminationFlag{ false }; - std::thread *_thread{ nullptr }; - std::vector _screens; - std::string _lookupPath; - void *_train; - void _cleanup(); - void _freeTrainState(); - PyObject *_trainState; - int m_updaterate { 200 }; - Timer::stopwatch m_updatestopwatch; - - public: - void reset(void *train); - void setLookupPath(std::string const &path); - void init(cParser &parser, TModel3d *model, std::string const &name, int const cab); - void update(); - TPythonScreens(); - ~TPythonScreens(); +public: +// constructors + render_task( PyObject *Renderer, PyObject *Input, material_handle Target ) : + m_renderer( Renderer ), m_input( Input ), m_target( Target ) + {} +// methods void run(); - void start(); - void finish(); + +private: +// members + PyObject *m_renderer {nullptr}; + PyObject *m_input { nullptr }; + material_handle m_target { null_handle }; }; -#endif // PyIntH \ No newline at end of file +class python_taskqueue { + +public: +// types + struct task_request { + + std::string const &renderer; + PyObject *input; + material_handle target; + }; +// constructors + python_taskqueue() = default; +// methods + // initializes the module. returns true on success + auto init() -> bool; + // shuts down the module + void exit(); + // adds specified task along with provided collection of data to the work queue. returns true on success + auto insert( task_request const &Task ) -> bool; + // executes python script stored in specified file. returns true on success + auto run_file( std::string const &File, std::string const &Path = "" ) -> bool; + +private: +// types + static int const WORKERCOUNT { 1 }; + using worker_array = std::array, WORKERCOUNT >; + using rendertask_sequence = threading::lockable< std::deque >; +// methods + auto fetch_renderer( std::string const Renderer ) -> PyObject *; + void run( GLFWwindow *Context, rendertask_sequence &Tasks, threading::condition_variable &Condition, std::atomic &Exit ); + void error(); +// members + PyObject *m_main { nullptr }; + PyObject *m_error { nullptr }; + PyThreadState *m_mainthread{ nullptr }; + worker_array m_workers; + threading::condition_variable m_condition; // wakes up the workers + std::atomic m_exit { false }; // signals the workers to quit + std::unordered_map m_renderers; // cache of python classes + rendertask_sequence m_tasks; +}; diff --git a/ResourceManager.h b/ResourceManager.h index 81e631a8..f75d8b43 100644 --- a/ResourceManager.h +++ b/ResourceManager.h @@ -9,8 +9,6 @@ http://mozilla.org/MPL/2.0/. #pragma once -int const null_handle = 0; - enum class resource_state { none, loading, diff --git a/Train.cpp b/Train.cpp index 42a378eb..bded4fe9 100644 --- a/Train.cpp +++ b/Train.cpp @@ -28,6 +28,7 @@ http://mozilla.org/MPL/2.0/. #include "dynobj.h" #include "mtable.h" #include "Console.h" +#include "application.h" namespace input { @@ -449,6 +450,7 @@ PyObject *TTrain::GetTrainState() { return nullptr; } + PyDict_SetItemString( dict, "name", PyGetString( DynamicObject->asName.c_str() ) ); PyDict_SetItemString( dict, "cab", PyGetInt( mover->ActiveCab ) ); // basic systems state data PyDict_SetItemString( dict, "battery", PyGetBool( mvControlled->Battery ) ); @@ -469,7 +471,7 @@ PyObject *TTrain::GetTrainState() { PyDict_SetItemString( dict, "dir_brake", PyGetBool( bEP ) ); bool bPN; if( ( typeid( *mvControlled->Hamulec ) == typeid( TLSt ) ) - || ( typeid( *mvControlled->Hamulec ) == typeid( TEStED ) ) ) { + || ( typeid( *mvControlled->Hamulec ) == typeid( TEStED ) ) ) { TBrake* temp_ham = mvControlled->Hamulec.get(); bPN = ( static_cast( temp_ham )->GetEDBCP() > 0.2 ); @@ -4682,7 +4684,6 @@ bool TTrain::Update( double const Deltatime ) } } - tor = DynamicObject->GetTrack(); // McZapkie-180203 // McZapkie: predkosc wyswietlana na tachometrze brana jest z obrotow kol auto const maxtacho { 3.0 }; fTachoVelocity = static_cast( std::min( std::abs(11.31 * mvControlled->WheelDiameter * mvControlled->nrot), mvControlled->Vmax * 1.05) ); @@ -5606,7 +5607,6 @@ bool TTrain::Update( double const Deltatime ) ggFuelPumpButton.Update(); ggOilPumpButton.Update(); //------ - pyScreens.update(); } // wyprowadzenie sygnałów dla haslera na PoKeys (zaznaczanie na taśmie) btHaslerBrakes.Turn(DynamicObject->MoverParameters->BrakePress > 0.4); // ciśnienie w cylindrach @@ -5676,8 +5676,8 @@ bool TTrain::Update( double const Deltatime ) } } /* - // NOTE: disabled while switch state isn't preserved while moving between compartments // check whether we should raise the pantographs, based on volume in pantograph tank + // NOTE: disabled while switch state isn't preserved while moving between compartments if( mvControlled->PantPress > ( mvControlled->TrainType == dt_EZT ? 2.4 : @@ -5692,7 +5692,15 @@ bool TTrain::Update( double const Deltatime ) } } */ - + // screens + fScreenTimer += Deltatime; + if( ( fScreenTimer > Global.PythonScreenUpdateRate * 0.001f ) + && ( false == FreeFlyModeFlag ) ) { // don't bother if we're outside + fScreenTimer = 0.f; + for( auto const &screen : m_screens ) { + Application.request( { screen.first, GetTrainState(), screen.second } ); + } + } // sounds update_sounds( Deltatime ); @@ -5853,7 +5861,6 @@ TTrain::update_sounds( double const Deltatime ) { dsbSlipAlarm.stop(); } } -/* // szum w czasie jazdy if( ( false == FreeFlyModeFlag ) && ( false == Global.CabWindowOpen ) @@ -5865,7 +5872,6 @@ TTrain::update_sounds( double const Deltatime ) { // don't play the optional ending sound if the listener switches views rsRunningNoise.stop( true == FreeFlyModeFlag ); } - */ // hunting oscillation noise if( ( false == FreeFlyModeFlag ) && ( false == Global.CabWindowOpen ) @@ -6255,6 +6261,8 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) { m_controlmapper.clear(); + // clear python screens + m_screens.clear(); // reset sound positions and owner auto const nullvector { glm::vec3() }; std::vector sounds = { @@ -6272,9 +6280,6 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) pMechViewAngle = { 0.0, 0.0 }; Global.pCamera.Pitch = pMechViewAngle.x; Global.pCamera.Yaw = pMechViewAngle.y; - - pyScreens.reset(this); - pyScreens.setLookupPath(DynamicObject->asBaseDir); bool parse = false; int cabindex = 0; DynamicObject->mdKabina = NULL; // likwidacja wskaźnika na dotychczasową kabinę @@ -6469,9 +6474,32 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) // matched the token, grab the next one continue; } + // TODO: add "pydestination:" else if (token == "pyscreen:") { - pyScreens.init(parser, DynamicObject->mdKabina, DynamicObject->name(), NewCabNo); + std::string submodelname, renderername; + parser.getTokens( 2 ); + parser + >> submodelname + >> renderername; + + auto const *submodel { DynamicObject->mdKabina->GetFromName( submodelname ) }; + if( submodel == nullptr ) { + WriteLog( "Python Screen: submodel " + submodelname + " not found - Ignoring screen" ); + continue; + } + auto const material { submodel->GetMaterial() }; + if( material <= 0 ) { + // sub model nie posiada tekstury lub tekstura wymienna - nie obslugiwana + WriteLog( "Python Screen: invalid texture id " + std::to_string( material ) + " - Ignoring screen" ); + continue; + } + // record renderer and material binding for future update requests + m_screens.emplace_back( + ( substr_path(renderername).empty() ? // supply vehicle folder as path if none is provided + DynamicObject->asBaseDir + renderername : + renderername ), + material ); } // btLampkaUnknown.Init("unknown",mdKabina,false); } while (token != ""); @@ -6480,7 +6508,6 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) { return false; } - pyScreens.start(); if (DynamicObject->mdKabina) { // configure placement of sound emitters which aren't bound with any device model, and weren't placed manually diff --git a/Train.h b/Train.h index 4c19b5d2..2a84462d 100644 --- a/Train.h +++ b/Train.h @@ -628,12 +628,10 @@ private: float fConverterTimer; // hunter-261211: dla przekaznika float fMainRelayTimer; // hunter-141211: zalaczanie WSa z opoznieniem float fCzuwakTestTimer; // hunter-091012: do testu czuwaka - float fLightsTimer; // yB 150617: timer do swiatel + float fScreenTimer { 0.f }; bool CAflag { false }; // hunter-131211: dla osobnego zbijania CA i SHP - double fPoslizgTimer; - TTrack *tor; // McZapkie-240302 - przyda sie do tachometru float fTachoVelocity{ 0.0f }; float fTachoVelocityJump{ 0.0f }; // ze skakaniem @@ -659,10 +657,8 @@ private: bool bHeat[8]; // grzanie // McZapkie: do syczenia float fPPress, fNPress; -// float fSPPress, fSNPress; - int iSekunda; // Ra: sekunda aktualizacji pr?dko?ci int iRadioChannel { 1 }; // numer aktualnego kana?u radiowego - TPythonScreens pyScreens; + std::vector> m_screens; public: float fPress[20][3]; // cisnienia dla wszystkich czlonow diff --git a/application.cpp b/application.cpp index 8330e5bb..d35fb33e 100644 --- a/application.cpp +++ b/application.cpp @@ -16,7 +16,6 @@ http://mozilla.org/MPL/2.0/. #include "globals.h" #include "simulation.h" #include "train.h" -#include "pyint.h" #include "sceneeditor.h" #include "renderer.h" #include "uilayer.h" @@ -129,6 +128,7 @@ eu07_application::init( int Argc, char *Argv[] ) { if( ( result = init_audio() ) != 0 ) { return result; } + m_taskqueue.init(); if( ( result = init_modes() ) != 0 ) { return result; } @@ -140,7 +140,7 @@ int eu07_application::run() { // main application loop - while( ( false == glfwWindowShouldClose( m_window ) ) + while( ( false == glfwWindowShouldClose( m_windows.front() ) ) && ( false == m_modestack.empty() ) && ( true == m_modes[ m_modestack.top() ]->update() ) && ( true == GfxRenderer.Render() ) ) { @@ -151,6 +151,12 @@ eu07_application::run() { return 0; } +bool +eu07_application::request( python_taskqueue::task_request const &Task ) { + + return m_taskqueue.insert( Task ); +} + void eu07_application::exit() { @@ -159,10 +165,11 @@ eu07_application::exit() { ui_layer::shutdown(); - glfwDestroyWindow( m_window ); + for( auto *window : m_windows ) { + glfwDestroyWindow( window ); + } glfwTerminate(); - - TPythonInterpreter::killInstance(); + m_taskqueue.exit(); } void @@ -197,7 +204,7 @@ eu07_application::push_mode( eu07_application::mode const Mode ) { void eu07_application::set_title( std::string const &Title ) { - glfwSetWindowTitle( m_window, Title.c_str() ); + glfwSetWindowTitle( m_windows.front(), Title.c_str() ); } void @@ -217,17 +224,13 @@ eu07_application::set_cursor( int const Mode ) { void eu07_application::set_cursor_pos( double const Horizontal, double const Vertical ) { - if( m_window != nullptr ) { - glfwSetCursorPos( m_window, Horizontal, Vertical ); - } + glfwSetCursorPos( m_windows.front(), Horizontal, Vertical ); } void eu07_application::get_cursor_pos( double &Horizontal, double &Vertical ) const { - if( m_window != nullptr ) { - glfwGetCursorPos( m_window, &Horizontal, &Vertical ); - } + glfwGetCursorPos( m_windows.front(), &Horizontal, &Vertical ); } void @@ -262,6 +265,24 @@ eu07_application::on_scroll( double const Xoffset, double const Yoffset ) { m_modes[ m_modestack.top() ]->on_scroll( Xoffset, Yoffset ); } +GLFWwindow * +eu07_application::window( int const Windowindex ) { + + if( Windowindex >= 0 ) { + return ( + Windowindex < m_windows.size() ? + m_windows[ Windowindex ] : + nullptr ); + } + // for index -1 create a new child window + glfwWindowHint( GLFW_VISIBLE, GL_FALSE ); + auto *childwindow = glfwCreateWindow( 1, 1, "eu07helper", nullptr, m_windows.front() ); + if( childwindow != nullptr ) { + m_windows.emplace_back( childwindow ); + } + return childwindow; +} + // private: void @@ -433,7 +454,7 @@ eu07_application::init_glfw() { // switch off the topmost flag ::SetWindowPos( Hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); #endif - m_window = window; + m_windows.emplace_back( window ); return 0; } @@ -441,16 +462,17 @@ eu07_application::init_glfw() { void eu07_application::init_callbacks() { - glfwSetFramebufferSizeCallback( m_window, window_resize_callback ); - glfwSetCursorPosCallback( m_window, cursor_pos_callback ); - glfwSetMouseButtonCallback( m_window, mouse_button_callback ); - glfwSetKeyCallback( m_window, key_callback ); - glfwSetScrollCallback( m_window, scroll_callback ); - glfwSetWindowFocusCallback( m_window, focus_callback ); + auto *window { m_windows.front() }; + glfwSetFramebufferSizeCallback( window, window_resize_callback ); + glfwSetCursorPosCallback( window, cursor_pos_callback ); + glfwSetMouseButtonCallback( window, mouse_button_callback ); + glfwSetKeyCallback( window, key_callback ); + glfwSetScrollCallback( window, scroll_callback ); + glfwSetWindowFocusCallback( window, focus_callback ); { int width, height; - glfwGetFramebufferSize( m_window, &width, &height ); - window_resize_callback( m_window, width, height ); + glfwGetFramebufferSize( window, &width, &height ); + window_resize_callback( window, width, height ); } } @@ -462,8 +484,8 @@ eu07_application::init_gfx() { return -1; } - if( ( false == GfxRenderer.Init( m_window ) ) - || ( false == ui_layer::init( m_window ) ) ) { + if( ( false == GfxRenderer.Init( m_windows.front() ) ) + || ( false == ui_layer::init( m_windows.front() ) ) ) { return -1; } diff --git a/application.h b/application.h index 17f9048c..7aceda31 100644 --- a/application.h +++ b/application.h @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "applicationmode.h" +#include "pyint.h" class eu07_application { @@ -29,6 +30,8 @@ public: init( int Argc, char *Argv[] ); int run(); + bool + request( python_taskqueue::task_request const &Task ); void exit(); void @@ -57,10 +60,9 @@ public: on_mouse_button( int const Button, int const Action, int const Mods ); void on_scroll( double const Xoffset, double const Yoffset ); - inline + // gives access to specified window, creates a new window if index == -1 GLFWwindow * - window() { - return m_window; } + window( int const Windowindex = 0 ); private: // types @@ -77,9 +79,10 @@ private: int init_audio(); int init_modes(); // members - GLFWwindow * m_window { nullptr }; modeptr_array m_modes { nullptr }; // collection of available application behaviour modes mode_stack m_modestack; // current behaviour mode + python_taskqueue m_taskqueue; + std::vector m_windows; }; extern eu07_application Application; diff --git a/stdafx.h b/stdafx.h index c59fc775..57bdfa9b 100644 --- a/stdafx.h +++ b/stdafx.h @@ -103,6 +103,8 @@ #include #include +int const null_handle = 0; + #include "openglmatrixstack.h" #include "openglcolor.h" diff --git a/utilities.h b/utilities.h index be746026..ee086586 100644 --- a/utilities.h +++ b/utilities.h @@ -313,4 +313,61 @@ glm::dvec3 LoadPoint( class cParser &Input ); std::string deserialize_random_set( cParser &Input, char const *Break = "\n\r\t ;" ); +namespace threading { + +// simple POD pairing of a data item and a mutex +// NOTE: doesn't do any locking itself, it's merely for cleaner argument arrangement and passing +template +struct lockable { + + Type_ data; + std::mutex mutex; +}; + +// basic wrapper simplifying use of std::condition_variable for most typical cases. +// has its own mutex and secondary variable to ignore spurious wakeups +class condition_variable { + +public: +// methods + void + wait() { + std::unique_lock lock( m_mutex ); + m_condition.wait( + lock, + [ this ]() { + return m_spurious == false; } ); } + template< class Rep_, class Period_ > + void + wait_for( const std::chrono::duration &Time ) { + std::unique_lock lock( m_mutex ); + m_condition.wait_for( + lock, + Time, + [ this ]() { + return m_spurious == false; } ); } + void + notify_one() { + spurious( false ); + m_condition.notify_one(); + } + void + notify_all() { + spurious( false ); + m_condition.notify_all(); + } + void + spurious( bool const Spurious ) { + std::lock_guard lock( m_mutex ); + m_spurious = Spurious; } + +private: +// members + mutable std::mutex m_mutex; + std::condition_variable m_condition; + bool m_spurious { true }; +}; + +} // threading + //--------------------------------------------------------------------------- From 226ddb6291077c3c890f7978856cdba4d1241480 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Fri, 28 Sep 2018 19:18:24 +0200 Subject: [PATCH 08/10] build 180928. heater sound, minor precipitation tweaks, minor bug fixes --- DynObj.cpp | 17 +++++++++++++++-- DynObj.h | 1 + Gauge.cpp | 2 +- Texture.cpp | 9 ++++++--- openglcolor.h | 4 +++- openglgeometrybank.cpp | 1 - renderer.cpp | 22 ++++++++++++++-------- version.h | 4 ++-- 8 files changed, 42 insertions(+), 18 deletions(-) diff --git a/DynObj.cpp b/DynObj.cpp index f9fc47c2..f9321f88 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -2157,7 +2157,7 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" btShutters1.Init( "shutters1", mdModel, false ); } if( MoverParameters->dizel_heat.water_aux.config.shutters ) { - btShutters1.Init( "shutters2", mdModel, false ); + btShutters2.Init( "shutters2", mdModel, false ); } TurnOff(); // resetowanie zmiennych submodeli @@ -3971,6 +3971,13 @@ void TDynamicObject::RenderSounds() { sSmallCompressor.stop(); } + // heating sound + if( MoverParameters->Heating ) { + sHeater.play( sound_flags::exclusive | sound_flags::looping ); + } + else { + sHeater.stop(); + } // brake system and braking sounds: @@ -5393,6 +5400,12 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co sConverter.owner( this ); } + else if( token == "heater:" ) { + // train heating device + sHeater.deserialize( parser, sound_type::single ); + sHeater.owner( this ); + } + else if( token == "turbo:" ) { // pliki z turbogeneratorem m_powertrainsounds.engine_turbo.deserialize( parser, sound_type::multipart, sound_parameters::range ); @@ -5827,7 +5840,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co // other engine compartment sounds auto const nullvector { glm::vec3() }; std::vector enginesounds = { - &sConverter, &sCompressor, &sSmallCompressor + &sConverter, &sCompressor, &sSmallCompressor, &sHeater }; for( auto sound : enginesounds ) { if( sound->offset() == nullvector ) { diff --git a/DynObj.h b/DynObj.h index 8c899e52..0a19dee1 100644 --- a/DynObj.h +++ b/DynObj.h @@ -409,6 +409,7 @@ private: sound_source sConverter { sound_placement::engine }; sound_source sCompressor { sound_placement::engine }; // NBMX wrzesien 2003 sound_source sSmallCompressor { sound_placement::engine }; + sound_source sHeater { sound_placement::engine }; // braking sounds sound_source dsbPneumaticRelay { sound_placement::external }; sound_source rsBrake { sound_placement::external, EU07_SOUND_BRAKINGCUTOFFRANGE }; // moved from cab diff --git a/Gauge.cpp b/Gauge.cpp index 7666055e..ca49c85e 100644 --- a/Gauge.cpp +++ b/Gauge.cpp @@ -209,7 +209,7 @@ void TGauge::UpdateValue( float fNewDesired, sound_source *Fallbacksound ) { auto const desiredtimes100 = static_cast( std::round( 100.0 * fNewDesired ) ); - if( desiredtimes100 == static_cast( 100.0 * m_targetvalue ) ) { + if( desiredtimes100 == static_cast( std::round( 100.0 * m_targetvalue ) ) ) { return; } m_targetvalue = fNewDesired; diff --git a/Texture.cpp b/Texture.cpp index 857ad9e1..665a2598 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -814,14 +814,17 @@ void texture_manager::bind( std::size_t const Unit, texture_handle const Texture ) { m_textures[ Texture ].second = m_garbagecollector.timestamp(); - + if( m_units[ Unit ].unit == 0 ) { + // no texture unit, nothing to bind the texture to + return; + } + // even if we may skip texture binding make sure the relevant texture unit is activated + unit( m_units[ Unit ].unit ); if( Texture == m_units[ Unit ].texture ) { // don't bind again what's already active return; } // TBD, TODO: do binding in texture object, add support for other types than 2d - if( m_units[ Unit ].unit == 0 ) { return; } - unit( m_units[ Unit ].unit ); if( Texture != null_handle ) { #ifndef EU07_DEFERRED_TEXTURE_UPLOAD // NOTE: we could bind dedicated 'error' texture here if the id isn't valid diff --git a/openglcolor.h b/openglcolor.h index a40f0732..39281880 100644 --- a/openglcolor.h +++ b/openglcolor.h @@ -9,6 +9,8 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include "globals.h" + // encapsulation of the fixed pipeline opengl color class opengl_color { @@ -32,7 +34,7 @@ public: inline void color4( glm::vec4 const &Color ) { - if( Color != m_color ) { + if( ( Color != m_color ) || ( false == Global.bUseVBO ) ) { m_color = Color; ::glColor4fv( glm::value_ptr( m_color ) ); } } inline diff --git a/openglgeometrybank.cpp b/openglgeometrybank.cpp index ba29170e..02fde9db 100644 --- a/openglgeometrybank.cpp +++ b/openglgeometrybank.cpp @@ -341,7 +341,6 @@ opengl_dlgeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stream_ auto const &chunk = gfx::geometry_bank::chunk( Geometry ); ::glNewList( chunkrecord.list, GL_COMPILE ); - ::glColor3f( -1.f, -1.f, -1.f ); // HACK: force the opengl color wrapper to include color call in the display list regardless of currently active color ::glBegin( chunk.type ); for( auto const &vertex : chunk.vertices ) { if( Streams & gfx::stream::normal ) { ::glNormal3fv( glm::value_ptr( vertex.normal ) ); } diff --git a/renderer.cpp b/renderer.cpp index 9a0adf7d..f5d6d9c4 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -2837,17 +2837,10 @@ opengl_renderer::Render_precipitation() { colors::white, 0.5f * clamp( Global.fLuminance, 0.f, 1.f ) ) ) ); ::glPushMatrix(); - + // tilt the precipitation cone against the velocity vector for crude motion blur auto const velocity { simulation::Environment.m_precipitation.m_cameramove * -1.0 }; if( glm::length2( velocity ) > 0.0 ) { auto const forward{ glm::normalize( velocity ) }; - if( false == FreeFlyModeFlag ) { - // counter potential vehicle roll - auto const roll { simulation::Train->Dynamic()->Roll() }; - if( roll != 0.0 ) { - ::glRotated( roll, forward.x, 0.0, forward.z ); - } - } auto left { glm::cross( forward, {0.0,1.0,0.0} ) }; auto const rotationangle { std::min( @@ -2857,6 +2850,19 @@ opengl_renderer::Render_precipitation() { simulation::Train->Dynamic()->GetVelocity() * 0.2 ) ) }; ::glRotated( rotationangle, left.x, 0.0, left.z ); } + if( false == FreeFlyModeFlag ) { + // counter potential vehicle roll + auto const roll { 0.5 * glm::degrees( simulation::Train->Dynamic()->Roll() ) }; + if( roll != 0.0 ) { + auto const forward { simulation::Train->Dynamic()->VectorFront() }; + auto const vehicledirection = simulation::Train->Dynamic()->DirectionGet(); + ::glRotated( roll, forward.x, 0.0, forward.z ); + } + } + if( Global.Weather == "rain:" ) { + // oddly enough random streaks produce more natural looking rain than ones the eye can follow + ::glRotated( Random() * 360, 0.0, 1.0, 0.0 ); + } // TBD: leave lighting on to allow vehicle lights to affect it? ::glDisable( GL_LIGHTING ); diff --git a/version.h b/version.h index 944d092c..66a33f7e 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 18 -#define VERSION_MINOR 923 -#define VERSION_REVISION 1 +#define VERSION_MINOR 928 +#define VERSION_REVISION 0 From 467d46eba2e0cdeb2856b1c5bfc718d6b40a52fe Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sat, 29 Sep 2018 20:14:28 +0200 Subject: [PATCH 09/10] partial support for cab switch types, basic precipitation level texture selection --- Gauge.cpp | 47 ++++++---- Gauge.h | 13 ++- McZapkie/MOVER.h | 11 ++- McZapkie/Mover.cpp | 215 ++++++++++++++++++++++++++++++++---------- Segment.cpp | 4 +- Train.cpp | 226 +++++++++++++++++++++++++++++++++------------ precipitation.cpp | 11 ++- 7 files changed, 389 insertions(+), 138 deletions(-) diff --git a/Gauge.cpp b/Gauge.cpp index ca49c85e..1f87a7b8 100644 --- a/Gauge.cpp +++ b/Gauge.cpp @@ -28,11 +28,11 @@ TGauge::TGauge( sound_source const &Soundtemplate ) : m_soundfxdecrease = m_soundtemplate; } -void TGauge::Init(TSubModel *Submodel, TGaugeType Type, float Scale, float Offset, float Friction, float Value, float const Endvalue, float const Endscale, bool const Interpolatescale ) +void TGauge::Init(TSubModel *Submodel, TGaugeAnimation Type, float Scale, float Offset, float Friction, float Value, float const Endvalue, float const Endscale, bool const Interpolatescale ) { // ustawienie parametrów animacji submodelu SubModel = Submodel; m_value = Value; - m_type = Type; + m_animation = Type; m_scale = Scale; m_offset = Offset; m_friction = Friction; @@ -45,7 +45,7 @@ void TGauge::Init(TSubModel *Submodel, TGaugeType Type, float Scale, float Offse return; } - if( m_type == TGaugeType::gt_Digital ) { + if( m_animation == TGaugeAnimation::gt_Digital ) { TSubModel *sm = SubModel->ChildGet(); do { @@ -147,19 +147,19 @@ bool TGauge::Load( cParser &Parser, TDynamicObject const *Owner, TModel3d *md1, ErrorLog( "Bad model: failed to locate sub-model \"" + submodelname + "\" in 3d model \"" + md1->NameGet() + "\"", logtype::model ); } - std::map gaugetypes { - { "rot", TGaugeType::gt_Rotate }, - { "rotvar", TGaugeType::gt_Rotate }, - { "mov", TGaugeType::gt_Move }, - { "movvar", TGaugeType::gt_Move }, - { "wip", TGaugeType::gt_Wiper }, - { "dgt", TGaugeType::gt_Digital } + std::map gaugetypes { + { "rot", TGaugeAnimation::gt_Rotate }, + { "rotvar", TGaugeAnimation::gt_Rotate }, + { "mov", TGaugeAnimation::gt_Move }, + { "movvar", TGaugeAnimation::gt_Move }, + { "wip", TGaugeAnimation::gt_Wiper }, + { "dgt", TGaugeAnimation::gt_Digital } }; auto lookup = gaugetypes.find( gaugetypename ); auto const type = ( lookup != gaugetypes.end() ? lookup->second : - TGaugeType::gt_Unknown ); + TGaugeAnimation::gt_Unknown ); Init( submodel, type, scale, offset, friction, 0, endvalue, endscale, interpolatescale ); @@ -170,10 +170,17 @@ bool TGauge::Load_mapping( cParser &Input ) { // token can be a key or block end - std::string const key { Input.getToken( true, "\n\r\t ,;" ) }; + auto const key { Input.getToken( true, "\n\r\t ,;" ) }; if( ( true == key.empty() ) || ( key == "}" ) ) { return false; } // if not block end then the key is followed by assigned value or sub-block - if( key == "soundinc:" ) { + if( key == "type:" ) { + auto const gaugetype { Input.getToken( true, "\n\r\t ,;" ) }; + m_type = ( + gaugetype == "impulse" ? TGaugeType::push : + gaugetype == "return" ? TGaugeType::push : + TGaugeType::toggle ); // default + } + else if( key == "soundinc:" ) { m_soundfxincrease.deserialize( Input, sound_type::single ); } else if( key == "sounddec:" ) { @@ -281,16 +288,16 @@ void TGauge::Update() { } if( SubModel ) { // warunek na wszelki wypadek, gdyby się submodel nie podłączył - switch (m_type) { - case TGaugeType::gt_Rotate: { + switch (m_animation) { + case TGaugeAnimation::gt_Rotate: { SubModel->SetRotate( float3( 0, 1, 0 ), GetScaledValue() * 360.0 ); break; } - case TGaugeType::gt_Move: { + case TGaugeAnimation::gt_Move: { SubModel->SetTranslate( float3( 0, 0, GetScaledValue() ) ); break; } - case TGaugeType::gt_Wiper: { + case TGaugeAnimation::gt_Wiper: { auto const scaledvalue { GetScaledValue() }; SubModel->SetRotate( float3( 0, 1, 0 ), scaledvalue * 360.0 ); auto *sm = SubModel->ChildGet(); @@ -302,7 +309,7 @@ void TGauge::Update() { } break; } - case TGaugeType::gt_Digital: { + case TGaugeAnimation::gt_Digital: { // Ra 2014-07: licznik cyfrowy auto *sm = SubModel->ChildGet(); /* std::string n = FormatFloat( "0000000000", floor( fValue ) ); // na razie tak trochę bez sensu @@ -390,4 +397,8 @@ TGauge::model_offset() const { glm::vec3() ); } +TGaugeType +TGauge::type() const { + return m_type; +} //--------------------------------------------------------------------------- diff --git a/Gauge.h b/Gauge.h index a73aad2f..82de24af 100644 --- a/Gauge.h +++ b/Gauge.h @@ -12,7 +12,7 @@ http://mozilla.org/MPL/2.0/. #include "Classes.h" #include "sound.h" -enum class TGaugeType { +enum class TGaugeAnimation { // typ ruchu gt_Unknown, // na razie nie znany gt_Rotate, // obrót @@ -21,6 +21,11 @@ enum class TGaugeType { gt_Digital // licznik cyfrowy, np. kilometrów }; +enum class TGaugeType { + toggle, + push +}; + // animowany wskaźnik, mogący przyjmować wiele stanów pośrednich class TGauge { @@ -32,7 +37,7 @@ public: inline void Clear() { *this = TGauge(); } - void Init(TSubModel *Submodel, TGaugeType Type, float Scale = 1, float Offset = 0, float Friction = 0, float Value = 0, float const Endvalue = -1.0, float const Endscale = -1.0, bool const Interpolate = false ); + void Init(TSubModel *Submodel, TGaugeAnimation Type, float Scale = 1, float Offset = 0, float Friction = 0, float Value = 0, float const Endvalue = -1.0, float const Endscale = -1.0, bool const Interpolate = false ); bool Load(cParser &Parser, TDynamicObject const *Owner, TModel3d *md1, TModel3d *md2 = nullptr, double mul = 1.0); void UpdateValue( float fNewDesired ); void UpdateValue( float fNewDesired, sound_source &Fallbacksound ); @@ -46,6 +51,7 @@ public: void UpdateValue(); // returns offset of submodel associated with the button from the model centre glm::vec3 model_offset() const; + TGaugeType type() const; // members TSubModel *SubModel { nullptr }; // McZapkie-310302: zeby mozna bylo sprawdzac czy zainicjowany poprawnie @@ -60,7 +66,8 @@ private: GetScaledValue() const; // members - TGaugeType m_type { TGaugeType::gt_Unknown }; // typ ruchu + TGaugeAnimation m_animation { TGaugeAnimation::gt_Unknown }; // typ ruchu + TGaugeType m_type { TGaugeType::toggle }; // switch type float m_friction { 0.f }; // hamowanie przy zliżaniu się do zadanej wartości float m_targetvalue { 0.f }; // wartość docelowa float m_value { 0.f }; // wartość obecna diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 9dcbaadb..fed359ee 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -624,6 +624,7 @@ struct TCoupling { 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 }; }; @@ -633,6 +634,7 @@ struct fuel_pump { 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 }; @@ -646,6 +648,7 @@ 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 }; }; @@ -1332,11 +1335,14 @@ public: bool DirectionBackward(void);/*! kierunek ruchu*/ bool WaterPumpBreakerSwitch( bool State, range_t const Notify = range_t::consist ); // water pump breaker state toggle bool WaterPumpSwitch( bool State, range_t const Notify = range_t::consist ); // water pump state toggle + bool WaterPumpSwitchOff( bool State, range_t const Notify = range_t::consist ); // water pump state toggle bool WaterHeaterBreakerSwitch( bool State, range_t const Notify = range_t::consist ); // water heater breaker state toggle bool WaterHeaterSwitch( bool State, range_t const Notify = range_t::consist ); // water heater state toggle bool WaterCircuitsLinkSwitch( bool State, range_t const Notify = range_t::consist ); // water circuits link state toggle bool FuelPumpSwitch( bool State, range_t const Notify = range_t::consist ); // fuel pump state toggle + 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 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*/ @@ -1366,7 +1372,8 @@ public: bool MaxCurrentSwitch(bool State); //przelacznik pradu wysokiego rozruchu bool MinCurrentSwitch(bool State); //przelacznik pradu automatycznego rozruchu bool AutoRelaySwitch(bool State); //przelacznik automatycznego rozruchu - bool AutoRelayCheck(void);//symulacja automatycznego rozruchu + bool AutoRelayCheck();//symulacja automatycznego rozruchu + bool MotorConnectorsCheck(); bool ResistorsFlagCheck(void) const; //sprawdzenie kontrolki oporow rozruchowych NBMX bool PantFront( bool const State, range_t const Notify = range_t::consist ); //obsluga pantografou przedniego @@ -1441,4 +1448,4 @@ private: void BrakeSubsystemDecode(); //Q 20160719 }; -extern double Distance(TLocation Loc1, TLocation Loc2, TDimension Dim1, TDimension Dim2); +//double Distance(TLocation Loc1, TLocation Loc2, TDimension Dim1, TDimension Dim2); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 06d4100e..b9f521e8 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -144,22 +144,6 @@ double TMoverParameters::Current(double n, double U) Mn = RList[ MainCtrlActualPos ].Mn * RList[ MainCtrlActualPos ].Bn; } - // writepaslog("#", - // "C++-----------------------------------------------------------------------------"); - // writepaslog("MCAP ", IntToStr(MainCtrlActualPos)); - // writepaslog("SCAP ", IntToStr(ScndCtrlActualPos)); - // writepaslog("n ", FloatToStr(n)); - // writepaslog("StLinFlag ", BoolToYN(StLinFlag)); - // writepaslog("DelayCtrlFlag ", booltoYN(DelayCtrlFlag)); - // writepaslog("Bn ", FloatToStr(Bn)); - // writepaslog("R ", FloatToStr(R)); - // writepaslog("Mn ", IntToStr(Mn)); - // writepaslog("RList[MCAP].Bn ", FloatToStr(RList[MainCtrlActualPos].Bn)); - // writepaslog("RList[MCAP].Mn ", FloatToStr(RList[MainCtrlActualPos].Mn)); - // writepaslog("RList[MCAP].R ", FloatToStr(RList[MainCtrlActualPos].R)); - - // z Megapacka ... bylo tutaj zakomentowane Q: no to usuwam... - if (DynamicBrakeFlag && (!FuseFlag) && (DynamicBrakeType == dbrake_automatic) && ConverterFlag && Mains) // hamowanie EP09 //TUHEX { @@ -1300,7 +1284,7 @@ double TMoverParameters::ComputeMovement(double dt, double dt1, const TTrackShap if (SetFlag(DamageFlag, dtrain_out)) { EventFlag = true; - Mains = false; + MainSwitch( false, range_t::local ); RunningShape.R = 0; if (TestFlag(Track.DamageFlag, dtrack_norail)) DerailReason = 1; // Ra: powód wykolejenia: brak szyn @@ -1312,7 +1296,7 @@ double TMoverParameters::ComputeMovement(double dt, double dt1, const TTrackShap if (SetFlag(DamageFlag, dtrain_out)) { EventFlag = true; - Mains = false; + MainSwitch( false, range_t::local ); RunningShape.R = 0; DerailReason = 3; // Ra: powód wykolejenia: za szeroki tor } @@ -1322,7 +1306,7 @@ double TMoverParameters::ComputeMovement(double dt, double dt1, const TTrackShap if (SetFlag(DamageFlag, dtrain_out)) { EventFlag = true; - Mains = false; + MainSwitch( false, range_t::local ); DerailReason = 4; // Ra: powód wykolejenia: nieodpowiednia trajektoria } if( ( true == TestFlag( DamageFlag, dtrain_out ) ) @@ -1542,7 +1526,9 @@ void TMoverParameters::WaterPumpCheck( double const Timestep ) { WaterPump.is_active = ( ( true == Battery ) && ( true == WaterPump.breaker ) - && ( ( true == WaterPump.is_enabled ) || ( WaterPump.start_type == start_t::battery ) ) ); + && ( false == WaterPump.is_disabled ) + && ( ( true == WaterPump.is_active ) + || ( true == WaterPump.is_enabled ) || ( WaterPump.start_type == start_t::battery ) ) ); } // water heater status check @@ -1571,10 +1557,12 @@ void TMoverParameters::FuelPumpCheck( double const Timestep ) { FuelPump.is_active = ( ( true == Battery ) - && ( FuelPump.start_type == start_t::manual ? ( FuelPump.is_enabled ) : - FuelPump.start_type == start_t::automatic ? ( dizel_startup || Mains ) : - FuelPump.start_type == start_t::manualwithautofallback ? ( FuelPump.is_enabled || dizel_startup || Mains ) : - false ) ); // shouldn't ever get this far but, eh + && ( false == FuelPump.is_disabled ) + && ( ( FuelPump.is_active ) + || ( FuelPump.start_type == start_t::manual ? ( FuelPump.is_enabled ) : + FuelPump.start_type == start_t::automatic ? ( dizel_startup || Mains ) : + FuelPump.start_type == start_t::manualwithautofallback ? ( FuelPump.is_enabled || dizel_startup || Mains ) : + false ) ) ); // shouldn't ever get this far but, eh } // oil pump status update @@ -1582,10 +1570,12 @@ void TMoverParameters::OilPumpCheck( double const Timestep ) { OilPump.is_active = ( ( true == Battery ) - && ( OilPump.start_type == start_t::manual ? ( OilPump.is_enabled ) : - OilPump.start_type == start_t::automatic ? ( dizel_startup || Mains ) : - OilPump.start_type == start_t::manualwithautofallback ? ( OilPump.is_enabled || dizel_startup || Mains ) : - false ) ); // shouldn't ever get this far but, eh + && ( false == OilPump.is_disabled ) + && ( ( OilPump.is_active ) + || ( OilPump.start_type == start_t::manual ? ( OilPump.is_enabled ) : + OilPump.start_type == start_t::automatic ? ( dizel_startup || Mains ) : + OilPump.start_type == start_t::manualwithautofallback ? ( OilPump.is_enabled || dizel_startup || Mains ) : + false ) ) ); // shouldn't ever get this far but, eh auto const maxrevolutions { EngineType == TEngineType::DieselEngine ? @@ -2419,6 +2409,31 @@ bool TMoverParameters::WaterPumpSwitch( bool State, range_t const Notify ) { return ( WaterPump.is_enabled != initialstate ); } +// water pump state toggle +bool TMoverParameters::WaterPumpSwitchOff( bool State, range_t const Notify ) { + + if( WaterPump.start_type == start_t::battery ) { + // automatic fuel pump ignores 'manual' state commands + return false; + } + + bool const initialstate { WaterPump.is_disabled }; + + WaterPump.is_disabled = State; + + if( Notify != range_t::local ) { + SendCtrlToNext( + "WaterPumpSwitchOff", + ( WaterPump.is_disabled ? 1 : 0 ), + CabNo, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); + } + + return ( WaterPump.is_disabled != initialstate ); +} + // water heater breaker state toggle bool TMoverParameters::WaterHeaterBreakerSwitch( bool State, range_t const Notify ) { /* @@ -2519,6 +2534,30 @@ bool TMoverParameters::FuelPumpSwitch( bool State, range_t const Notify ) { return ( FuelPump.is_enabled != initialstate ); } +bool TMoverParameters::FuelPumpSwitchOff( bool State, range_t const Notify ) { + + if( FuelPump.start_type == start_t::automatic ) { + // automatic fuel pump ignores 'manual' state commands + return false; + } + + bool const initialstate { FuelPump.is_disabled }; + + FuelPump.is_disabled = State; + + if( Notify != range_t::local ) { + SendCtrlToNext( + "FuelPumpSwitchOff", + ( FuelPump.is_disabled ? 1 : 0 ), + CabNo, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); + } + + return ( FuelPump.is_disabled != initialstate ); +} + // oil pump state toggle bool TMoverParameters::OilPumpSwitch( bool State, range_t const Notify ) { @@ -2544,6 +2583,30 @@ bool TMoverParameters::OilPumpSwitch( bool State, range_t const Notify ) { return ( OilPump.is_enabled != initialstate ); } +bool TMoverParameters::OilPumpSwitchOff( bool State, range_t const Notify ) { + + if( OilPump.start_type == start_t::automatic ) { + // automatic pump ignores 'manual' state commands + return false; + } + + bool const initialstate { OilPump.is_disabled }; + + OilPump.is_disabled = State; + + if( Notify != range_t::local ) { + SendCtrlToNext( + "OilPumpSwitchOff", + ( OilPump.is_disabled ? 1 : 0 ), + CabNo, + ( Notify == range_t::unit ? + coupling::control | coupling::permanent : + coupling::control ) ); + } + + return ( OilPump.is_disabled != initialstate ); +} + // ************************************************************************************************* // Q: 20160713 // włączenie / wyłączenie obwodu głownego @@ -2577,6 +2640,10 @@ bool TMoverParameters::MainSwitch( bool const State, range_t const Notify ) } else { Mains = false; + // potentially knock out the pumps if their switch doesn't force them on + WaterPump.is_active &= WaterPump.is_enabled; + OilPump.is_active &= OilPump.is_enabled; + FuelPump.is_active &= FuelPump.is_enabled; } if( ( TrainType == dt_EZT ) @@ -4449,6 +4516,13 @@ double TMoverParameters::TractionForce( double dt ) { && ( MainSwitch( false, ( TrainType == dt_EZT ? range_t::unit : range_t::local ) ) ) ); // TODO: check whether we need to send this EMU-wide break; } + + case TEngineType::DieselElectric: { + // TODO: move this to the auto relay check when the electric engine code paths are unified + StLinFlag = MotorConnectorsCheck(); + break; + } + default: { break; } @@ -5393,19 +5467,14 @@ bool TMoverParameters::AutoRelayCheck(void) bool OK = false; // b:int; bool ARC = false; + auto const motorconnectors { MotorConnectorsCheck() }; + // Ra 2014-06: dla SN61 nie działa prawidłowo - // rozlaczanie stycznikow liniowych - if( ( false == Mains ) - || ( true == FuseFlag ) - || ( true == StLinSwitchOff ) - || ( MainCtrlPos == 0 ) - || ( ( TrainType != dt_EZT ) && ( BrakePress > 2.1 ) ) - || ( ActiveDir == 0 ) ) // hunter-111211: wylacznik cisnieniowy - { - StLinFlag = false; // yBARC - rozlaczenie stycznikow liniowych + // yBARC - rozlaczenie stycznikow liniowych + if( false == motorconnectors ) { + StLinFlag = false; OK = false; - if (!DynamicBrakeFlag) - { + if( false == DynamicBrakeFlag ) { Im = 0; Itot = 0; ResistorsFlag = false; @@ -5580,17 +5649,11 @@ bool TMoverParameters::AutoRelayCheck(void) else // not StLinFlag { OK = false; - // ybARC - tutaj sa wszystkie warunki, jakie musza byc spelnione, zeby mozna byla - // zalaczyc styczniki liniowe - if (((MainCtrlPos == 1) || ((TrainType == dt_EZT) && (MainCtrlPos > 0))) && - (!FuseFlag) && (Mains) && ((BrakePress < 1.0) || (TrainType == dt_EZT)) && - (MainCtrlActualPos == 0) && (ActiveDir != 0)) - { //^^ TODO: sprawdzic BUG, prawdopodobnie w CreateBrakeSys() + // ybARC - zalaczenie stycznikow liniowych + if( true == motorconnectors ) { DelayCtrlFlag = true; - if( (LastRelayTime >= InitialCtrlDelay) - && ( false == StLinSwitchOff ) ) - { - StLinFlag = true; // ybARC - zalaczenie stycznikow liniowych + if( LastRelayTime >= InitialCtrlDelay ) { + StLinFlag = true; MainCtrlActualPos = 1; DelayCtrlFlag = false; SetFlag(SoundFlag, sound::relay | sound::loud); @@ -5663,6 +5726,34 @@ bool TMoverParameters::AutoRelayCheck(void) } } +bool TMoverParameters::MotorConnectorsCheck() { + + // hunter-111211: wylacznik cisnieniowy + auto const pressureswitch { + ( TrainType != dt_EZT ) + && ( ( BrakePress > 2.0 ) + || ( PipePress < 3.6 ) ) }; + + if( pressureswitch ) { return false; } + + auto const connectorsoff { + ( false == Mains ) + || ( true == FuseFlag ) + || ( true == StLinSwitchOff ) + || ( MainCtrlPos == 0 ) + || ( ActiveDir == 0 ) }; + + if( connectorsoff ) { return false; } + + auto const connectorson { + ( true == StLinFlag ) + || ( ( MainCtrlActualPos == 0 ) + && ( ( MainCtrlPos == 1 ) + || ( ( TrainType == dt_EZT ) && ( MainCtrlPos > 0 ) ) ) ) }; + + return connectorson; +} + // ************************************************************************************************* // Q: 20160713 // Podnosi / opuszcza przedni pantograf. Returns: state of the pantograph after the operation @@ -9133,6 +9224,14 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C } OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } + else if( Command == "WaterPumpSwitchOff" ) { + + if( WaterPump.start_type != start_t::battery ) { + // automatic fuel pump ignores 'manual' state commands + WaterPump.is_disabled = ( CValue1 == 1 ); + } + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } else if( Command == "WaterHeaterBreakerSwitch" ) { /* if( FuelPump.start_type != start::automatic ) { @@ -9169,6 +9268,13 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C } OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } + else if (Command == "FuelPumpSwitchOff") { + if( FuelPump.start_type != start_t::automatic ) { + // automatic fuel pump ignores 'manual' state commands + FuelPump.is_disabled = ( CValue1 == 1 ); + } + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } else if (Command == "OilPumpSwitch") { if( OilPump.start_type != start_t::automatic ) { // automatic pump ignores 'manual' state commands @@ -9176,6 +9282,13 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C } OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } + else if (Command == "OilPumpSwitchOff") { + if( OilPump.start_type != start_t::automatic ) { + // automatic pump ignores 'manual' state commands + OilPump.is_disabled = ( CValue1 == 1 ); + } + OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); + } else if (Command == "MainSwitch") { if (CValue1 == 1) { @@ -9190,6 +9303,10 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C } else { Mains = false; + // potentially knock out the pumps if their switch doesn't force them on + WaterPump.is_active &= WaterPump.is_enabled; + OilPump.is_active &= OilPump.is_enabled; + FuelPump.is_active &= FuelPump.is_enabled; } OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); } diff --git a/Segment.cpp b/Segment.cpp index 97b5b877..10bf62cc 100644 --- a/Segment.cpp +++ b/Segment.cpp @@ -397,7 +397,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori float m1, jmm1, m2, jmm2; // pozycje względne na odcinku 0...1 (ale nie parametr Beziera) step = fStep; - tv1 = 1.0; // Ra: to by można było wyliczać dla odcinka, wyglądało by lepiej + tv1 = 0.0; // Ra: to by można było wyliczać dla odcinka, wyglądało by lepiej s = fStep * iSkip; // iSkip - ile odcinków z początku pominąć int i = iSkip; // domyślnie 0 t = fTsBuffer[ i ]; // tabela wattości t dla segmentów @@ -435,7 +435,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Ori while( tv1 < 0.0 ) { tv1 += 1.0; } - tv2 = tv1 - step / texturelength; // mapowanie na końcu segmentu + tv2 = tv1 + step / texturelength; // mapowanie na końcu segmentu t = fTsBuffer[ i ]; // szybsze od GetTFromS(s); pos2 = glm::dvec3{ FastGetPoint( t ) - Origin }; diff --git a/Train.cpp b/Train.cpp index bded4fe9..22ff5ab3 100644 --- a/Train.cpp +++ b/Train.cpp @@ -2117,8 +2117,17 @@ void TTrain::OnCommand_linebreakerclose( TTrain *Train, command_data const &Comm void TTrain::OnCommand_fuelpumptoggle( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // only reacting to press, so the switch doesn't flip back and forth if key is held down + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggFuelPumpButton.type() == TGaugeType::push ) { + // impulse switch + // currently there's no off button so we always try to turn it on + OnCommand_fuelpumpenable( Train, Command ); + } + else { + // two-state switch + if( Command.action == GLFW_RELEASE ) { return; } + if( false == Train->mvControlled->FuelPump.is_enabled ) { // turn on OnCommand_fuelpumpenable( Train, Command ); @@ -2132,32 +2141,65 @@ void TTrain::OnCommand_fuelpumptoggle( TTrain *Train, command_data const &Comman void TTrain::OnCommand_fuelpumpenable( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // visual feedback - Train->ggFuelPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + if( Command.action == GLFW_REPEAT ) { return; } - if( true == Train->mvControlled->FuelPump.is_enabled ) { return; } // already enabled - - Train->mvControlled->FuelPumpSwitch( true ); + if( Train->ggFuelPumpButton.type() == TGaugeType::push ) { + // impulse switch + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggFuelPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + Train->mvControlled->FuelPumpSwitch( true ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + Train->ggFuelPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + Train->mvControlled->FuelPumpSwitch( false ); + } + } + else { + // two-state switch, only cares about press events + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggFuelPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + Train->mvControlled->FuelPumpSwitch( true ); + Train->mvControlled->FuelPumpSwitchOff( false ); + } } } void TTrain::OnCommand_fuelpumpdisable( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // visual feedback - Train->ggFuelPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + if( Command.action == GLFW_REPEAT ) { return; } - if( false == Train->mvControlled->FuelPump.is_enabled ) { return; } // already disabled - - Train->mvControlled->FuelPumpSwitch( false ); + if( Train->ggFuelPumpButton.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->ggFuelPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + Train->mvControlled->FuelPumpSwitch( false ); + Train->mvControlled->FuelPumpSwitchOff( true ); + } } } void TTrain::OnCommand_oilpumptoggle( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // only reacting to press, so the switch doesn't flip back and forth if key is held down + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggOilPumpButton.type() == TGaugeType::push ) { + // impulse switch + // currently there's no off button so we always try to turn it on + OnCommand_oilpumpenable( Train, Command ); + } + else { + // two-state switch + if( Command.action == GLFW_RELEASE ) { return; } + if( false == Train->mvControlled->OilPump.is_enabled ) { // turn on OnCommand_oilpumpenable( Train, Command ); @@ -2171,25 +2213,49 @@ void TTrain::OnCommand_oilpumptoggle( TTrain *Train, command_data const &Command void TTrain::OnCommand_oilpumpenable( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // visual feedback - Train->ggOilPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + if( Command.action == GLFW_REPEAT ) { return; } - if( true == Train->mvControlled->OilPump.is_enabled ) { return; } // already enabled - - Train->mvControlled->OilPumpSwitch( true ); + if( Train->ggOilPumpButton.type() == TGaugeType::push ) { + // impulse switch + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggOilPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + Train->mvControlled->OilPumpSwitch( true ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + Train->ggOilPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + Train->mvControlled->OilPumpSwitch( false ); + } + } + else { + // two-state switch, only cares about press events + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggOilPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + Train->mvControlled->OilPumpSwitch( true ); + Train->mvControlled->OilPumpSwitchOff( false ); + } } } void TTrain::OnCommand_oilpumpdisable( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // visual feedback - Train->ggOilPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + if( Command.action == GLFW_REPEAT ) { return; } - if( false == Train->mvControlled->OilPump.is_enabled ) { return; } // already disabled - - Train->mvControlled->OilPumpSwitch( false ); + if( Train->ggOilPumpButton.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->ggOilPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + Train->mvControlled->OilPumpSwitch( false ); + Train->mvControlled->OilPumpSwitchOff( true ); + } } } @@ -2312,8 +2378,17 @@ void TTrain::OnCommand_waterpumpbreakeropen( TTrain *Train, command_data const & void TTrain::OnCommand_waterpumptoggle( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // only reacting to press, so the switch doesn't flip back and forth if key is held down + if( Command.action == GLFW_REPEAT ) { return; } + + if( Train->ggWaterPumpButton.type() == TGaugeType::push ) { + // impulse switch + // currently there's no off button so we always try to turn it on + OnCommand_waterpumpenable( Train, Command ); + } + else { + // two-state switch + if( Command.action == GLFW_RELEASE ) { return; } + if( false == Train->mvControlled->WaterPump.is_enabled ) { // turn on OnCommand_waterpumpenable( Train, Command ); @@ -2327,25 +2402,49 @@ void TTrain::OnCommand_waterpumptoggle( TTrain *Train, command_data const &Comma void TTrain::OnCommand_waterpumpenable( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // visual feedback - Train->ggWaterPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + if( Command.action == GLFW_REPEAT ) { return; } - if( true == Train->mvControlled->WaterPump.is_enabled ) { return; } // already enabled - - Train->mvControlled->WaterPumpSwitch( true ); + if( Train->ggWaterPumpButton.type() == TGaugeType::push ) { + // impulse switch + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggWaterPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + Train->mvControlled->WaterPumpSwitch( true ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + Train->ggWaterPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + Train->mvControlled->WaterPumpSwitch( false ); + } + } + else { + // two-state switch, only cares about press events + if( Command.action == GLFW_PRESS ) { + // visual feedback + Train->ggWaterPumpButton.UpdateValue( 1.0, Train->dsbSwitch ); + Train->mvControlled->WaterPumpSwitch( true ); + Train->mvControlled->WaterPumpSwitchOff( false ); + } } } void TTrain::OnCommand_waterpumpdisable( TTrain *Train, command_data const &Command ) { - if( Command.action == GLFW_PRESS ) { - // visual feedback - Train->ggWaterPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + if( Command.action == GLFW_REPEAT ) { return; } - if( false == Train->mvControlled->WaterPump.is_enabled ) { return; } // already disabled - - Train->mvControlled->WaterPumpSwitch( false ); + if( Train->ggWaterPumpButton.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->ggWaterPumpButton.UpdateValue( 0.0, Train->dsbSwitch ); + Train->mvControlled->WaterPumpSwitch( false ); + Train->mvControlled->WaterPumpSwitchOff( true ); + } } } @@ -5122,14 +5221,13 @@ bool TTrain::Update( double const Deltatime ) ( true == mvControlled->ResistorsFlagCheck() ) || ( mvControlled->MainCtrlActualPos == 0 ) ); // do EU04 - if( ( mvControlled->StLinFlag ) - || ( mvOccupied->BrakePress > 2.0 ) - || ( mvOccupied->PipePress < 3.6 ) ) { - // Ra: czy to jest udawanie działania styczników liniowych? + if( mvControlled->StLinFlag ) { btLampkaStyczn.Turn( false ); } - else if( mvOccupied->BrakePress < 1.0 ) - btLampkaStyczn.Turn( true ); // mozna prowadzic rozruch + else { + // mozna prowadzic rozruch + btLampkaStyczn.Turn( mvOccupied->BrakePress < 1.0 ); + } if( ( ( TestFlag( mvControlled->Couplers[ side::rear ].CouplingFlag, coupling::control ) ) && ( mvControlled->CabNo == 1 ) ) || ( ( TestFlag( mvControlled->Couplers[ side::front ].CouplingFlag, coupling::control ) ) && ( mvControlled->CabNo == -1 ) ) ) btLampkaUkrotnienie.Turn( true ); @@ -7140,10 +7238,12 @@ void TTrain::set_cab_controls() { mvControlled->WaterPump.breaker ? 1.0 : 0.0 ); - ggWaterPumpButton.PutValue( - mvControlled->WaterPump.is_enabled ? - 1.0 : - 0.0 ); + if( ggWaterPumpButton.type() != TGaugeType::push ) { + ggWaterPumpButton.PutValue( + mvControlled->WaterPump.is_enabled ? + 1.0 : + 0.0 ); + } // water heater ggWaterHeaterBreakerButton.PutValue( mvControlled->WaterHeater.breaker ? @@ -7158,15 +7258,19 @@ void TTrain::set_cab_controls() { 1.0 : 0.0 ); // fuel pump - ggFuelPumpButton.PutValue( - mvControlled->FuelPump.is_enabled ? - 1.0 : - 0.0 ); + if( ggFuelPumpButton.type() != TGaugeType::push ) { + ggFuelPumpButton.PutValue( + mvControlled->FuelPump.is_enabled ? + 1.0 : + 0.0 ); + } // oil pump - ggOilPumpButton.PutValue( - mvControlled->OilPump.is_enabled ? + if( ggOilPumpButton.type() != TGaugeType::push ) { + ggOilPumpButton.PutValue( + mvControlled->OilPump.is_enabled ? 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 @@ -7574,9 +7678,9 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con if (Parser.getToken() == "analog") { // McZapkie-300302: zegarek - ggClockSInd.Init(DynamicObject->mdKabina->GetFromName("ClockShand"), TGaugeType::gt_Rotate, 1.0/60.0); - ggClockMInd.Init(DynamicObject->mdKabina->GetFromName("ClockMhand"), TGaugeType::gt_Rotate, 1.0/60.0); - ggClockHInd.Init(DynamicObject->mdKabina->GetFromName("ClockHhand"), TGaugeType::gt_Rotate, 1.0/12.0); + ggClockSInd.Init(DynamicObject->mdKabina->GetFromName("ClockShand"), TGaugeAnimation::gt_Rotate, 1.0/60.0); + ggClockMInd.Init(DynamicObject->mdKabina->GetFromName("ClockMhand"), TGaugeAnimation::gt_Rotate, 1.0/60.0); + ggClockHInd.Init(DynamicObject->mdKabina->GetFromName("ClockHhand"), TGaugeAnimation::gt_Rotate, 1.0/12.0); } } else if (Label == "evoltage:") diff --git a/precipitation.cpp b/precipitation.cpp index 54264bc2..848fec24 100644 --- a/precipitation.cpp +++ b/precipitation.cpp @@ -91,13 +91,18 @@ basic_precipitation::init() { create( 18 ); // TODO: select texture based on current overcast level + // TODO: when the overcast level dynamic change is in check the current level during render and pick the appropriate texture on the fly + std::string const densitysuffix { ( + Global.Overcast < 1.35 ? + "_light" : + "_medium" ) }; if( Global.Weather == "rain:" ) { - m_texture = GfxRenderer.Fetch_Texture( "fx/rain_medium" ); m_moverateweathertypefactor = 2.f; + m_texture = GfxRenderer.Fetch_Texture( "fx/rain" + densitysuffix ); } else if( Global.Weather == "snow:" ) { - m_texture = GfxRenderer.Fetch_Texture( "fx/snow_medium" ); m_moverateweathertypefactor = 1.25f; + m_texture = GfxRenderer.Fetch_Texture( "fx/snow" + densitysuffix ); } return true; @@ -111,7 +116,7 @@ basic_precipitation::update() { if( timedelta == 0.0 ) { return; } m_textureoffset += m_moverate * m_moverateweathertypefactor * timedelta; - m_textureoffset = clamp_circular( m_textureoffset, 1.f ); + m_textureoffset = clamp_circular( m_textureoffset, 10.f ); auto cameramove { glm::dvec3{ Global.pCamera.Pos - m_camerapos} }; cameramove.y = 0.0; // vertical movement messes up vector calculation From 913541bbee8ba811dd54fb375c47983d6b1ff4f6 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Mon, 1 Oct 2018 15:54:46 +0200 Subject: [PATCH 10/10] build 181001. texture size definition, fog influence on skydome, minor vehicle logic bug fixes --- Driver.cpp | 2 +- DynObj.cpp | 15 +++-- McZapkie/Mover.cpp | 9 ++- Model3d.cpp | 8 +-- PyInt.cpp | 4 ++ Track.cpp | 119 +++++++++++++++++++++++--------------- Track.h | 3 +- material.cpp | 7 +++ material.h | 1 + openglmatrixstack.h | 21 +++++-- renderer.cpp | 26 +++++---- simulationenvironment.cpp | 4 +- station.cpp | 1 + version.h | 2 +- 14 files changed, 142 insertions(+), 80 deletions(-) diff --git a/Driver.cpp b/Driver.cpp index be6c926e..a4666bea 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -962,7 +962,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN // perform loading/unloading auto const platformside = static_cast( std::floor( std::abs( sSpeedTable[ i ].evEvent->input_value( 2 ) ) ) ) % 10; - auto const exchangetime = simulation::Station.update_load( pVehicles[ 0 ], *TrainParams, platformside ); + auto const exchangetime = std::max( 5.0, simulation::Station.update_load( pVehicles[ 0 ], *TrainParams, platformside ) ); WaitingSet( std::max( -fStopTime, exchangetime ) ); // na końcu rozkładu się ustawia 60s i tu by było skrócenie if( TrainParams->CheckTrainLatency() < 0.0 ) { diff --git a/DynObj.cpp b/DynObj.cpp index f9321f88..360ff418 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3971,9 +3971,15 @@ void TDynamicObject::RenderSounds() { sSmallCompressor.stop(); } - // heating sound - if( MoverParameters->Heating ) { - sHeater.play( sound_flags::exclusive | sound_flags::looping ); + // heater sound + if( ( true == MoverParameters->Heating ) + && ( std::abs( MoverParameters->enrot ) > 0.01 ) ) { + // TBD: check whether heating should depend on 'engine rotations' for electric vehicles + sHeater + .pitch( true == sHeater.is_combined() ? + std::abs( MoverParameters->enrot ) * 60.f * 0.01f : + 1.f ) + .play( sound_flags::exclusive | sound_flags::looping ); } else { sHeater.stop(); @@ -3997,8 +4003,7 @@ void TDynamicObject::RenderSounds() { } else if( quantizedratiochange < 0 ) { m_brakecylinderpistonrecede - .pitch( - true == m_brakecylinderpistonrecede.is_combined() ? + .pitch( true == m_brakecylinderpistonrecede.is_combined() ? quantizedratio * 0.01f : m_brakecylinderpistonrecede.m_frequencyoffset + m_brakecylinderpistonrecede.m_frequencyfactor * 1.f ) .play(); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index b9f521e8..34fd7fd3 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1570,11 +1570,12 @@ void TMoverParameters::OilPumpCheck( double const Timestep ) { OilPump.is_active = ( ( true == Battery ) + && ( false == Mains ) && ( false == OilPump.is_disabled ) && ( ( OilPump.is_active ) || ( OilPump.start_type == start_t::manual ? ( OilPump.is_enabled ) : - OilPump.start_type == start_t::automatic ? ( dizel_startup || Mains ) : - OilPump.start_type == start_t::manualwithautofallback ? ( OilPump.is_enabled || dizel_startup || Mains ) : + OilPump.start_type == start_t::automatic ? ( dizel_startup ) : + OilPump.start_type == start_t::manualwithautofallback ? ( OilPump.is_enabled || dizel_startup ) : false ) ) ); // shouldn't ever get this far but, eh auto const maxrevolutions { @@ -2642,7 +2643,6 @@ bool TMoverParameters::MainSwitch( bool const State, range_t const Notify ) Mains = false; // potentially knock out the pumps if their switch doesn't force them on WaterPump.is_active &= WaterPump.is_enabled; - OilPump.is_active &= OilPump.is_enabled; FuelPump.is_active &= FuelPump.is_enabled; } @@ -4765,7 +4765,7 @@ double TMoverParameters::TractionForce( double dt ) { Voltage = 0; // przekazniki bocznikowania, kazdy inny dla kazdej pozycji - if ((MainCtrlPos == 0) || (ShuntMode)) + if ((MainCtrlPos == 0) || (ShuntMode) || (false==Mains)) ScndCtrlPos = 0; else { @@ -9305,7 +9305,6 @@ bool TMoverParameters::RunCommand( std::string Command, double CValue1, double C Mains = false; // potentially knock out the pumps if their switch doesn't force them on WaterPump.is_active &= WaterPump.is_enabled; - OilPump.is_active &= OilPump.is_enabled; FuelPump.is_active &= FuelPump.is_enabled; } OK = SendCtrlToNext( Command, CValue1, CValue2, Couplertype ); diff --git a/Model3d.cpp b/Model3d.cpp index 63f75d55..272cb7cb 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -135,7 +135,7 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic iVboPtr = Pos; // pozycja w VBO */ if (!parser.expectToken("type:")) - Error("Model type parse failure!"); + ErrorLog("Bad model: expected submodel type definition not found while loading model \"" + Model->NameGet() + "\"" ); { std::string type = parser.getToken(); if (type == "mesh") @@ -1225,8 +1225,10 @@ bool TModel3d::LoadFromFile(std::string const &FileName, bool dynamic) } */ auto const name { FileName }; + // cache the file name, in case someone wants it later + m_filename = name; - asBinary = name + ".e3d"; + asBinary = name + ".e3d"; if (FileExists(asBinary)) { LoadFromBinFile(asBinary, dynamic); @@ -1244,8 +1246,6 @@ bool TModel3d::LoadFromFile(std::string const &FileName, bool dynamic) } } } - // cache the file name, in case someone wants it later - m_filename = name; bool const result = Root ? (iSubModelsCount > 0) : false; // brak pliku albo problem z wczytaniem if (false == result) diff --git a/PyInt.cpp b/PyInt.cpp index 7a201cfc..ff4ddeb3 100644 --- a/PyInt.cpp +++ b/PyInt.cpp @@ -32,6 +32,10 @@ void render_task::run() { ::glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + if( GLEW_EXT_texture_filter_anisotropic ) { + // anisotropic filtering + ::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering ); + } ::glTexEnvf( GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.0 ); // build texture ::glTexImage2D( diff --git a/Track.cpp b/Track.cpp index 982b77c6..598e7f52 100644 --- a/Track.cpp +++ b/Track.cpp @@ -1254,8 +1254,10 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { 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 + 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( fTexLength == 4.f ) { + 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 ) ); @@ -1318,7 +1320,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } else { // mapowanie proporcjonalne do powierzchni, rozmiar w poprzek określa fTexLength - auto const max = fTexRatio2 * fTexLength; // szerokość proporcjonalna do długości + 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) { @@ -1383,7 +1385,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } } gfx::vertex_array vertices; - Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -4 : 4, fTexLength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -4 : 4, texturelength); if( ( Bank != 0 ) && ( true == Geometry2.empty() ) ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } @@ -1394,20 +1396,21 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } if (m_material1) { // szyny - generujemy dwie, najwyżej rysować się będzie jedną + auto const texturelength { texture_length( m_material1 ) }; gfx::vertex_array vertices; if( ( Bank != 0 ) && ( true == Geometry1.empty() ) ) { - Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, texturelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // reuse the scratchpad - Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, texturelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if( ( Bank == 0 ) && ( false == Geometry1.empty() ) ) { // special variant, replace existing data for a turntable track - Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts1, iTrapezoid ? -nnumPts : nnumPts, texturelength ); GfxRenderer.Replace( vertices, Geometry1[ 0 ] ); vertices.clear(); // reuse the scratchpad - Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts2, iTrapezoid ? -nnumPts : nnumPts, texturelength ); GfxRenderer.Replace( vertices, Geometry1[ 1 ] ); } } @@ -1452,28 +1455,30 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { { // nowa wersja z SPKS, ale odwrotnie lewa/prawa gfx::vertex_array vertices; if( m_material1 ) { + auto const texturelength { texture_length( m_material1 ) }; // fixed parts - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, bladelength ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // left blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength, SwitchExtension->fOffset2 ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( m_material2 ) { + auto const texturelength { texture_length( m_material2 ) }; // fixed parts - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, bladelength ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // right blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -fMaxOffset + SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength, -fMaxOffset + SwitchExtension->fOffset1 ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1482,28 +1487,30 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { { // lewa działa lepiej niż prawa gfx::vertex_array vertices; if( m_material1 ) { + auto const texturelength { texture_length( m_material1 ) }; // fixed parts - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength ); // lewa szyna normalna cała + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength ); // lewa szyna normalna cała Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, bladelength ); // prawa szyna za iglicą + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength, 1.0, bladelength ); // prawa szyna za iglicą Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // right blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength, -SwitchExtension->fOffset2 ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( m_material2 ) { + auto const texturelength { texture_length( m_material2 ) }; // fixed parts - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength ); // prawa szyna normalnie cała + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, texturelength ); // prawa szyna normalnie cała Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, bladelength ); // lewa szyna za iglicą + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, texturelength, 1.0, bladelength ); // lewa szyna za iglicą Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // left blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, fMaxOffset - SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength, fMaxOffset - SwitchExtension->fOffset1 ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1521,10 +1528,8 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { 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 = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości + 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) { @@ -1561,12 +1566,14 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } if (m_material1) // jeśli podana była tekstura, generujemy trójkąty { // tworzenie trójkątów nawierzchni szosy + auto const texturelength { texture_length( m_material1 ) }; gfx::vertex_array vertices; - Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -2 : 2, fTexLength); + Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid ? -2 : 2, texturelength); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); } if (m_material2) { // pobocze drogi - poziome przy przechyłce (a może krawężnik i chodnik zrobić jak w Midtown Madness 2?) + 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 @@ -1629,7 +1636,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { // 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 * fTexLength; // test: szerokość proporcjonalna do długości + auto const max = fTexRatio2 * texturelength; // test: szerokość proporcjonalna do długości auto const map1l = ( max > 0.f ? side / max : @@ -1726,24 +1733,24 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony // odcinka if( ( fTexHeight1 >= 0.0 ) || ( slop != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts1, -3, fTexLength ); // tylko jeśli jest z prawej + Segment->RenderLoft( vertices, m_origin, rpts1, -3, texturelength ); // tylko jeśli jest z prawej Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength ); // tylko jeśli jest z lewej + Segment->RenderLoft( vertices, m_origin, rpts2, -3, texturelength ); // tylko jeśli jest z lewej Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } } else { // pobocza zwykłe, brak przechyłki if( ( fTexHeight1 >= 0.0 ) || ( slop != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts1, 3, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts1, 3, texturelength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - Segment->RenderLoft( vertices, m_origin, rpts2, 3, fTexLength ); + Segment->RenderLoft( vertices, m_origin, rpts2, 3, texturelength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1803,7 +1810,8 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { 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 = fTexRatio1 * fTexLength; // test: szerokość proporcjonalna do długości + 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 @@ -1833,6 +1841,7 @@ 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 @@ -1896,7 +1905,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { // 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 * fTexLength; // test: szerokość proporcjonalna do długości + auto const max = fTexRatio2 * texturelength; // test: szerokość proporcjonalna do długości auto const map1l = ( max > 0.f ? side / max : @@ -1976,22 +1985,22 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { if (SwitchExtension->iRoads == 4) { // pobocza do trapezowatej nawierzchni - dodatkowe punkty z drugiej strony odcinka if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 3 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 3 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 4 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 4 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 5 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); + SwitchExtension->Segments[ 5 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, 0.0, &b, render ); if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); @@ -2001,17 +2010,17 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { else { // punkt 3 pokrywa się z punktem 1, jak w zwrotnicy; połączenie 1->2 nie musi być prostoliniowe if( ( fTexHeight1 >= 0.0 ) || ( side != 0.0 ) ) { - SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P2 do P4 + SwitchExtension->Segments[ 2 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, 0.0, &b, render ); // z P2 do P4 if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P4 do P3=P1 (odwrócony) + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, 0.0, &b, render ); // z P4 do P3=P1 (odwrócony) if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, -3, fTexLength, 1.0, 0, 0, 0.0, &b, render ); // z P1 do P2 + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, -3, texturelength, 1.0, 0, 0, 0.0, &b, render ); // z P1 do P2 if( true == render ) { Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); @@ -2028,6 +2037,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } if( m_material1 ) { + auto const texturelength { texture_length( m_material1 ) }; gfx::vertex_array vertices; // jeśli podana tekstura nawierzchni // we start with a vertex in the middle... @@ -2039,8 +2049,8 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { glm::vec3{ 0.0f, 1.0f, 0.0f }, glm::vec2{ 0.5f, 0.5f } ); // ...and add one extra vertex to close the fan... - u = ( SwitchExtension->vPoints[ 0 ].x - oxz.x + m_origin.x ) / fTexLength; - v = ( SwitchExtension->vPoints[ 0 ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * fTexLength ); + u = ( SwitchExtension->vPoints[ 0 ].x - oxz.x + m_origin.x ) / texturelength; + v = ( SwitchExtension->vPoints[ 0 ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * texturelength ); vertices.emplace_back( glm::vec3 { SwitchExtension->vPoints[ 0 ].x, @@ -2054,8 +2064,8 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { // ...then draw the precalculated rest for (int i = pointcount + SwitchExtension->iRoads - 1; i >= 0; --i) { // mapowanie we współrzędnych scenerii - u = ( SwitchExtension->vPoints[ i ].x - oxz.x + m_origin.x ) / fTexLength; - v = ( SwitchExtension->vPoints[ i ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * fTexLength ); + u = ( SwitchExtension->vPoints[ i ].x - oxz.x + m_origin.x ) / texturelength; + v = ( SwitchExtension->vPoints[ i ].z - oxz.z + m_origin.z ) / ( fTexRatio1 * texturelength ); vertices.emplace_back( glm::vec3 { SwitchExtension->vPoints[ i ].x, @@ -2507,28 +2517,32 @@ TTrack * TTrack::RaAnimate() if (SwitchExtension->RightSwitch) { // nowa wersja z SPKS, ale odwrotnie lewa/prawa if( m_material1 ) { + auto const texturelength { texture_length( m_material1 ) }; // left blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength, SwitchExtension->fOffset2 ); GfxRenderer.Replace( vertices, Geometry1[ 2 ] ); vertices.clear(); } if( m_material2 ) { + auto const texturelength { texture_length( m_material2 ) }; // right blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -fMaxOffset + SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength, -fMaxOffset + SwitchExtension->fOffset1 ); GfxRenderer.Replace( vertices, Geometry2[ 2 ] ); vertices.clear(); } } else { // lewa działa lepiej niż prawa if( m_material1 ) { + auto const texturelength { texture_length( m_material1 ) }; // right blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, texturelength, 1.0, 0, bladelength, -SwitchExtension->fOffset2 ); GfxRenderer.Replace( vertices, Geometry1[ 2 ] ); vertices.clear(); } if( m_material2 ) { + auto const texturelength { texture_length( m_material2 ) }; // left blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, fMaxOffset - SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, texturelength, 1.0, 0, bladelength, fMaxOffset - SwitchExtension->fOffset1 ); GfxRenderer.Replace( vertices, Geometry2[ 2 ] ); vertices.clear(); } @@ -2868,6 +2882,19 @@ TTrack::export_as_text_( std::ostream &Output ) const { << "\n"; } +float +TTrack::texture_length( material_handle const Material ) { + + if( Material == null_handle ) { + return fTexLength; + } + auto const texturelength { GfxRenderer.Material( Material ).size.y }; + return ( + texturelength < 0.f ? + fTexLength : + texturelength ); +} + void TTrack::MovedUp1(float const dh) { // poprawienie przechyłki wymaga wydłużenia podsypki fTexHeight1 += dh; diff --git a/Track.h b/Track.h index 9fb57165..e3ca3233 100644 --- a/Track.h +++ b/Track.h @@ -298,7 +298,8 @@ private: void deserialize_( std::istream &Input ); // export() subclass details, sends basic content of the class in legacy (text) format to provided stream void export_as_text_( std::ostream &Output ) const; - + // returns texture length for specified material + float texture_length( material_handle const Material ); }; diff --git a/material.cpp b/material.cpp index d5c03784..f83c5e58 100644 --- a/material.cpp +++ b/material.cpp @@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/. #include "material.h" #include "renderer.h" #include "utilities.h" +#include "sn_utils.h" #include "globals.h" bool @@ -73,6 +74,12 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c priority2 = Priority; } } + else if( key == "size:" ) { + Input.getTokens( 2 ); + Input + >> size.x + >> size.y; + } else { auto const value { Input.getToken( true, "\n\r\t ;" ) }; if( value == "{" ) { diff --git a/material.h b/material.h index d4f64088..104990de 100644 --- a/material.h +++ b/material.h @@ -24,6 +24,7 @@ struct opengl_material { bool has_alpha { false }; // alpha state, calculated from presence of alpha in texture1 std::string name; + glm::vec2 size { -1.f, -1.f }; // 'physical' size of bound texture, in meters // constructors opengl_material() = default; diff --git a/openglmatrixstack.h b/openglmatrixstack.h index d37a8cf1..b37a1532 100644 --- a/openglmatrixstack.h +++ b/openglmatrixstack.h @@ -57,6 +57,10 @@ public: translate( glm::vec3 const &Translation ) { m_stack.top() = glm::translate( m_stack.top(), Translation ); upload(); } + void + scale( glm::vec3 const &Scale ) { + m_stack.top() = glm::scale( m_stack.top(), Scale ); + upload(); } void multiply( glm::mat4 const &Matrix ) { m_stack.top() *= Matrix; @@ -133,9 +137,9 @@ public: m_stacks[ m_mode ].rotate( static_cast(glm::radians(Angle)), glm::vec3( - static_cast(X), - static_cast(Y), - static_cast(Z) ) ); } + static_cast( X ), + static_cast( Y ), + static_cast( Z ) ) ); } template void translate( Type_ const X, Type_ const Y, Type_ const Z ) { @@ -145,6 +149,14 @@ public: static_cast( Y ), static_cast( Z ) ) ); } template + void + scale( Type_ const X, Type_ const Y, Type_ const Z ) { + m_stacks[ m_mode ].scale( + glm::vec3( + static_cast( X ), + static_cast( Y ), + static_cast( Z ) ) ); } + template void multiply( Type_ const *Matrix ) { m_stacks[ m_mode ].multiply( @@ -204,7 +216,8 @@ extern opengl_matrices OpenGLMatrices; #define glRotatef OpenGLMatrices.rotate #define glTranslated OpenGLMatrices.translate #define glTranslatef OpenGLMatrices.translate -// NOTE: no scale override as we aren't using it anywhere +#define glScaled OpenGLMatrices.scale +#define glScalef OpenGLMatrices.scale #define glMultMatrixd OpenGLMatrices.multiply #define glMultMatrixf OpenGLMatrices.multiply #define glOrtho OpenGLMatrices.ortho diff --git a/renderer.cpp b/renderer.cpp index f5d6d9c4..bdcd6d15 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -1433,9 +1433,12 @@ opengl_renderer::Render( world_environment *Environment ) { ::glDisable( GL_LIGHTING ); ::glDisable( GL_DEPTH_TEST ); ::glDepthMask( GL_FALSE ); - ::glPushMatrix(); // skydome + // drawn with 500m radius to blend in if the fog range is low + ::glPushMatrix(); + ::glScalef( 500.f, 500.f, 500.f ); Environment->m_skydome.Render(); + ::glPopMatrix(); // stars if( Environment->m_stars.m_stars != nullptr ) { // setup @@ -1450,12 +1453,6 @@ opengl_renderer::Render( world_environment *Environment ) { ::glPopMatrix(); } // celestial bodies - float const duskfactor = 1.0f - clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f; - glm::vec3 suncolor = interpolate( - glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ), - glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ), - duskfactor ); - if( DebugModeFlag == true ) { // mark sun position for easier debugging Environment->m_sun.render(); @@ -1470,10 +1467,17 @@ opengl_renderer::Render( world_environment *Environment ) { ::glBlendFunc( GL_SRC_ALPHA, GL_ONE ); auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW ); + + auto const fogfactor { clamp( Global.fFogEnd / 2000.f, 0.f, 1.f ) }; // stronger fog reduces opacity of the celestial bodies + float const duskfactor = 1.0f - clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f; + glm::vec3 suncolor = interpolate( + glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ), + glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ), + duskfactor ); // sun { Bind_Texture( m_suntexture ); - ::glColor4f( suncolor.x, suncolor.y, suncolor.z, clamp( 1.5f - Global.Overcast, 0.f, 1.f ) ); + ::glColor4f( suncolor.x, suncolor.y, suncolor.z, clamp( 1.5f - Global.Overcast, 0.f, 1.f ) * fogfactor ); auto const sunvector = Environment->m_sun.getDirection(); auto const sunposition = modelview * glm::vec4( sunvector.x, sunvector.y, sunvector.z, 1.0f ); @@ -1495,14 +1499,15 @@ opengl_renderer::Render( world_environment *Environment ) { { Bind_Texture( m_moontexture ); glm::vec3 mooncolor( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ); - // fade the moon if it's near the sun in the sky, especially during the day ::glColor4f( mooncolor.r, mooncolor.g, mooncolor.b, + // fade the moon if it's near the sun in the sky, especially during the day std::max( 0.f, 1.0 - 0.5 * Global.fLuminance - - 0.65 * std::max( 0.f, glm::dot( Environment->m_sun.getDirection(), Environment->m_moon.getDirection() ) ) ) ); + - 0.65 * std::max( 0.f, glm::dot( Environment->m_sun.getDirection(), Environment->m_moon.getDirection() ) ) ) + * fogfactor ); auto const moonposition = modelview * glm::vec4( Environment->m_moon.getDirection(), 1.0f ); ::glPushMatrix(); @@ -1563,7 +1568,6 @@ opengl_renderer::Render( world_environment *Environment ) { ::glDisable( GL_LIGHTING ); } - ::glPopMatrix(); ::glDepthMask( GL_TRUE ); ::glEnable( GL_DEPTH_TEST ); ::glEnable( GL_LIGHTING ); diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index 5602f7af..5033c753 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -62,8 +62,8 @@ void world_environment::compute_weather() const { Global.Weather = ( - Global.Overcast < 0.25 ? "clear:" : - Global.Overcast < 1.0 ? "cloudy:" : + Global.Overcast <= 0.25 ? "clear:" : + Global.Overcast <= 1.0 ? "cloudy:" : ( Global.Season != "winter:" ? "rain:" : "snow:" ) ); diff --git a/station.cpp b/station.cpp index 75f7f267..63b5b0a6 100644 --- a/station.cpp +++ b/station.cpp @@ -51,6 +51,7 @@ basic_station::update_load( TDynamicObject *First, Mtable::TTrainParameters &Sch if( parameters.LoadType.name.empty() ) { // (try to) set the cargo type for empty cars + parameters.LoadAmount = 0.f; // safety measure against edge cases parameters.AssignLoad( "passengers" ); } diff --git a/version.h b/version.h index 66a33f7e..abf7d5cf 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 18 -#define VERSION_MINOR 928 +#define VERSION_MINOR 1001 #define VERSION_REVISION 0