From bc43c21174406883b07324a2ced50b8b344c96db Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Sun, 19 Nov 2017 16:02:12 +0100 Subject: [PATCH] audio subsystem: openal emitter, minor audio fixes --- Button.cpp | 1 + DynObj.cpp | 73 ++++++++++++++++++--- Globals.cpp | 32 +-------- Globals.h | 22 +------ McZapkie/MOVER.h | 22 +++---- ResourceManager.h | 7 ++ Texture.h | 3 +- Train.cpp | 66 +++++++------------ Train.h | 2 +- World.cpp | 25 +++---- World.h | 7 +- audio.cpp | 33 ++++++---- audio.h | 6 ++ audiorenderer.cpp | 132 ++++++++++++++++++++++++++++++++++++- audiorenderer.h | 76 +++++++++++++++++++++- maszyna.vcxproj.filters | 10 +-- openglgeometrybank.cpp | 2 +- openglgeometrybank.h | 2 +- renderer.cpp | 7 +- renderer.h | 2 +- simulation.cpp | 5 +- sound.cpp | 140 ++++++++++++++++++++++++++++++++++++++-- sound.h | 48 +++++++++++--- sun.cpp | 25 +++++-- 24 files changed, 566 insertions(+), 182 deletions(-) diff --git a/Button.cpp b/Button.cpp index 22cfe6c4..c7b501a4 100644 --- a/Button.cpp +++ b/Button.cpp @@ -46,6 +46,7 @@ void TButton::Load(cParser &Parser, TModel3d *pModel1, TModel3d *pModel2) { else { // new, block type config // TODO: rework the base part into yaml-compatible flow style mapping + submodelname = Parser.getToken( false ); while( true == Load_mapping( Parser ) ) { ; // all work done by while() } diff --git a/DynObj.cpp b/DynObj.cpp index ad105306..c1846335 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3354,6 +3354,7 @@ bool TDynamicObject::Update(double dt, double dt1) #ifdef EU07_USE_OLD_SOUNDCODE rsDoorOpen.Play(1, 0, MechInside, vPosition); #else + rsDoorClose.stop(); rsDoorOpen.play(); #endif dDoorMoveL += dt1 * 0.5 * MoverParameters->DoorOpenSpeed; @@ -3363,6 +3364,7 @@ bool TDynamicObject::Update(double dt, double dt1) #ifdef EU07_USE_OLD_SOUNDCODE rsDoorClose.Play(1, 0, MechInside, vPosition); #else + rsDoorOpen.stop(); rsDoorClose.play(); #endif dDoorMoveL -= dt1 * MoverParameters->DoorCloseSpeed; @@ -3374,6 +3376,7 @@ bool TDynamicObject::Update(double dt, double dt1) #ifdef EU07_USE_OLD_SOUNDCODE rsDoorOpen.Play(1, 0, MechInside, vPosition); #else + rsDoorClose.stop(); rsDoorOpen.play(); #endif dDoorMoveR += dt1 * 0.5 * MoverParameters->DoorOpenSpeed; @@ -3383,6 +3386,7 @@ bool TDynamicObject::Update(double dt, double dt1) #ifdef EU07_USE_OLD_SOUNDCODE rsDoorClose.Play(1, 0, MechInside, vPosition); #else + rsDoorOpen.stop(); rsDoorClose.play(); #endif dDoorMoveR -= dt1 * MoverParameters->DoorCloseSpeed; @@ -3849,16 +3853,70 @@ void TDynamicObject::RenderSounds() { if (GetVelocity() == 0) rsDerailment.Stop(); } +#else +// McZapkie! - dzwiek compressor.wav tylko gdy dziala sprezarka +if( MoverParameters->VeselVolume != 0 ) { + + if( MoverParameters->TrainType != dt_PseudoDiesel ) { + // NBMX dzwiek przetwornicy + if( MoverParameters->ConverterFlag ) { + sConverter.play(); + } + else { + sConverter.stop(); + } + } + else { + } + + if( MoverParameters->CompressorFlag ) { + sCompressor.play(); + } + else { + sCompressor.stop(); + } + if( MoverParameters->PantCompFlag ) { + // Winger 160404 - dzwiek malej sprezarki + sSmallCompressor.play(); + } + else { + sSmallCompressor.stop(); + } + + + if( TestFlag( MoverParameters->WarningSignal, 1 ) ) { + sHorn1.play(); + } + else { + sHorn1.stop(); + } + if( TestFlag( MoverParameters->WarningSignal, 2 ) ) { + sHorn2.play(); + } + else { + sHorn2.stop(); + } + + if( MoverParameters->DoorClosureWarning ) { + if( MoverParameters->DepartureSignal ) { + // NBMX sygnal odjazdu + // MC: pod warunkiem ze jest zdefiniowane w chk + sDepartureSignal.play(); + } + else { + sDepartureSignal.stop(); + } + } + +} #endif }; // McZapkie-250202 // wczytywanie pliku z danymi multimedialnymi (dzwieki) -void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, - std::string ReplacableSkin) -{ +void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName, std::string ReplacableSkin ) { + double dSDist; - // asBaseDir=BaseDir; Global::asCurrentDynamicPath = BaseDir; std::string asFileName = BaseDir + TypeName + ".mmd"; std::string asLoadName; @@ -3898,8 +3956,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, } m_materialdata.multi_textures = clamp( m_materialdata.multi_textures, 0, 1 ); // na razie ustawiamy na 1 } - asModel = BaseDir + asModel; // McZapkie 2002-07-20: dynamics maja swoje - // modele w dynamics/basedir + asModel = BaseDir + asModel; // McZapkie 2002-07-20: dynamics maja swoje modele w dynamics/basedir Global::asCurrentTexturePath = BaseDir; // biezaca sciezka do tekstur to dynamic/... mdModel = TModelsManager::GetModel(asModel, true); assert( mdModel != nullptr ); // TODO: handle this more gracefully than all going to shit @@ -4757,9 +4814,8 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, } else if( token == "curve:" ) { - - double attenuation; #ifdef EU07_USE_OLD_SOUNDCODE + double attenuation; parser.getTokens( 2, false ); parser >> token @@ -4959,6 +5015,7 @@ void TDynamicObject::LoadMMediaFile(std::string BaseDir, std::string TypeName, mdLowPolyInt->Init(); Global::asCurrentTexturePath = szTexturePath; // kiedyś uproszczone wnętrze mieszało tekstury nieba + Global::asCurrentDynamicPath = ""; } //--------------------------------------------------------------------------- diff --git a/Globals.cpp b/Globals.cpp index 43c66c17..8bf348b4 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -53,19 +53,15 @@ std::string Global::szTexturesDDS = ".dds"; // lista tekstur od DDS int Global::iPause = 0; // 0x10; // globalna pauza ruchu bool Global::bActive = true; // czy jest aktywnym oknem TWorld *Global::pWorld = NULL; -cParser *Global::pParser = NULL; TCamera *Global::pCamera = NULL; // parametry kamery -TDynamicObject *Global::pUserDynamic = NULL; // pojazd użytkownika, renderowany bez trzęsienia TTranscripts Global::tranTexts; // obiekt obsługujący stenogramy dźwięków na ekranie float4 Global::UITextColor = float4( 225.0 / 255.0f, 225.0f / 255.0f, 225.0f / 255.0f, 1.0f ); -// parametry scenerii vector3 Global::pCameraPosition; vector3 Global::DebugCameraPosition; -double Global::pCameraRotation; -double Global::pCameraRotationDeg; std::vector Global::FreeCameraInit; std::vector Global::FreeCameraInitAngle; +// parametry scenerii GLfloat Global::FogColor[] = {0.6f, 0.7f, 0.8f}; double Global::fFogStart = 1700; double Global::fFogEnd = 2000; @@ -127,8 +123,6 @@ bool Global::bGlutFont = false; // czy tekst generowany przez GLUT32.DLL int Global::iConvertModels{ 0 }; // temporary override, to prevent generation of .e3d not compatible with old exe int Global::iSlowMotionMask = -1; // maska wyłączanych właściwości dla zwiększenia FPS // bool Global::bTerrainCompact=true; //czy zapisać teren w pliku -TAnimModel *Global::pTerrainCompact = NULL; // do zapisania terenu w pliku -std::string Global::asTerrainModel = ""; // nazwa obiektu terenu do zapisania w pliku double Global::fFpsAverage = 20.0; // oczekiwana wartosć FPS double Global::fFpsDeviation = 5.0; // odchylenie standardowe FPS double Global::fFpsMin = 30.0; // dolna granica FPS, przy której promień scenerii będzie zmniejszany @@ -965,16 +959,6 @@ void Global::SetCameraPosition(vector3 pNewCameraPosition) pCameraPosition = pNewCameraPosition; } -void Global::SetCameraRotation(double Yaw) -{ // ustawienie bezwzględnego kierunku kamery z korekcją do przedziału <-M_PI,M_PI> - pCameraRotation = Yaw; - while (pCameraRotation < -M_PI) - pCameraRotation += 2 * M_PI; - while (pCameraRotation > M_PI) - pCameraRotation -= 2 * M_PI; - pCameraRotationDeg = pCameraRotation * 180.0 / M_PI; -} - void Global::TrainDelete(TDynamicObject *d) { // usunięcie pojazdu prowadzonego przez użytkownika if (pWorld) @@ -983,20 +967,6 @@ void Global::TrainDelete(TDynamicObject *d) //--------------------------------------------------------------------------- -bool Global::DoEvents() -{ // wywoływać czasem, żeby nie robił wrażenia zawieszonego - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - if (msg.message == WM_QUIT) - return FALSE; - TranslateMessage(&msg); - DispatchMessage(&msg); - } - return TRUE; -} -//--------------------------------------------------------------------------- - TTranscripts::TTranscripts() { /* diff --git a/Globals.h b/Globals.h index 1dba16ae..9c7fe26a 100644 --- a/Globals.h +++ b/Globals.h @@ -10,10 +10,8 @@ http://mozilla.org/MPL/2.0/. #pragma once #include -#include + #include "renderer.h" -#include "glfw/glfw3.h" -#include "gl/glew.h" #include "dumb3d.h" // definicje klawiszy @@ -113,16 +111,6 @@ const int k_WalkMode = 73; int const k_DimHeadlights = 74; const int MaxKeys = 75; -// klasy dla wskaźników globalnych -/* -class TGround; -class TWorld; -class TCamera; -class TDynamicObject; -class TAnimModel; // obiekt terenu -class cParser; // nowy (powolny!) parser -class TEvent; -*/ class TTranscript { // klasa obsługująca linijkę napisu do dźwięku public: @@ -159,9 +147,7 @@ public: static void InitKeys(); inline static Math3D::vector3 GetCameraPosition() { return pCameraPosition; }; static void SetCameraPosition(Math3D::vector3 pNewCameraPosition); - static void SetCameraRotation(double Yaw); static void TrainDelete(TDynamicObject *d); - static bool DoEvents(); static std::string Bezogonkow(std::string str, bool _ = false); static double Min0RSpeed(double vel1, double vel2); @@ -170,8 +156,6 @@ public: static bool RealisticControlMode; // controls ability to steer the vehicle from outside views static Math3D::vector3 pCameraPosition; // pozycja kamery w świecie static Math3D::vector3 DebugCameraPosition; // pozycja kamery w świecie - static double pCameraRotation; // kierunek bezwzględny kamery w świecie: 0=północ, 90°=zachód (-azymut) - static double pCameraRotationDeg; // w stopniach, dla animacji billboard static std::vector FreeCameraInit; // pozycje kamery static std::vector FreeCameraInitAngle; static int iWindowWidth; @@ -272,16 +256,12 @@ public: static bool bHideConsole; // hunter-271211: ukrywanie konsoli static TWorld *pWorld; // wskaźnik na świat do usuwania pojazdów - static TAnimModel *pTerrainCompact; // obiekt terenu do ewentualnego zapisania w pliku - static std::string asTerrainModel; // nazwa obiektu terenu do zapisania w pliku static bool bRollFix; // czy wykonać przeliczanie przechyłki - static cParser *pParser; static double fFpsAverage; // oczekiwana wartosć FPS static double fFpsDeviation; // odchylenie standardowe FPS static double fFpsMin; // dolna granica FPS, przy której promień scenerii będzie zmniejszany static double fFpsMax; // górna granica FPS, przy której promień scenerii będzie zwiększany static TCamera *pCamera; // parametry kamery - static TDynamicObject *pUserDynamic; // pojazd użytkownika, renderowany bez trzęsienia static double fCalibrateIn[6][6]; // parametry kalibracyjne wejść z pulpitu static double fCalibrateOut[7][6]; // parametry kalibracyjne wyjść dla pulpitu static double fCalibrateOutMax[7]; // wartości maksymalne wyjść dla pulpitu diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index ce949609..e63e297d 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -547,18 +547,18 @@ struct TMotorParameters struct TSecuritySystem { - int SystemType; /*0: brak, 1: czuwak aktywny, 2: SHP/sygnalizacja kabinowa*/ - double AwareDelay; // czas powtarzania czuwaka + int SystemType { 0 }; /*0: brak, 1: czuwak aktywny, 2: SHP/sygnalizacja kabinowa*/ + double AwareDelay { -1.0 }; // czas powtarzania czuwaka double AwareMinSpeed; // minimalna prędkość załączenia czuwaka, normalnie 10% Vmax - double SoundSignalDelay; - double EmergencyBrakeDelay; - int Status; /*0: wylaczony, 1: wlaczony, 2: czuwak, 4: shp, 8: alarm, 16: hamowanie awaryjne*/ - double SystemTimer; - double SystemSoundCATimer; - double SystemSoundSHPTimer; - double SystemBrakeCATimer; - double SystemBrakeSHPTimer; - double SystemBrakeCATestTimer; // hunter-091012 + double SoundSignalDelay { -1.0 }; + double EmergencyBrakeDelay { -1.0 }; + int Status { 0 }; /*0: wylaczony, 1: wlaczony, 2: czuwak, 4: shp, 8: alarm, 16: hamowanie awaryjne*/ + double SystemTimer { 0.0 }; + double SystemSoundCATimer { 0.0 }; + double SystemSoundSHPTimer { 0.0 }; + double SystemBrakeCATimer { 0.0 }; + double SystemBrakeSHPTimer { 0.0 }; + double SystemBrakeCATestTimer { 0.0 }; // hunter-091012 int VelocityAllowed; int NextVelocityAllowed; /*predkosc pokazywana przez sygnalizacje kabinowa*/ bool RadioStop; // czy jest RadioStop diff --git a/ResourceManager.h b/ResourceManager.h index 2d467e19..92fec3ca 100644 --- a/ResourceManager.h +++ b/ResourceManager.h @@ -9,6 +9,8 @@ http://mozilla.org/MPL/2.0/. #pragma once +int const null_handle = 0; + enum class resource_state { none, loading, @@ -64,6 +66,11 @@ class ResourceManager }; */ +using resource_timestamp = std::chrono::steady_clock::time_point; + +// takes containers providing access to specific element through operator[] +// with elements of std::pair +// the element should provide method release() freeing resources owned by the element template class garbage_collector { diff --git a/Texture.h b/Texture.h index c9bd5c7b..da69e063 100644 --- a/Texture.h +++ b/Texture.h @@ -71,7 +71,6 @@ private: }; typedef int texture_handle; -int const null_handle = 0; class texture_manager { @@ -104,7 +103,7 @@ private: // types: typedef std::pair< opengl_texture *, - std::chrono::steady_clock::time_point > texturetimepoint_pair; + resource_timestamp > texturetimepoint_pair; typedef std::vector< texturetimepoint_pair > texturetimepointpair_sequence; diff --git a/Train.cpp b/Train.cpp index 02d9c241..81ceec1d 100644 --- a/Train.cpp +++ b/Train.cpp @@ -4490,6 +4490,7 @@ bool TTrain::Update( double const Deltatime ) } else btLampkaCzuwaka.Turn( false ); + btLampkaSHP.Turn(TestFlag(mvOccupied->SecuritySystem.Status, s_active)); } else // wylaczone @@ -5604,50 +5605,34 @@ TTrain::update_sounds( double const Deltatime ) { if( dsbSlipAlarm ) dsbSlipAlarm->Stop(); } - +#endif // McZapkie-141102: SHP i czuwak, TODO: sygnalizacja kabinowa if (mvOccupied->SecuritySystem.Status > 0) { // hunter-091012: rozdzielenie alarmow - if (TestFlag(mvOccupied->SecuritySystem.Status, s_CAalarm) || - TestFlag(mvOccupied->SecuritySystem.Status, s_SHPalarm)) - { - if (dsbBuzzer) - { - dsbBuzzer->GetStatus(&stat); - if (!(stat & DSBSTATUS_PLAYING)) - { - play_sound( dsbBuzzer, DSBVOLUME_MAX, DSBPLAY_LOOPING ); - Console::BitsSet(1 << 14); // ustawienie bitu 16 na PoKeys - } - } - } - else - { - if (dsbBuzzer) - { - dsbBuzzer->GetStatus(&stat); - if (stat & DSBSTATUS_PLAYING) - { - dsbBuzzer->Stop(); - Console::BitsClear(1 << 14); // ustawienie bitu 16 na PoKeys - } - } - } - } - else // wylaczone - { - if (dsbBuzzer) - { - dsbBuzzer->GetStatus(&stat); - if (stat & DSBSTATUS_PLAYING) - { - dsbBuzzer->Stop(); - Console::BitsClear(1 << 14); // ustawienie bitu 16 na PoKeys - } - } - } + if( TestFlag( mvOccupied->SecuritySystem.Status, s_CAalarm ) + || TestFlag( mvOccupied->SecuritySystem.Status, s_SHPalarm ) ) { + if( false == dsbBuzzer.is_playing() ) { + dsbBuzzer.play( sound_flags::looping ); + Console::BitsSet( 1 << 14 ); // ustawienie bitu 16 na PoKeys + } + } + else { + if( true == dsbBuzzer.is_playing() ) { + dsbBuzzer.stop(); + Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys + } + } + } + else { + // wylaczone + if( true == dsbBuzzer.is_playing() ) { + dsbBuzzer.stop(); + Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys + } + } +#ifdef EU07_USE_OLD_SOUNDCODE /* if ((mvControlled->Mains) && (mvControlled->EngineType==ElectricSeriesMotor)) { @@ -5755,7 +5740,6 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) if (token == "internaldata:") { - do { token = ""; @@ -5964,7 +5948,7 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) rsFadeSound.FM = 1.0; rsFadeSound.FA = 1.0; */ - rsFadeSound.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency ); + rsFadeSound.deserialize( parser, sound_type::single ); } else if (token == "localbrakesound:") { diff --git a/Train.h b/Train.h index 693ad396..fd1bd4fb 100644 --- a/Train.h +++ b/Train.h @@ -453,7 +453,7 @@ private: float fCzuwakTestTimer; // hunter-091012: do testu czuwaka float fLightsTimer; // yB 150617: timer do swiatel - int CAflag; // hunter-131211: dla osobnego zbijania CA i SHP + bool CAflag { false }; // hunter-131211: dla osobnego zbijania CA i SHP double fPoslizgTimer; TTrack *tor; diff --git a/World.cpp b/World.cpp index 45470e8c..4c003b34 100644 --- a/World.cpp +++ b/World.cpp @@ -77,7 +77,7 @@ simulation_time::init() { m_time.wSecond = 0; } - m_yearday = yearday( m_time.wDay, m_time.wMonth, m_time.wYear ); + m_yearday = year_day( m_time.wDay, m_time.wMonth, m_time.wYear ); } void @@ -125,16 +125,15 @@ simulation_time::update( double const Deltatime ) { } int -simulation_time::yearday( int Day, const int Month, const int Year ) { +simulation_time::year_day( int Day, const int Month, const int Year ) const { - char daytab[ 2 ][ 13 ] = { + char const daytab[ 2 ][ 13 ] = { { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } }; - int i, leap; - leap = ( Year % 4 == 0 ) && ( Year % 100 != 0 ) || ( Year % 400 == 0 ); - for( i = 1; i < Month; ++i ) + int leap { ( Year % 4 == 0 ) && ( Year % 100 != 0 ) || ( Year % 400 == 0 ) }; + for( int i = 1; i < Month; ++i ) Day += daytab[ leap ][ i ]; return Day; @@ -215,7 +214,6 @@ void TWorld::TrainDelete(TDynamicObject *d) Train = NULL; Controlled = NULL; // tego też już nie ma mvControlled = NULL; - Global::pUserDynamic = NULL; // tego też nie ma }; bool TWorld::Init( GLFWwindow *Window ) { @@ -267,7 +265,6 @@ bool TWorld::Init( GLFWwindow *Window ) { { Controlled = Train->Dynamic(); mvControlled = Controlled->ControlledFind()->MoverParameters; - Global::pUserDynamic = Controlled; // renerowanie pojazdu względem kabiny WriteLog("Player train init OK"); glfwSetWindowTitle( window, ( Global::AppName + " (" + Controlled->MoverParameters->Name + " @ " + Global::SceneryFile + ")" ).c_str() ); @@ -771,7 +768,6 @@ void TWorld::InOutKey( bool const Near ) FreeFlyModeFlag = !FreeFlyModeFlag; // zmiana widoku if (FreeFlyModeFlag) { // jeżeli poza kabiną, przestawiamy w jej okolicę - OK - Global::pUserDynamic = NULL; // bez renderowania względem kamery if (Train) { // cache current cab position so there's no need to set it all over again after each out-in switch Train->pMechSittingPosition = Train->pMechOffset; @@ -787,7 +783,6 @@ void TWorld::InOutKey( bool const Near ) { // jazda w kabinie if (Train) { - Global::pUserDynamic = Controlled; // renerowanie względem kamery Train->Dynamic()->bDisplayCab = true; Train->Dynamic()->ABuSetModelShake( vector3(0, 0, 0)); // zerowanie przesunięcia przed powrotem? @@ -1061,6 +1056,8 @@ bool TWorld::Update() { Timer::subsystem.sim_total.stop(); simulation::Region->update_sounds(); + audio::renderer.update( dt ); + GfxRenderer.Update( dt ); ResourceSweep(); @@ -1136,12 +1133,10 @@ TWorld::Update_Camera( double const Deltatime ) { else if( Global::shiftState ) { // patrzenie w bok przez szybę Camera.LookAt = Camera.Pos - ( lr ? -1 : 1 ) * Train->Dynamic()->VectorLeft() * Train->Dynamic()->MoverParameters->ActiveCab; - Global::SetCameraRotation( -modelrotate ); } else { // patrzenie w kierunku osi pojazdu, z uwzględnieniem kabiny - jakby z lusterka, // ale bez odbicia Camera.LookAt = Camera.Pos - Train->GetDirection() * Train->Dynamic()->MoverParameters->ActiveCab; //-1 albo 1 - Global::SetCameraRotation( M_PI - modelrotate ); // tu już trzeba uwzględnić lusterka } Camera.Roll = std::atan( Train->pMechShake.x * Train->fMechRoll ); // hustanie kamery na boki Camera.Pitch = 0.5 * std::atan( Train->vMechVelocity.z * Train->fMechPitch ); // hustanie kamery przod tyl @@ -1174,11 +1169,10 @@ TWorld::Update_Camera( double const Deltatime ) { else // patrzenie w kierunku osi pojazdu, z uwzględnieniem kabiny Camera.LookAt = Train->GetWorldMechPosition() + Train->GetDirection() * 5.0 * Train->Dynamic()->MoverParameters->ActiveCab; //-1 albo 1 Camera.vUp = Train->GetUp(); - Global::SetCameraRotation( Camera.Yaw - modelrotate ); // tu już trzeba uwzględnić lusterka } } - else { // kamera nieruchoma - Global::SetCameraRotation( Camera.Yaw - M_PI ); + else { + // kamera nieruchoma } // all done, update camera position to the new value Global::pCameraPosition = Camera.Pos; @@ -2118,7 +2112,6 @@ void TWorld::ChangeDynamic() { Train->Dynamic()->asBaseDir + Train->Dynamic()->MoverParameters->TypeName + ".mmd" ); if( !FreeFlyModeFlag ) { - Global::pUserDynamic = Controlled; // renerowanie względem kamery Train->Dynamic()->bDisplayCab = true; Train->Dynamic()->ABuSetModelShake( vector3( 0, 0, 0 ) ); // zerowanie przesunięcia przed powrotem? diff --git a/World.h b/World.h index 9acc2581..09c9dae9 100644 --- a/World.h +++ b/World.h @@ -39,13 +39,13 @@ public: second() const { return ( m_time.wMilliseconds * 0.001 + m_time.wSecond ); } int year_day() const { return m_yearday; } + // helper, calculates day of year from given date + int + year_day( int Day, int const Month, int const Year ) const; int julian_day() const; private: - // calculates day of year from given date - int - yearday( int Day, int const Month, int const Year ); // calculates day and month from given day of year void daymonth( WORD &Day, WORD &Month, WORD const Year, WORD const Yearday ); @@ -115,7 +115,6 @@ TWorld(); // calculates current season of the year based on set simulation date void compute_season( int const Yearday ) const; - private: void Update_Environment(); void Update_Camera( const double Deltatime ); diff --git a/audio.cpp b/audio.cpp index ecb69a1f..673b64f1 100644 --- a/audio.cpp +++ b/audio.cpp @@ -98,7 +98,7 @@ buffer_manager::~buffer_manager() { } // creates buffer object out of data stored in specified file. returns: handle to the buffer or null_handle if creation failed -buffer_handle +audio::buffer_handle buffer_manager::create( std::string const &Filename ) { auto filename { ToLower( Filename ) }; @@ -114,14 +114,18 @@ buffer_manager::create( std::string const &Filename ) { std::begin( filename ), std::end( filename ), '\\', '/' ); - // try dynamic-specific sounds first - auto lookup { find_buffer( Global::asCurrentDynamicPath + filename ) }; - if( lookup != null_handle ) { - return lookup; - } - std::string filelookup { find_file( Global::asCurrentDynamicPath + filename ) }; - if( false == filelookup.empty() ) { - return emplace( filelookup ); + audio::buffer_handle lookup { null_handle }; + std::string filelookup; + if( false == Global::asCurrentDynamicPath.empty() ) { + // try dynamic-specific sounds first + lookup = find_buffer( Global::asCurrentDynamicPath + filename ); + if( lookup != null_handle ) { + return lookup; + } + filelookup = find_file( Global::asCurrentDynamicPath + filename ); + if( false == filelookup.empty() ) { + return emplace( filelookup ); + } } // if dynamic-specific lookup finds nothing, try the default sound folder lookup = find_buffer( szSoundPath + filename ); @@ -136,8 +140,15 @@ buffer_manager::create( std::string const &Filename ) { return null_handle; } +// provides direct access to a specified buffer +audio::openal_buffer const & +buffer_manager::buffer( audio::buffer_handle const Buffer ) const { + + return m_buffers[ Buffer ]; +} + // places in the bank a buffer containing data stored in specified file. returns: handle to the buffer -buffer_handle +audio::buffer_handle buffer_manager::emplace( std::string Filename ) { buffer_handle const handle { m_buffers.size() }; @@ -151,7 +162,7 @@ buffer_manager::emplace( std::string Filename ) { return handle; } -buffer_handle +audio::buffer_handle buffer_manager::find_buffer( std::string const &Buffername ) const { auto lookup = m_buffermappings.find( Buffername ); diff --git a/audio.h b/audio.h index fd944367..a907a41d 100644 --- a/audio.h +++ b/audio.h @@ -16,6 +16,7 @@ namespace audio { ALuint const null_resource { ~ALuint{ 0 } }; +// wrapper for audio sample struct openal_buffer { // members ALuint id { null_resource }; // associated AL resource @@ -36,6 +37,8 @@ private: using buffer_handle = std::size_t; + +// class buffer_manager { public: @@ -47,6 +50,9 @@ public: // creates buffer object out of data stored in specified file. returns: handle to the buffer or null_handle if creation failed buffer_handle create( std::string const &Filename ); + // provides direct access to a specified buffer + audio::openal_buffer const & + buffer( audio::buffer_handle const Buffer ) const; private: // types diff --git a/audiorenderer.cpp b/audiorenderer.cpp index aa06766b..2a1df7a7 100644 --- a/audiorenderer.cpp +++ b/audiorenderer.cpp @@ -10,12 +10,88 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "audiorenderer.h" +#include "sound.h" #include "logs.h" namespace audio { openal_renderer renderer; +// starts playback of queued buffers +void +openal_source::play() { + + ::alSourcePlay( id ); + is_playing = true; +} + +// stops the playback +void +openal_source::stop() { + + ::alSourcei( id, AL_LOOPING, AL_FALSE ); + ::alSourceStop( id ); + // NOTE: we don't update the is_playing flag + // this way the state will change only on next update loop, + // giving the controller a chance to properly change state from cease to none + // even with multiple active sounds under control +} + +// updates state of the source +void +openal_source::update( int const Deltatime ) { + + // TODO: test whether the emitter was within range during the last tick, potentially update the counter and flag it for timeout + ::alGetSourcei( id, AL_BUFFERS_PROCESSED, &buffer_index ); + // for multipart sounds trim away processed sources until only one remains, the last one may be set to looping by the controller + ALuint bufferid; + while( ( buffer_index > 0 ) + && ( buffers.size() > 1 ) ) { + ::alSourceUnqueueBuffers( id, 1, &bufferid ); + buffers.erase( std::begin( buffers ) ); + --buffer_index; + } + int state; + ::alGetSourcei( id, AL_SOURCE_STATE, &state ); + is_playing = ( state == AL_PLAYING ); + // request instructions from the controller + controller->update( *this ); +} + +// toggles looping of the sound emitted by the source +void +openal_source::loop( bool const State ) { + + ::alSourcei( + id, + AL_LOOPING, + ( State ? + AL_TRUE : + AL_FALSE ) ); +} + +// releases bound buffers and resets state of the class variables +// NOTE: doesn't release allocated implementation-side source +void +openal_source::clear() { + + controller = nullptr; + // unqueue bound buffers: + // ensure no buffer is in use... + ::alSourcei( id, AL_LOOPING, AL_FALSE ); + ::alSourceStop( id ); + is_playing = false; + // ...prepare space for returned ids of unqueued buffers (not that we need that info)... + std::vector bufferids; + bufferids.resize( buffers.size() ); + // ...release the buffers and update source data to match + ::alSourceUnqueueBuffers( id, bufferids.size(), bufferids.data() ); + buffers.clear(); + buffer_index = 0; +} + + + openal_renderer::~openal_renderer() { ::alcMakeContextCurrent( nullptr ); @@ -24,12 +100,19 @@ openal_renderer::~openal_renderer() { if( m_device != nullptr ) { ::alcCloseDevice( m_device ); } } -buffer_handle +audio::buffer_handle openal_renderer::fetch_buffer( std::string const &Filename ) { return m_buffers.create( Filename ); } +// provides direct access to a specified buffer +audio::openal_buffer const & +openal_renderer::buffer( audio::buffer_handle const Buffer ) const { + + return m_buffers.buffer( Buffer ); +} + // initializes the service bool openal_renderer::init() { @@ -42,11 +125,56 @@ openal_renderer::init() { // basic initialization failed return false; } - + // all done m_ready = true; return true; } +// schedules playback of specified sample, under control of the specified emitter +void +openal_renderer::insert( sound_source *Controller, audio::buffer_handle const Sound ) { + + audio::openal_source::buffer_sequence buffers { Sound }; + return + insert( + Controller, + std::begin( buffers ), std::end( buffers ) ); +} + +// updates state of all active emitters +void +openal_renderer::update( int const Deltatime ) { + + auto source { std::begin( m_sources ) }; + while( source != std::end( m_sources ) ) { + // update each source + source->update( Deltatime ); + // if after the update the source isn't playing, put it away on the spare stack, it's done + if( false == source->is_playing ) { + source->clear(); + m_sourcespares.push( *source ); + source = m_sources.erase( source ); + } + else { + // otherwise proceed through the list normally + ++source; + } + } +} + +// returns an instance of implementation-side part of the sound emitter +audio::openal_source +openal_renderer::fetch_source() { + + audio::openal_source soundsource; + if( false == m_sourcespares.empty() ) { + // reuse (a copy of) already allocated source + soundsource = m_sourcespares.top(); + m_sourcespares.pop(); + } + return soundsource; +} + bool openal_renderer::init_caps() { diff --git a/audiorenderer.h b/audiorenderer.h index 8e52e016..ddc8ed57 100644 --- a/audiorenderer.h +++ b/audiorenderer.h @@ -10,9 +10,60 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "audio.h" +#include "resourcemanager.h" + +class sound_source; namespace audio { +// implementation part of the sound emitter +// TODO: generic interface base, for implementations other than openAL +struct openal_source { + +// types + using buffer_sequence = std::vector; + +// members + ALuint id { audio::null_resource }; // associated AL resource + sound_source *controller { nullptr }; // source controller + buffer_sequence buffers; // sequence of samples the source will emit + int buffer_index; // currently queued sample from the buffer sequence + bool is_playing { false }; + +// methods + template + openal_source & + bind( sound_source *Controller, Iterator_ First, Iterator_ Last ) { + controller = Controller; + buffers.insert( std::end( buffers ), First, Last ); + if( id == audio::null_resource ) { + ::alGenSources( 1, &id ); } + // look up and queue assigned buffers + std::vector bufferids; + for( auto const buffer : buffers ) { + bufferids.emplace_back( audio::renderer.buffer( buffer ).id ); } + ::alSourceQueueBuffers( id, bufferids.size(), bufferids.data() ); + return *this; } + // starts playback of queued buffers + void + play(); + // stops the playback + void + stop(); + // updates state of the source + void + update( int const Deltatime ); + // toggles looping of the sound emitted by the source + void + loop( bool const State ); + // releases bound buffers and resets state of the class variables + // NOTE: doesn't release allocated implementation-side source + void + clear(); +}; + + + class openal_renderer { public: @@ -21,23 +72,46 @@ public: // methods // buffer methods // returns handle to a buffer containing audio data from specified file - buffer_handle + audio::buffer_handle fetch_buffer( std::string const &Filename ); + // provides direct access to a specified buffer + audio::openal_buffer const & + buffer( audio::buffer_handle const Buffer ) const; // core methods // initializes the service bool init(); + // schedules playback of provided range of samples, under control of the specified sound emitter + template + void + insert( sound_source *Controller, Iterator_ First, Iterator_ Last ) { + m_sources.emplace_back( fetch_source().bind( Controller, First, Last ) ); } + // schedules playback of specified sample, under control of the specified sound emitter + void + insert( sound_source *Controller, audio::buffer_handle const Sound ); + // updates state of all active emitters + void + update( int const Deltatime ); private: +// types + using source_list = std::list; + using source_sequence = std::stack; // methods bool init_caps(); + // returns an instance of implementation-side part of the sound emitter + audio::openal_source + fetch_source(); // members ALCdevice * m_device { nullptr }; ALCcontext * m_context { nullptr }; bool m_ready { false }; // renderer is initialized and functional buffer_manager m_buffers; + // TBD: list of sources as vector, sorted by distance, for openal implementations with limited number of active sources? + source_list m_sources; + source_sequence m_sourcespares; // already created and currently unused sound sources }; extern openal_renderer renderer; diff --git a/maszyna.vcxproj.filters b/maszyna.vcxproj.filters index 79bade59..7652f761 100644 --- a/maszyna.vcxproj.filters +++ b/maszyna.vcxproj.filters @@ -225,15 +225,15 @@ Source Files + + Source Files + Source Files Source Files - - Source Files - @@ -449,13 +449,13 @@ Header Files - + Header Files Header Files - + Header Files diff --git a/openglgeometrybank.cpp b/openglgeometrybank.cpp index 2e106500..dc8989a3 100644 --- a/openglgeometrybank.cpp +++ b/openglgeometrybank.cpp @@ -395,7 +395,7 @@ geometrybank_manager::create_bank() { if( true == Global::bUseVBO ) { m_geometrybanks.emplace_back( std::make_shared(), std::chrono::steady_clock::time_point() ); } else { m_geometrybanks.emplace_back( std::make_shared(), std::chrono::steady_clock::time_point() ); } // NOTE: handle is effectively (index into chunk array + 1) this leaves value of 0 to serve as error/empty handle indication - return { m_geometrybanks.size(), 0 }; + return { static_cast( m_geometrybanks.size() ), 0 }; } // creates a new geometry chunk of specified type from supplied vertex data, in specified bank. returns: handle to the chunk or NULL diff --git a/openglgeometrybank.h b/openglgeometrybank.h index 00b9cd8a..c2bf6ac2 100644 --- a/openglgeometrybank.h +++ b/openglgeometrybank.h @@ -302,7 +302,7 @@ private: // types: typedef std::pair< std::shared_ptr, - std::chrono::steady_clock::time_point > geometrybanktimepoint_pair; + resource_timestamp > geometrybanktimepoint_pair; typedef std::deque< geometrybanktimepoint_pair > geometrybanktimepointpair_sequence; diff --git a/renderer.cpp b/renderer.cpp index e3254003..80cf167a 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -696,9 +696,10 @@ opengl_renderer::setup_pass( renderpass_config &Config, rendermode const Mode, f point = lightviewmatrix * point; } bounding_box( frustumchunkmin, frustumchunkmax, std::begin( frustumchunkshapepoints ), std::end( frustumchunkshapepoints ) ); - // quantize the frustum points with 50 m resolution, to reduce shadow shimmer on scale/orientation changes - frustumchunkmin = 50.f * glm::floor( frustumchunkmin * ( 1.f / 50.f ) ); - frustumchunkmax = 50.f * glm::ceil( frustumchunkmax * ( 1.f / 50.f ) ); + // quantize the frustum points and add some padding, to reduce shadow shimmer on scale changes + auto const quantizationstep{ std::min( Global::shadowtune.depth, 50.f ) }; + frustumchunkmin = quantizationstep * glm::floor( frustumchunkmin * ( 1.f / quantizationstep ) ); + frustumchunkmax = quantizationstep * glm::ceil( frustumchunkmax * ( 1.f / quantizationstep ) ); // ...use the dimensions to set up light projection boundaries... // NOTE: since we only have one cascade map stage, we extend the chunk forward/back to catch areas normally covered by other stages camera.projection() *= diff --git a/renderer.h b/renderer.h index e8613eb6..e8582afc 100644 --- a/renderer.h +++ b/renderer.h @@ -21,7 +21,7 @@ http://mozilla.org/MPL/2.0/. #define EU07_USE_PICKING_FRAMEBUFFER //#define EU07_USE_DEBUG_SHADOWMAP -#define EU07_USE_DEBUG_CAMERA +//#define EU07_USE_DEBUG_CAMERA //#define EU07_USE_DEBUG_CULLING struct opengl_light { diff --git a/simulation.cpp b/simulation.cpp index cd176f5f..6ef2c32b 100644 --- a/simulation.cpp +++ b/simulation.cpp @@ -460,10 +460,7 @@ state_manager::deserialize_node( cParser &Input, scene::scratch_data &Scratchpad auto *sound { deserialize_sound( Input, Scratchpad, nodedata ) }; if( false == simulation::Sounds.insert( sound ) ) { -#ifdef EU07_USE_OLD_SOUNDCODE - ErrorLog( "Bad scenario: sound node with duplicate name \"" + sound->m_name + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); -#else -#endif + ErrorLog( "Bad scenario: sound node with duplicate name \"" + sound->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); } simulation::Region->insert_sound( sound, Scratchpad ); } diff --git a/sound.cpp b/sound.cpp index 519b51e1..d17912f5 100644 --- a/sound.cpp +++ b/sound.cpp @@ -10,6 +10,8 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "sound.h" +#include "parser.h" +#include "globals.h" // restores state of the class from provided data stream sound_source & @@ -25,12 +27,14 @@ sound_source::deserialize( cParser &Input, sound_type const Legacytype, int cons switch( Legacytype ) { case sound_type::single: { // single sample only - Input.getTokens( 1, true, "\n\r\t ,;" ); + m_soundmain = audio::renderer.fetch_buffer( Input.getToken( true, "\n\r\t ,;" ) ); break; } case sound_type::multipart: { // three samples: start, middle, stop - Input.getTokens( 3, true, "\n\r\t ,;" ); + m_soundbegin = audio::renderer.fetch_buffer( Input.getToken( true, "\n\r\t ,;" ) ); + m_soundmain = audio::renderer.fetch_buffer( Input.getToken( true, "\n\r\t ,;" ) ); + m_soundend = audio::renderer.fetch_buffer( Input.getToken( true, "\n\r\t ,;" ) ); break; } default: { @@ -53,14 +57,142 @@ sound_source::deserialize( cParser &Input, sound_type const Legacytype, int cons // issues contextual play commands for the audio renderer void -sound_source::play() { +sound_source::play( int const Flags ) { + if( ( false == Global::bSoundEnabled ) + || ( true == empty() ) ) { + // if the sound is disabled altogether or nothing can be emitted from this source, no point wasting time + return; + } + + m_flags = Flags; + + switch( m_stage ) { + case stage::none: + case stage::restart: { + if( m_soundbegin != null_handle ) { + std::vector bufferlist{ m_soundbegin, m_soundmain }; + audio::renderer.insert( this, std::begin( bufferlist ), std::end( bufferlist ) ); + } + else { + audio::renderer.insert( this, m_soundmain ); + } + break; + } + case stage::main: { + // TODO: schedule another main sample playback, or a suitable sample from the table for combined sources + break; + } + case stage::end: { + // schedule stop of current sequence end sample... + m_stage = stage::restart; + // ... and queue startup or main sound again + return play( Flags ); + break; + } + default: { + break; + } + } +} + +// stops currently active play commands controlled by this emitter +void +sound_source::stop() { + + if( ( m_stage == stage::none ) + || ( m_stage == stage::end ) ) { + return; + } + + switch( m_stage ) { + case stage::begin: + case stage::main: { + // set the source to kill any currently active sounds + m_stage = stage::cease; + if( m_soundend != null_handle ) { + // and if there's defined sample for the sound end, play it instead + audio::renderer.insert( this, m_soundend ); + } + break; + } + case stage::end: { + // set the source to kill any currently active sounds + m_stage = stage::cease; + break; + } + default: { + break; + } + } +} + +// adjusts parameters of provided implementation-side sound source +void +sound_source::update( audio::openal_source &Source ) { + + if( true == Source.is_playing ) { + + // kill the sound if requested + if( m_stage == stage::cease ) { + Source.stop(); + return; + } + // TODO: positional update + if( m_soundbegin != null_handle ) { + // multipart sound + // detect the moment when the sound moves from startup sample to the main + if( ( m_stage == stage::begin ) + && ( Source.buffers[ Source.buffer_index ] == m_soundmain ) ) { + // when it happens update active sample flags, and activate the looping + Source.loop( true ); + m_stage = stage::main; + } + } + } + else { + // if the emitter isn't playing it's either done or wasn't yet started + // we can determine this from number of processed buffers + if( Source.buffer_index != Source.buffers.size() ) { + auto const buffer { Source.buffers[ Source.buffer_index ] }; + if( buffer == m_soundbegin ) { m_stage = stage::begin; } + // TODO: take ito accound sample table for combined sounds + else if( buffer == m_soundmain ) { m_stage = stage::main; } + else if( buffer == m_soundend ) { m_stage = stage::end; } + // TODO: emitter initialization + if( ( buffer == m_soundmain ) + && ( true == TestFlag( m_flags, sound_flags::looping ) ) ) { + // main sample can be optionally set to loop + Source.loop( true ); + } + // all set, start playback + Source.play(); + } + else { + // + auto const buffer { Source.buffers[ Source.buffer_index - 1 ] }; + if( ( buffer == m_soundend ) + || ( ( m_soundend == null_handle ) + && ( buffer == m_soundmain ) ) ) { + m_stage = stage::none; + } + } + } } bool sound_source::empty() const { - return true; + // NOTE: we test only the main sound, won't bother playing potential bookends if this is missing + // TODO: take into account presence of sample table, for combined sounds + return ( m_soundmain == null_handle ); +} + +// returns true if the source is emitting any sound +bool +sound_source::is_playing() const { + + return ( m_stage != stage::none ); } //--------------------------------------------------------------------------- diff --git a/sound.h b/sound.h index 877c5491..7f9ffb29 100644 --- a/sound.h +++ b/sound.h @@ -10,7 +10,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "audiorenderer.h" -#include "parser.h" +#include "classes.h" #include "names.h" enum sound_type { @@ -19,14 +19,18 @@ enum sound_type { }; enum sound_parameters { - none, - range = 0x1, + range = 0x1, amplitude = 0x2, frequency = 0x4 }; +enum sound_flags { + looping = 0x1, // the main sample will be looping; implied for multi-sounds + exclusive = 0x2 // the source won't dispatch more than one active instance of the sound; implied for multi-sounds +}; + // mini controller and audio dispatcher; issues play commands for the audio renderer, -// updates parameters of assigned audio sources for the playback duration +// updates parameters of created audio emitters for the playback duration // TODO: move to simulation namespace after clean up of owner classes class sound_source { @@ -34,15 +38,18 @@ public: // methods // restores state of the class from provided data stream sound_source & - deserialize( cParser &Input, sound_type const Legacytype, int const Legacyparameters = sound_parameters::none ); + deserialize( cParser &Input, sound_type const Legacytype, int const Legacyparameters = NULL ); sound_source & - deserialize( std::string const &Input, sound_type const Legacytype, int const Legacyparameters = sound_parameters::none ); + deserialize( std::string const &Input, sound_type const Legacytype, int const Legacyparameters = NULL ); // issues contextual play commands for the audio renderer void - play(); - // sets volume of audio streams associated with this source - sound_source & - volume( float const Volume ); + play( int const Flags = NULL ); + // stops currently active play commands controlled by this emitter + void + stop(); + // adjusts parameters of provided implementation-side sound source + void + update( audio::openal_source &Source ); // sound source name setter/getter void name( std::string Name ); @@ -51,10 +58,31 @@ public: // returns true if there isn't any sound buffer associated with the object, false otherwise bool empty() const; + // returns true if the source is emitting any sound + bool + is_playing() const; private: +// types + enum stage { + none, + begin, + main, + end, + cease, + restart + }; + // members + TDynamicObject * m_owner { nullptr }; // optional, the vehicle carrying this sound source + glm::vec3 m_offset; // relative position of the source, either from the owner or the region centre std::string m_name; + stage m_stage{ stage::none }; + int m_flags{ NULL }; + audio::buffer_handle m_soundmain { null_handle }; // main sound emitted by the source + audio::buffer_handle m_soundbegin { null_handle }; // optional, sound emitted before the main sound + audio::buffer_handle m_soundend { null_handle }; // optional, sound emitted after the main sound + // TODO: table of samples with associated values, activated when controlling variable matches the value }; // sound source name setter/getter diff --git a/sun.cpp b/sun.cpp index 9f4ac2b5..9e744635 100644 --- a/sun.cpp +++ b/sun.cpp @@ -14,10 +14,6 @@ cSun::cSun() { setLocation( 19.00f, 52.00f ); // default location roughly in centre of Poland m_observer.press = 1013.0; // surface pressure, millibars m_observer.temp = 15.0; // ambient dry-bulb temperature, degrees C - - TIME_ZONE_INFORMATION timezoneinfo; // TODO: timezone dependant on geographic location - ::GetTimeZoneInformation( &timezoneinfo ); - m_observer.timezone = -timezoneinfo.Bias / 60.0f; } cSun::~cSun() { gluDeleteQuadric( sunsphere ); } @@ -27,6 +23,27 @@ cSun::init() { sunsphere = gluNewQuadric(); gluQuadricNormals( sunsphere, GLU_SMOOTH ); + + // calculate and set timezone for the current date of the simulation + // TODO: timezone dependant on geographic location + TIME_ZONE_INFORMATION timezoneinfo; + ::GetTimeZoneInformation( &timezoneinfo ); + // account for potential daylight/normal time bias + // NOTE: we're using xp-compatible time zone information and current year, instead of 'historically proper' values + auto zonebias { timezoneinfo.Bias }; + auto const year { simulation::Time.data().wYear }; + auto const yearday { simulation::Time.year_day() }; + if( yearday < simulation::Time.year_day( timezoneinfo.DaylightDate.wDay, timezoneinfo.DaylightDate.wMonth, year ) ) { + zonebias += timezoneinfo.StandardBias; + } + else if( yearday < simulation::Time.year_day( timezoneinfo.StandardDate.wDay, timezoneinfo.StandardDate.wMonth, year ) ) { + zonebias += timezoneinfo.DaylightBias; + } + else { + zonebias += timezoneinfo.StandardBias; + } + + m_observer.timezone = -zonebias / 60.0f; } void