From 0d0e299a2916d6980ed14dc0025afecda8e4dd86 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Thu, 16 Jul 2020 21:32:51 +0200 Subject: [PATCH] engine-powered compressor sound pitch adjustment, model node angles, global settings export and diagnostics, departure permit sound enhancement, radio channel selection ai logic enhancement, door permission ai logic fix --- AnimModel.cpp | 24 ++++- AnimModel.h | 2 + Driver.cpp | 95 +++++++++-------- Driver.h | 2 + DynObj.cpp | 17 ++-- Globals.cpp | 247 ++++++++++++++++++++++++++++++++++++++++++++- Globals.h | 63 ++++++++---- McZapkie/MOVER.h | 3 + McZapkie/Mover.cpp | 56 ++++++---- application.cpp | 9 ++ driveruipanels.cpp | 3 +- mtable.cpp | 22 +++- mtable.h | 3 + particles.cpp | 7 +- sound.h | 1 + 15 files changed, 452 insertions(+), 102 deletions(-) diff --git a/AnimModel.cpp b/AnimModel.cpp index f18fdc7b3..f7dc4752c 100644 --- a/AnimModel.cpp +++ b/AnimModel.cpp @@ -432,6 +432,15 @@ bool TAnimModel::Init(std::string const &asName, std::string const &asReplacable return ( pModel != nullptr ); } +bool +TAnimModel::is_keyword( std::string const &Token ) const { + + return ( Token == "endmodel" ) + || ( Token == "lights" ) + || ( Token == "lightcolors" ) + || ( Token == "angles" ); +} + bool TAnimModel::Load(cParser *parser, bool ter) { // rozpoznanie wpisu modelu i ustawienie świateł std::string name = parser->getToken(); @@ -485,8 +494,7 @@ bool TAnimModel::Load(cParser *parser, bool ter) if( token == "lights" ) { auto i{ 0 }; while( ( false == ( token = parser->getToken() ).empty() ) - && ( token != "lightcolors" ) - && ( token != "endmodel" ) ) { + && ( false == is_keyword( token ) ) ) { if( i < iNumLights ) { // stan światła jest liczbą z ułamkiem @@ -499,8 +507,7 @@ bool TAnimModel::Load(cParser *parser, bool ter) if( token == "lightcolors" ) { auto i{ 0 }; while( ( false == ( token = parser->getToken() ).empty() ) - && ( token != "lights" ) - && ( token != "endmodel" ) ) { + && ( false == is_keyword( token ) ) ) { if( ( i < iNumLights ) && ( token != "-1" ) ) { // -1 leaves the default color intact @@ -513,6 +520,15 @@ bool TAnimModel::Load(cParser *parser, bool ter) ++i; } } + + if( token == "angles" ) { + parser->getTokens( 3 ); + *parser + >> vAngle[ 0 ] + >> vAngle[ 1 ] + >> vAngle[ 2 ]; + } + } while( ( false == token.empty() ) && ( token != "endmodel" ) ); diff --git a/AnimModel.h b/AnimModel.h index 12dd5c2fb..feffdda40 100644 --- a/AnimModel.h +++ b/AnimModel.h @@ -177,6 +177,8 @@ class TAnimModel : public scene::basic_node { 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; + // checks whether provided token is a legacy (text) format keyword + bool is_keyword( std::string const &Token ) const; // members TAnimContainer *pRoot { nullptr }; // pojemniki sterujące, tylko dla aniomowanych submodeli diff --git a/Driver.cpp b/Driver.cpp index 767ac916c..186ec9fb5 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -898,6 +898,21 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN } else if (iDrivigFlags & moveStopPoint) // jeśli pomijanie W4, to nie sprawdza czasu odjazdu { // tylko gdy nazwa zatrzymania się zgadza + if( ( OrderCurrentGet() & ( Obey_train | Bank ) ) != 0 ) { + // check whether the station specifies radio channel change + // NOTE: we don't do it in shunt mode, as shunting operations tend to use dedicated radio channel + // NOTE: there's a risk radio channel change was specified by a station which we skipped during timetable rewind + // we ignore this for the time being as it's not a high priority error + auto const radiochannel { TrainParams.radio_channel() }; + if( radiochannel > 0 ) { + if( iGuardRadio != 0 ) { + iGuardRadio = radiochannel; + } + if( AIControllFlag ) { + iRadioChannel = radiochannel; + } + } + } IsScheduledPassengerStopVisible = true; // block potential timetable rewind if the next stop shows up later in the scan if (false == TrainParams.IsStop()) { // jeśli nie ma tu postoju @@ -3217,8 +3232,8 @@ bool TController::IncSpeed() return false; } bool OK = true; - if( ( doors_open() ) - && ( VelDesired > 0.0 ) ) { // to prevent door shuffle on stop + if( ( VelDesired > 0.0 ) // to prevent door shuffle on stop + && ( doors_open() || doors_permit_active() ) ) { // zamykanie drzwi - tutaj wykonuje tylko AI (zmienia fActionTime) Doors( false ); } @@ -4091,14 +4106,14 @@ void TController::Doors( bool const Open, int const Side ) { } else { // zamykanie - if( ( false == pVehicle->MoverParameters->Doors.permit_needed ) + if( ( false == doors_permit_active() ) && ( false == doors_open() ) ) { // the doors are already closed and we don't have to revoke control permit, we can skip all hard work iDrivigFlags &= ~moveDoorOpened; return; } - if( AIControllFlag ) { + if( ( AIControllFlag ) && ( doors_open() ) ) { if( ( true == mvOccupied->Doors.has_warning ) && ( false == mvOccupied->DepartureSignal ) && ( true == TestFlag( iDrivigFlags, moveDoorOpened ) ) ) { @@ -4107,20 +4122,22 @@ void TController::Doors( bool const Open, int const Side ) { } } - if( ( true == doors_open() ) - && ( ( fActionTime > -0.5 ) - || ( false == AIControllFlag ) ) ) { + if( ( false == AIControllFlag ) || ( fActionTime > -0.5 ) ) { // ai doesn't close the door until it's free to depart, but human driver has free reign to do stupid things - if( ( pVehicle->MoverParameters->Doors.close_control == control_t::conductor ) - || ( ( true == AIControllFlag ) ) ) { - // if the door are controlled by the driver, we let the user operate them unless this user is an ai - // the train conductor, if present, handles door operation also for human-driven trains - if( ( pVehicle->MoverParameters->Doors.close_control == control_t::driver ) - || ( pVehicle->MoverParameters->Doors.close_control == control_t::mixed ) ) { - pVehicle->MoverParameters->OperateDoors( side::right, false ); - pVehicle->MoverParameters->OperateDoors( side::left, false ); - } - if( pVehicle->MoverParameters->Doors.permit_needed ) { + if( true == doors_open() ) { + if( ( pVehicle->MoverParameters->Doors.close_control == control_t::conductor ) + || ( ( true == AIControllFlag ) ) ) { + // if the door are controlled by the driver, we let the user operate them unless this user is an ai + // the train conductor, if present, handles door operation also for human-driven trains + if( ( pVehicle->MoverParameters->Doors.close_control == control_t::driver ) + || ( pVehicle->MoverParameters->Doors.close_control == control_t::mixed ) ) { + pVehicle->MoverParameters->OperateDoors( side::right, false ); + pVehicle->MoverParameters->OperateDoors( side::left, false ); + } + } + } + if( true == doors_permit_active() ) { + if( true == AIControllFlag ) { pVehicle->MoverParameters->PermitDoors( side::right, false ); pVehicle->MoverParameters->PermitDoors( side::left, false ); } @@ -4157,19 +4174,14 @@ TController::doors_open() const { return ( IsAnyDoorOpen[ side::right ] || IsAnyDoorOpen[ side::left ] ); -/* - auto *vehicle = pVehicles[ 0 ]; // pojazd na czole składu - while( vehicle != nullptr ) { - if( ( false == vehicle->MoverParameters->Doors.instances[side::right].is_closed ) - || ( false == vehicle->MoverParameters->Doors.instances[side::left].is_closed ) ) { - // any open door is enough - return true; - } - vehicle = vehicle->Next(); - } - // if we're still here there's nothing open - return false; -*/ +} + +bool +TController::doors_permit_active() const { + + return ( + IsAnyDoorPermitActive[ side::right ] + || IsAnyDoorPermitActive[ side::left ] ); } void TController::RecognizeCommand() @@ -4275,7 +4287,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N { ".ogg", ".flac", ".wav" } ); if( false == lookup.first.empty() ) { // wczytanie dźwięku odjazdu w wersji radiowej (słychać tylko w kabinie) - tsGuardSignal = sound_source( sound_placement::internal, 2 * EU07_SOUND_CABCONTROLSCUTOFFRANGE ).deserialize( lookup.first + lookup.second, sound_type::single ); + tsGuardSignal = sound_source( sound_placement::internal, EU07_SOUND_HANDHELDRADIORANGE ).deserialize( lookup.first + lookup.second, sound_type::single ); iGuardRadio = iRadioChannel; } } @@ -4733,6 +4745,7 @@ TController::UpdateSituation(double dt) { double AbsAccS = 0; IsAnyCouplerStretched = false; IsAnyDoorOpen[ side::right ] = IsAnyDoorOpen[ side::left ] = false; + IsAnyDoorPermitActive[ side::right ] = IsAnyDoorPermitActive[ side::left ] = false; ConsistShade = 0.0; TDynamicObject *p = pVehicles[0]; // pojazd na czole składu while (p) @@ -4777,12 +4790,11 @@ TController::UpdateSituation(double dt) { || ( vehicle->Couplers[ end::rear ].stretch_duration > 0.0 ); // check door state auto const switchsides { p->DirectionGet() != iDirection }; - IsAnyDoorOpen[ side::right ] = - IsAnyDoorOpen[ side::right ] - || ( false == vehicle->Doors.instances[ ( switchsides ? side::left : side::right ) ].is_closed ); - IsAnyDoorOpen[ side::left ] = - IsAnyDoorOpen[ side::left ] - || ( false == vehicle->Doors.instances[ ( switchsides ? side::right : side::left ) ].is_closed ); + IsAnyDoorOpen[ side::right ] |= ( false == vehicle->Doors.instances[ ( switchsides ? side::left : side::right ) ].is_closed ); + IsAnyDoorOpen[ side::left ] |= ( false == vehicle->Doors.instances[ ( switchsides ? side::right : side::left ) ].is_closed ); + IsAnyDoorPermitActive[ side::right ] |= ( vehicle->Doors.permit_needed && vehicle->Doors.instances[ ( switchsides ? side::left : side::right ) ].open_permit ); + IsAnyDoorPermitActive[ side::left ] |= ( vehicle->Doors.permit_needed && vehicle->Doors.instances[ ( switchsides ? side::right : side::left ) ].open_permit ); + // measure lighting level // TBD: apply weight (multiplier) to partially lit vehicles? ConsistShade += ( p->fShade > 0.0 ? p->fShade : 1.0 ); @@ -6012,14 +6024,13 @@ TController::UpdateSituation(double dt) { fActionTime = -5.0; // niech trochę potrzyma } else { - // if (iGuardRadio==iRadioChannel) //zgodność kanału - // if (!FreeFlyModeFlag) //obserwator musi być w środku pojazdu - // (albo może mieć radio przenośne) - kierownik mógłby powtarzać przy braku reakcji - // TODO: proper system for sending/receiving radio messages - // place the sound in appropriate cab of the manned vehicle + // radio message tsGuardSignal.owner( pVehicle ); +/* tsGuardSignal.offset( { 0.f, 2.f, pVehicle->MoverParameters->Dim.L * 0.4f * ( pVehicle->MoverParameters->CabOccupied < 0 ? -1 : 1 ) } ); tsGuardSignal.play( sound_flags::exclusive ); +*/ + simulation::radio_message( &tsGuardSignal, iGuardRadio ); // NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera fActionTime = -5.0; // niech trochę potrzyma } diff --git a/Driver.h b/Driver.h index 710606c2d..be3f7afef 100644 --- a/Driver.h +++ b/Driver.h @@ -465,6 +465,7 @@ class TController { void Doors( bool const Open, int const Side = 0 ); // returns true if any vehicle in the consist has an open door bool doors_open() const; + bool doors_permit_active() const; void AutoRewident(); // ustawia hamulce w składzie void UpdatePantographs(); // members @@ -482,6 +483,7 @@ class TController { double ConsistShade{ 1.0 }; // averaged amount of sunlight received by the consist TDynamicObject *pVehicles[ 2 ]; // skrajne pojazdy w składzie (niekoniecznie bezpośrednio sterowane) bool IsAnyDoorOpen[ 2 ]; // state of door in the consist + bool IsAnyDoorPermitActive[ 2 ]; // state of door permit in the consist bool IsAnyLineBreakerOpen{ false }; // state of line breaker in all powered vehicles under control bool IsAnyConverterOverloadRelayOpen{ false }; // state of converter overload relays in all vehicles under control bool IsAnyMotorOverloadRelayOpen{ false }; // state of motor overload relays in all vehicles under control diff --git a/DynObj.cpp b/DynObj.cpp index ea1a669e9..2f239db06 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4096,6 +4096,15 @@ void TDynamicObject::RenderSounds() { if( MoverParameters->CompressorSpeed > 0.0 ) { // McZapkie! - dzwiek compressor.wav tylko gdy dziala sprezarka if( MoverParameters->CompressorFlag ) { + if( MoverParameters->CompressorPower == 3 ) { + // presume the compressor sound is recorded for idle revolutions + // increase the pitch according to increase of engine revolutions + auto const enginefactor { ( MoverParameters->EngineMaxRPM() / MoverParameters->EngineIdleRPM() ) * MoverParameters->EngineRPMRatio() }; + sCompressor.pitch( + clamp( // try to keep the sound pitch in semi-reasonable range + enginefactor, + 0.5, 2.5 ) ); + } sCompressor.play( sound_flags::exclusive | sound_flags::looping ); } else { @@ -7269,7 +7278,7 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub engine.m_amplitudeoffset + engine.m_amplitudefactor * ( 0.25 * ( Vehicle.EnginePower / Vehicle.Power ) - + 0.75 * ( Vehicle.enrot * 60 ) / ( Vehicle.DElist[ Vehicle.MainCtrlPosNo ].RPM ) ); + + 0.75 * Vehicle.EngineRPMRatio() ); break; } case TEngineType::DieselEngine: { @@ -7299,11 +7308,7 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub // calculate potential recent increase of engine revolutions auto const revolutionsperminute { Vehicle.enrot * 60 }; auto const revolutionsdifference { revolutionsperminute - engine_revs_last }; - auto const idlerevolutionsthreshold { 1.01 * ( - Vehicle.EngineType == TEngineType::DieselElectric ? - Vehicle.DElist[ 0 ].RPM : - Vehicle.dizel_nmin * 60 ) }; - + auto const idlerevolutionsthreshold { 1.01 * Vehicle.EngineIdleRPM() }; engine_revs_change = std::max( 0.0, engine_revs_change - 2.5 * Deltatime ); if( ( revolutionsperminute > idlerevolutionsthreshold ) && ( revolutionsdifference > 1.0 * Deltatime ) ) { diff --git a/Globals.cpp b/Globals.cpp index 0bad551f3..3ba864ecb 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -656,10 +656,11 @@ global_settings::ConfigParse(cParser &Parser) { Parser >> token; auto const priority { token }; PythonScreenUpdateRate = ( + priority == "normal" ? 200 : priority == "lower" ? 500 : priority == "lowest" ? 1000 : priority == "off" ? 0 : - 200 ); + stol_def( priority, 200 ) ); } else if( token == "python.updatetime" ) { @@ -954,4 +955,246 @@ global_settings::ConfigParse(cParser &Parser) { #ifdef _WIN32 Console::ModeSet(iFeedbackMode, iFeedbackPort); // tryb pracy konsoli sterowniczej #endif -} \ No newline at end of file +} + +void +global_settings::export_as_text( std::ostream &Output ) const { + + export_as_text( Output, "sceneryfile", SceneryFile ); + export_as_text( Output, "humanctrlvehicle", local_start_vehicle ); + export_as_text( Output, "fieldofview", FieldOfView ); + export_as_text( Output, "width", iWindowWidth ); + export_as_text( Output, "height", iWindowHeight ); + export_as_text( Output, "heightbase", fDistanceFactor ); + export_as_text( Output, "targetfps", targetfps ); + export_as_text( Output, "basedrawrange", BaseDrawRange ); + export_as_text( Output, "fullscreen", bFullScreen ); + export_as_text( Output, "fullscreenmonitor", fullscreen_monitor ); + export_as_text( Output, "vsync", VSync ); + // NOTE: values are changed dynamically during simulation. cache initial settings and export instead + if( FreeFlyModeFlag ) { + Output + << "freefly yes " + << FreeCameraInit[ 0 ].x << " " + << FreeCameraInit[ 0 ].y << " " + << FreeCameraInit[ 0 ].z << "\n"; + } + else { + export_as_text( Output, "freefly", FreeFlyModeFlag ); + } + export_as_text( Output, "wireframe", bWireFrame ); + export_as_text( Output, "debugmode", DebugModeFlag ); + export_as_text( Output, "soundenabled", bSoundEnabled ); + export_as_text( Output, "sound.openal.renderer", AudioRenderer ); + export_as_text( Output, "sound.volume", AudioVolume ); + export_as_text( Output, "sound.volume.radio", RadioVolume ); + export_as_text( Output, "physicslog", WriteLogFlag ); + export_as_text( Output, "fullphysics", FullPhysics ); + export_as_text( Output, "debuglog", iWriteLogEnabled ); + export_as_text( Output, "multiplelogs", MultipleLogs ); + export_as_text( Output, "logs.filter", DisabledLogTypes ); + Output + << "mousescale " + << fMouseXScale << " " + << fMouseYScale << "\n"; + export_as_text( Output, "mousecontrol", InputMouse ); + export_as_text( Output, "enabletraction", bEnableTraction ); + export_as_text( Output, "loadtraction", bLoadTraction ); + export_as_text( Output, "friction", fFriction ); + export_as_text( Output, "livetraction", bLiveTraction ); + export_as_text( Output, "skyenabled", asSky ); + export_as_text( Output, "defaultext", szDefaultExt ); + export_as_text( Output, "newaircouplers", bnewAirCouplers ); + export_as_text( Output, "anisotropicfiltering", AnisotropicFiltering ); + export_as_text( Output, "usevbo", bUseVBO ); + export_as_text( Output, "feedbackmode", iFeedbackMode ); + export_as_text( Output, "feedbackport", iFeedbackPort ); + export_as_text( Output, "multiplayer", iMultiplayer ); + export_as_text( Output, "maxtexturesize", iMaxTextureSize ); + export_as_text( Output, "maxcabtexturesize", iMaxCabTextureSize ); + export_as_text( Output, "movelight", fMoveLight ); + export_as_text( Output, "dynamiclights", DynamicLightCount ); + if( std::isnormal( ScenarioTimeOverride ) ) { + export_as_text( Output, "scenario.time.override", ScenarioTimeOverride ); + } + export_as_text( Output, "scenario.time.offset", ScenarioTimeOffset ); + export_as_text( Output, "scenario.time.current", ScenarioTimeCurrent ); + export_as_text( Output, "scenario.weather.temperature", AirTemperature ); + export_as_text( Output, "scalespeculars", ScaleSpecularValues ); + export_as_text( Output, "gfxrenderer", GfxRenderer ); + export_as_text( Output, "shadows", RenderShadows ); + Output + << "shadowtune " + << shadowtune.map_size << " " + << 0 << " " + << shadowtune.range << " " + << 0 << "\n"; + export_as_text( Output, "gfx.shadows.cab.range", RenderCabShadowsRange ); + export_as_text( Output, "gfx.smoke", Smoke ); + export_as_text( Output, "gfx.smoke.fidelity", SmokeFidelity ); + export_as_text( Output, "smoothtraction", bSmoothTraction ); + export_as_text( Output, "splinefidelity", SplineFidelity ); + export_as_text( Output, "rendercab", render_cab ); + export_as_text( Output, "createswitchtrackbeds", CreateSwitchTrackbeds ); + export_as_text( Output, "gfx.resource.sweep", ResourceSweep ); + export_as_text( Output, "gfx.resource.move", ResourceMove ); + export_as_text( Output, "gfx.reflections.framerate", 1.0 / ReflectionUpdateInterval ); + export_as_text( Output, "timespeed", fTimeSpeed ); + export_as_text( Output, "multisampling", iMultisampling ); + export_as_text( Output, "latitude", fLatitudeDeg ); + export_as_text( Output, "convertmodels", iConvertModels + ( iConvertModels > 0 ? 128 : 0 ) ); + export_as_text( Output, "file.binary.terrain", file_binary_terrain ); + export_as_text( Output, "inactivepause", bInactivePause ); + export_as_text( Output, "slowmotion", iSlowMotionMask ); + export_as_text( Output, "hideconsole", bHideConsole ); + export_as_text( Output, "rollfix", bRollFix ); + export_as_text( Output, "fpsaverage", fFpsAverage ); + export_as_text( Output, "fpsdeviation", fFpsDeviation ); + for( auto idx = 0; idx < 6; ++idx ) { + Output + << "calibrate5din " + << idx << " " + << fCalibrateIn[ idx ][ 0 ] << " " + << fCalibrateIn[ idx ][ 1 ] << " " + << fCalibrateIn[ idx ][ 2 ] << " " + << fCalibrateIn[ idx ][ 3 ] << " " + << fCalibrateIn[ idx ][ 4 ] << " " + << fCalibrateIn[ idx ][ 5 ] << "\n"; + } + for( auto idx = 0; idx < 6; ++idx ) { + Output + << "calibrate5dout " + << idx << " " + << fCalibrateOut[ idx ][ 0 ] << " " + << fCalibrateOut[ idx ][ 1 ] << " " + << fCalibrateOut[ idx ][ 2 ] << " " + << fCalibrateOut[ idx ][ 3 ] << " " + << fCalibrateOut[ idx ][ 4 ] << " " + << fCalibrateOut[ idx ][ 5 ] << "\n"; + } + Output + << "calibrateoutmaxvalues " + << fCalibrateOutMax[ 0 ] << " " + << fCalibrateOutMax[ 1 ] << " " + << fCalibrateOutMax[ 2 ] << " " + << fCalibrateOutMax[ 3 ] << " " + << fCalibrateOutMax[ 4 ] << " " + << fCalibrateOutMax[ 5 ] << " " + << fCalibrateOutMax[ 6 ] << "\n"; + export_as_text( Output, "calibrateoutdebuginfo", iCalibrateOutDebugInfo ); + for( auto idx = 0; idx < 7; ++idx ) { + Output + << "pwm " + << idx << " " + << iPoKeysPWM[ idx ] << "\n"; + } + export_as_text( Output, "brakestep", fBrakeStep ); + export_as_text( Output, "joinduplicatedevents", bJoinEvents ); + export_as_text( Output, "hiddenevents", iHiddenEvents ); + export_as_text( Output, "pause", ( iPause & 1 ) != 0 ); + export_as_text( Output, "lang", asLang ); + export_as_text( Output, "python.updatetime", PythonScreenUpdateRate ); + Output + << "uitextcolor " + << UITextColor.r * 255 << " " + << UITextColor.g * 255 << " " + << UITextColor.b * 255 << "\n"; + export_as_text( Output, "ui.bg.opacity", UIBgOpacity ); + export_as_text( Output, "input.gamepad", InputGamepad ); +#ifdef WITH_UART + if( uart_conf.enable ) { + Output + << "uart " + << uart_conf.port << " " + << uart_conf.baud << " " + << uart_conf.updatetime << "\n"; + } + Output + << "uarttune " + << uart_conf.mainbrakemin << " " + << uart_conf.mainbrakemax << " " + << uart_conf.localbrakemin << " " + << uart_conf.localbrakemax << " " + << uart_conf.tankmax << " " + << uart_conf.tankuart << " " + << uart_conf.pipemax << " " + << uart_conf.pipeuart << " " + << uart_conf.brakemax << " " + << uart_conf.brakeuart << " " + << uart_conf.hvmax << " " + << uart_conf.hvuart << " " + << uart_conf.currentmax << " " + << uart_conf.currentuart << " " + << uart_conf.lvmax << " " + << uart_conf.lvuart << "\n"; + export_as_text( Output, "uarttachoscale", uart_conf.tachoscale ); + Output + << "uartfeature " + << uart_conf.mainenable << " " + << uart_conf.scndenable << " " + << uart_conf.trainenable << " " + << uart_conf.localenable << "\n"; + export_as_text( Output, "uartdebug", uart_conf.debug ); +#endif +#ifdef USE_EXTCAM_CAMERA + export_as_text( Output, "extcam.cmd", extcam_cmd ); + export_as_text( Output, "extcam.rec", extcam_rec ); + Output + << "extcam.res " + << extcam_res.x << " " + << extcam_res.y << "\n"; +#endif + export_as_text( Output, "compresstex", compress_tex ); + export_as_text( Output, "gfx.framebuffer.width", gfx_framebuffer_width ); + export_as_text( Output, "gfx.framebuffer.height", gfx_framebuffer_height ); + export_as_text( Output, "gfx.framebuffer.fidelity", gfx_framebuffer_fidelity ); + export_as_text( Output, "gfx.shadowmap.enabled", gfx_shadowmap_enabled ); + // TODO: export fpslimit + export_as_text( Output, "randomseed", Global.random_seed ); + export_as_text( Output, "gfx.envmap.enabled", gfx_envmap_enabled ); + export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled ); + export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter ); + // TODO: export gfx_postfx_motionblur_format + export_as_text( Output, "gfx.postfx.chromaticaberration.enabled", gfx_postfx_chromaticaberration_enabled ); + // TODO: export gfx_format_color + // TODO: export gfx_format_depth + export_as_text( Output, "gfx.skiprendering", gfx_skiprendering ); + export_as_text( Output, "gfx.skippipeline", gfx_skippipeline ); + export_as_text( Output, "gfx.extraeffects", gfx_extraeffects ); + export_as_text( Output, "python.enabled", python_enabled ); + export_as_text( Output, "python.threadedupload", python_threadedupload ); + export_as_text( Output, "python.uploadmain", python_uploadmain ); + export_as_text( Output, "python.mipmaps", python_mipmaps ); + for( auto const &server : network_servers ) { + Output + << "network.server " + << server.first << " " << server.second << "\n"; + } + if( false == network_client->first.empty() ) { + Output + << "network.client " + << network_client->first << " " << network_client->second << "\n"; + } + export_as_text( Output, "execonexit", exec_on_exit ); +} + +template <> +void +global_settings::export_as_text( std::ostream &Output, std::string const Key, std::string const &Value ) const { + + if( Value.empty() ) { return; } + + if( Value.find( ' ' ) != std::string::npos ) { + Output << Key << " \"" << Value << "\"\n"; + } + else { + Output << Key << " " << Value << "\n"; + } +} + +template <> +void +global_settings::export_as_text( std::ostream &Output, std::string const Key, bool const &Value ) const { + + Output << Key << " " << ( Value ? "yes" : "no" ) << "\n"; +} diff --git a/Globals.h b/Globals.h index 106820ae6..d8c336311 100644 --- a/Globals.h +++ b/Globals.h @@ -28,11 +28,11 @@ struct global_settings { bool shiftState{ false }; //m7todo: brzydko bool ctrlState{ false }; bool altState{ false }; - std::mt19937 random_engine; - std::mt19937 local_random_engine; - bool ready_to_load { false }; - std::time_t starting_timestamp = 0; // starting time, in local timezone - uint32_t random_seed = 0; + std::mt19937 random_engine; + std::mt19937 local_random_engine; + bool ready_to_load{ false }; + std::time_t starting_timestamp = 0; // starting time, in local timezone + uint32_t random_seed = 0; TCamera pCamera; // parametry kamery TCamera pDebugCamera; std::array FreeCameraInit; // pozycje kamery @@ -56,12 +56,12 @@ struct global_settings { std::string asCurrentTexturePath{ szTexturePath }; std::string asCurrentDynamicPath; int CurrentMaxTextureSize{ 4096 }; - bool GfxFramebufferSRGB { true }; - bool UpdateMaterials { true }; + bool GfxFramebufferSRGB{ true }; + bool UpdateMaterials{ true }; // settings // filesystem bool bLoadTraction{ true }; - bool CreateSwitchTrackbeds { true }; + bool CreateSwitchTrackbeds{ true }; std::string szTexturesTGA{ ".tga" }; // lista tekstur od TGA std::string szTexturesDDS{ ".dds" }; // lista tekstur od DDS std::string szDefaultExt{ szTexturesDDS }; @@ -77,7 +77,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 }; + 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 }; @@ -91,9 +91,9 @@ struct global_settings { bool FakeLight{ false }; // toggle between fixed and dynamic daylight double fTimeSpeed{ 1.0 }; // przyspieszenie czasu, zmienna do testów double fLatitudeDeg{ 52.0 }; // szerokość geograficzna - float ScenarioTimeOverride { std::numeric_limits::quiet_NaN() }; // requested scenario start time - float ScenarioTimeOffset { 0.f }; // time shift (in hours) applied to train timetables - bool ScenarioTimeCurrent { false }; // automatic time shift to match scenario time with local clock + float ScenarioTimeOverride{ std::numeric_limits::quiet_NaN() }; // requested scenario start time + float ScenarioTimeOffset{ 0.f }; // time shift (in hours) applied to train timetables + bool ScenarioTimeCurrent{ false }; // automatic time shift to match scenario time with local clock bool bInactivePause{ true }; // automatyczna pauza, gdy okno nieaktywne int iSlowMotionMask{ -1 }; // maska wyłączanych właściwości bool bHideConsole{ false }; // hunter-271211: ukrywanie konsoli @@ -101,16 +101,16 @@ struct global_settings { bool bJoinEvents{ false }; // czy grupować eventy o tych samych nazwach int iHiddenEvents{ 1 }; // czy łączyć eventy z torami poprzez nazwę toru // ui - int PythonScreenUpdateRate { 200 }; // delay between python-based screen updates, in milliseconds + int PythonScreenUpdateRate{ 200 }; // delay between python-based screen updates, in milliseconds int iTextMode{ 0 }; // tryb pracy wyświetlacza tekstowego - glm::vec4 UITextColor { glm::vec4( 225.f / 255.f, 225.f / 255.f, 225.f / 255.f, 1.f ) }; // base color of UI text - float UIBgOpacity { 0.65f }; // opacity of ui windows + glm::vec4 UITextColor{ glm::vec4( 225.f / 255.f, 225.f / 255.f, 225.f / 255.f, 1.f ) }; // base color of UI text + float UIBgOpacity{ 0.65f }; // opacity of ui windows std::string asLang{ "pl" }; // domyślny język - http://tools.ietf.org/html/bcp47 // gfx int iWindowWidth{ 800 }; int iWindowHeight{ 600 }; float fDistanceFactor{ 1.f }; // baza do przeliczania odległości dla LoD - float targetfps { 0.0f }; + float targetfps{ 0.0f }; bool bFullScreen{ false }; bool VSync{ false }; bool bWireFrame{ false }; @@ -179,7 +179,7 @@ struct global_settings { // wartości maksymalne wyjść dla pulpitu double fCalibrateOutMax[ 7 ] = { 0, 0, 0, 0, 0, 0, 0 }; - int iCalibrateOutDebugInfo { -1 }; // numer wyjścia kalibrowanego dla którego wyświetlać informacje podczas kalibracji + int iCalibrateOutDebugInfo{ -1 }; // numer wyjścia kalibrowanego dla którego wyświetlać informacje podczas kalibracji int iPoKeysPWM[ 7 ] = { 0, 1, 2, 3, 4, 5, 6 }; // numery wejść dla PWM #ifdef WITH_UART uart_input::conf_t uart_conf; @@ -192,14 +192,14 @@ struct global_settings { // TODO: move these to relevant areas bool render_cab = true; - std::chrono::duration minframetime {0.0f}; + std::chrono::duration minframetime{ 0.0f }; std::string fullscreen_monitor; bool python_enabled = true; - bool python_mipmaps = true; - bool python_threadedupload = true; - bool python_uploadmain = true; + bool python_mipmaps = true; + bool python_threadedupload = true; + bool python_uploadmain = true; bool gfx_skiprendering = false; int gfx_framebuffer_width = -1; @@ -227,8 +227,29 @@ struct global_settings { // methods void LoadIniFile( std::string asFileName ); void ConfigParse( cParser &parser ); + // sends basic content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; + template + void + export_as_text( std::ostream &Output, std::string const Key, Type_ const &Value ) const; }; +template +void +global_settings::export_as_text( std::ostream &Output, std::string const Key, Type_ const &Value ) const { + + Output << Key << " " << Value << "\n"; +} + +template <> +void +global_settings::export_as_text( std::ostream &Output, std::string const Key, std::string const &Value ) const; + +template <> +void +global_settings::export_as_text( std::ostream &Output, std::string const Key, bool const &Value ) const; + extern global_settings Global; //--------------------------------------------------------------------------- diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 04b00a28e..b3f5e224f 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1647,6 +1647,9 @@ class TMoverParameters double PipeRatio(void);/*ile napelniac*/ double RealPipeRatio(void);/*jak szybko*/ double BrakeVP(void) const; + double EngineRPMRatio() const; // returns current engine revolutions as percentage of max engine revolutions, in range 0-1 + double EngineIdleRPM() const; + double EngineMaxRPM() const; /*! przesylanie komend sterujacych*/ bool SendCtrlToNext(std::string const CtrlCommand, double const ctrlvalue, double const dir, int const Couplertype = coupling::control ); diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index ae1ad9541..ff50476f7 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1000,6 +1000,34 @@ double TMoverParameters::PipeRatio(void) return pr; } +double +TMoverParameters::EngineRPMRatio() const { + + return clamp( ( + EngineType == TEngineType::DieselElectric ? ( ( 60.0 * std::abs( enrot ) ) / DElist[ MainCtrlPosNo ].RPM ) : + EngineType == TEngineType::DieselEngine ? ( std::abs( enrot ) / nmax ) : + 1.0 ), // shouldn't ever get here but, eh + 0.0, 1.0 ); +} + +double +TMoverParameters::EngineIdleRPM() const { + + return ( + EngineType == TEngineType::DieselEngine ? dizel_nmin * 60 : + EngineType == TEngineType::DieselElectric ? DElist[ MainCtrlNoPowerPos() ].RPM : + std::numeric_limits::max() ); // shouldn't ever get here but, eh +} + +double +TMoverParameters::EngineMaxRPM() const { + + return ( + EngineType == TEngineType::DieselEngine ? dizel_nmax * 60 : + EngineType == TEngineType::DieselElectric ? DElist[ MainCtrlPosNo ].RPM : + std::numeric_limits::max() ); // shouldn't ever get here but, eh +} + // ************************************************************************************************* // Q: 20160716 // Wykrywanie kolizji @@ -1959,17 +1987,13 @@ void TMoverParameters::OilPumpCheck( double const Timestep ) { OilPump.start_type == start_t::manualwithautofallback ? ( OilPump.is_enabled || dizel_startup ) : false ) ) ); // shouldn't ever get this far but, eh - auto const maxrevolutions { - EngineType == TEngineType::DieselEngine ? - dizel_nmax : - DElist[ MainCtrlPosNo ].RPM / 60.0 }; auto const minpressure { OilPump.pressure_minimum > 0.f ? OilPump.pressure_minimum : 0.15f }; // arbitrary fallback value OilPump.pressure_target = ( - enrot > 0.1 ? interpolate( minpressure, OilPump.pressure_maximum, static_cast( clamp( enrot / maxrevolutions, 0.0, 1.0 ) ) ) * OilPump.resource_amount : + enrot > 0.1 ? interpolate( minpressure, OilPump.pressure_maximum, static_cast( EngineRPMRatio() ) ) * OilPump.resource_amount : true == OilPump.is_active ? std::min( minpressure + 0.1f, OilPump.pressure_maximum ) : // slight pressure margin to give time to switch off the pump and start the engine 0.f ); @@ -4128,14 +4152,10 @@ void TMoverParameters::CompressorCheck(double dt) { switch( CompressorPower ) { case 3: { // the compressor is coupled with the diesel engine, engine revolutions affect the output - auto const enginefactor { ( - EngineType == TEngineType::DieselElectric ? ( ( 60.0 * std::abs( enrot ) ) / DElist[ MainCtrlPosNo ].RPM ) : - EngineType == TEngineType::DieselEngine ? ( std::abs( enrot ) / nmax ) : - 1.0 ) }; // shouldn't ever get here but, eh CompressedVolume += CompressorSpeedF * ( 2.0 * MaxCompressorF - Compressor ) / MaxCompressorF - * enginefactor + * EngineRPMRatio() * dt * ( CompressorGovernorLock ? 0.0 : 1.0 ); // with the lock active air is vented out break; @@ -5217,7 +5237,7 @@ double TMoverParameters::TractionForce( double dt ) { std::max( tmp, std::min( - DElist[ MainCtrlPosNo ].RPM, + EngineMaxRPM(), EngineHeatingRPM ) / 60.0 ); } @@ -5576,7 +5596,7 @@ double TMoverParameters::TractionForce( double dt ) { if (EIMCtrlType > 0) //sterowanie cyfrowe { auto eimic_positive = std::max(0.0, eimic_real); - auto const rpmratio {60.0 * enrot / DElist[MainCtrlPosNo].RPM}; + auto const rpmratio { EngineRPMRatio() }; tempImax = DElist[MainCtrlPosNo].Imax * eimic_positive; tempUmax = DElist[MainCtrlPosNo].Umax * std::min(eimic_positive, rpmratio); tempPmax = DElist[MainCtrlPosNo].GenPower * std::min(eimic_positive, rpmratio); @@ -5611,8 +5631,7 @@ double TMoverParameters::TractionForce( double dt ) { EngineVoltage = ( SST[ MainCtrlPos ].Umax * AnPos ) + ( SST[ MainCtrlPos ].Umin * ( 1.0 - AnPos ) ); // NOTE: very crude way to approximate power generated at current rpm instead of instant top output // NOTE, TODO: doesn't take into account potentially increased revolutions if heating is on, fix it - auto const rpmratio { 60.0 * enrot / DElist[ MainCtrlPos ].RPM }; - tmp = rpmratio * ( SST[ MainCtrlPos ].Pmax * AnPos ) + ( SST[ MainCtrlPos ].Pmin * ( 1.0 - AnPos ) ); + tmp = EngineRPMRatio() * ( SST[ MainCtrlPos ].Pmax * AnPos ) + ( SST[ MainCtrlPos ].Pmin * ( 1.0 - AnPos ) ); Ft = tmp * 1000.0 / ( abs( tmpV ) + 1.6 ); } else { @@ -6203,8 +6222,7 @@ double TMoverParameters::TractionForce( double dt ) { switch( EngineType ) { case TEngineType::DieselElectric: { // rough approximation of extra effort to overcome friction etc - auto const rpmratio{ 60.0 * enrot / DElist[ MainCtrlPosNo ].RPM }; - EnginePower += rpmratio * 0.15 * DElist[ MainCtrlPosNo ].GenPower; + EnginePower += EngineRPMRatio() * 0.15 * DElist[ MainCtrlPosNo ].GenPower; break; } default: { @@ -7780,11 +7798,7 @@ void TMoverParameters::dizel_Heat( double const dt ) { auto const engineoff { ( Mains ? 0 : 1 ) }; auto const rpm { enrot * 60 }; // TODO: calculate this once and cache for further use, instead of doing it repeatedly all over the place - auto const maxrevolutions { ( - EngineType == TEngineType::DieselEngine ? dizel_nmax * 60 : - EngineType == TEngineType::DieselElectric ? DElist[ MainCtrlPosNo ].RPM : - std::numeric_limits::max() ) }; // shouldn't ever get here but, eh - auto const revolutionsfactor { clamp( rpm / maxrevolutions, 0.0, 1.0 ) }; + auto const revolutionsfactor { EngineRPMRatio() }; auto const waterpump { WaterPump.is_active ? 1 : 0 }; auto const gw = engineon * interpolate( gwmin, gwmax, revolutionsfactor ) + waterpump * 1000 + engineoff * 200; diff --git a/application.cpp b/application.cpp index 3d026496f..b2876d79c 100644 --- a/application.cpp +++ b/application.cpp @@ -127,6 +127,15 @@ eu07_application::init( int Argc, char *Argv[] ) { WriteLog( "Authors: Marcin_EU, McZapkie, ABu, Winger, Tolaris, nbmx, OLO_EU, Bart, Quark-t, " "ShaXbee, Oli_EU, youBy, KURS90, Ra, hunter, szociu, Stele, Q, firleju and others\n" ); + { + WriteLog( "// settings" ); + std::stringstream settingspipe; + Global.export_as_text( settingspipe ); + WriteLog( settingspipe.str() ); + } + + WriteLog( "// startup" ); + if( ( result = init_glfw() ) != 0 ) { return result; } diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 9a53e35de..2e37fa11f 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -659,7 +659,6 @@ debug_panel::update_section_vehicle( std::vector &Output ) { auto const &mover { *m_input.mover }; auto const isowned { /* ( vehicle.Mechanik == nullptr ) && */ ( vehicle.ctOwner != nullptr ) && ( vehicle.ctOwner->Vehicle() != m_input.vehicle ) }; - auto const isplayervehicle { ( m_input.train != nullptr ) && ( m_input.train->Dynamic() == m_input.vehicle ) }; auto const isdieselenginepowered { ( mover.EngineType == TEngineType::DieselElectric ) || ( mover.EngineType == TEngineType::DieselEngine ) }; auto const isdieselinshuntmode { mover.ShuntMode && mover.EngineType == TEngineType::DieselElectric }; @@ -699,7 +698,7 @@ debug_panel::update_section_vehicle( std::vector &Output ) { ( mover.CompressorGovernorLock ? '!' : '.' ), ( mover.StLinSwitchOff ? '-' : ( mover.ControlPressureSwitch ? '!' : ( mover.StLinFlag ? '+' : '.' ) ) ), ( mover.Heating ? 'H' : ( mover.HeatingAllow ? 'h' : '.' ) ), - std::string( isplayervehicle ? locale::strings[ locale::string::debug_vehicle_radio ] + ( mover.Radio ? std::to_string( m_input.train->RadioChannel() ) : "-" ) : "" ).c_str(), + std::string( m_input.mechanik ? locale::strings[ locale::string::debug_vehicle_radio ] + ( mover.Radio ? std::to_string( m_input.mechanik->iRadioChannel ) : "-" ) : "" ).c_str(), std::string( isdieselenginepowered ? locale::strings[ locale::string::debug_vehicle_oilpressure ] + to_string( mover.OilPump.pressure, 2 ) : "" ).c_str(), // power transfers // 3000v diff --git a/mtable.cpp b/mtable.cpp index c0fccfdbe..71373ff02 100644 --- a/mtable.cpp +++ b/mtable.cpp @@ -57,6 +57,13 @@ bool TTrainParameters::IsMaintenance() const { return false; } +int TTrainParameters::radio_channel() const { + if( ( StationIndex < StationCount ) ) + return TimeTable[ StationIndex ].radio_channel; + else + return -1; +} + bool TTrainParameters::UpdateMTable( scenario_time const &Time, std::string const &NewName ) { return UpdateMTable( Time.data().wHour, Time.data().wMinute + Time.data().wSecond * 0.0167, NewName ); @@ -406,9 +413,22 @@ bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax) || ( s == "2" ) || fin.bad() ) ) { record->StationWare += s; - record->is_maintenance = ( s.find( "pt" ) != std::string::npos ); fin >> s; } + // cache relevant station data + record->is_maintenance = ( s.find( "pt" ) != std::string::npos ); + { + auto const stationware { Split( record->StationWare, ',' ) }; + for( auto const &entry : stationware ) { + if( entry[ 0 ] == 'R' ) { + auto const entrysplit { split_index( entry ) }; + if( ( entrysplit.first == "R" ) + && ( entrysplit.second <= 10 ) ) { + record->radio_channel = entrysplit.second; + } + } + } + } record->TrackNo = atoi(s.c_str()); fin >> s; if (s != "|") diff --git a/mtable.h b/mtable.h index 1c13772ec..8c2fc0e1b 100644 --- a/mtable.h +++ b/mtable.h @@ -37,6 +37,7 @@ struct TMTableLine float Dm{ -1.f }; // godz. i min. odjazdu float tm{ 0.f }; // czas jazdy do tej stacji w min. (z kolumny) bool is_maintenance{ false }; + int radio_channel{ -1 }; }; typedef TMTableLine TMTable[MaxTTableSize + 1]; @@ -77,6 +78,8 @@ class TTrainParameters bool DirectionChange(); void StationIndexInc(); void serialize( dictionary_source *Output ) const; + // returns: radio channel associated with current station, or -1 + int radio_channel() const; }; class TMTableTime diff --git a/particles.cpp b/particles.cpp index 44df63de6..f2fa5188b 100644 --- a/particles.cpp +++ b/particles.cpp @@ -275,9 +275,10 @@ smoke_source::location() const { break; } case owner_type::node: { - // TODO: take into account node rotation - auto const rotation { glm::angleAxis( glm::radians( m_owner.node->Angles().y ), glm::vec3{ 0.f, 1.f, 0.f } ) }; - location = rotation * glm::vec3{ m_offset }; + auto const rotationx { glm::angleAxis( glm::radians( m_owner.node->Angles().x ), glm::vec3{ 1.f, 0.f, 0.f } ) }; + auto const rotationy { glm::angleAxis( glm::radians( m_owner.node->Angles().y ), glm::vec3{ 0.f, 1.f, 0.f } ) }; + auto const rotationz { glm::angleAxis( glm::radians( m_owner.node->Angles().z ), glm::vec3{ 0.f, 0.f, 1.f } ) }; + location = rotationy * rotationx * rotationz * glm::vec3{ m_offset }; location += m_owner.node->location(); break; } diff --git a/sound.h b/sound.h index d810fee08..fc4aad08b 100644 --- a/sound.h +++ b/sound.h @@ -17,6 +17,7 @@ float const EU07_SOUND_GLOBALRANGE { -1.f }; float const EU07_SOUND_CABCONTROLSCUTOFFRANGE { 7.5f }; float const EU07_SOUND_BRAKINGCUTOFFRANGE { 100.f }; float const EU07_SOUND_RUNNINGNOISECUTOFFRANGE { 200.f }; +float const EU07_SOUND_HANDHELDRADIORANGE { 750.f }; enum class sound_type { single,