From 09f24df109cca32cbd6dc200795dbb60b49c8c04 Mon Sep 17 00:00:00 2001 From: tmj-fstate Date: Fri, 24 Aug 2018 21:11:35 +0200 Subject: [PATCH] scenery groups export and deserialization, associated events included in scenery node groups, AI brake charging tweak, door locking and vehicle hunting oscillation sounds --- Console.cpp | 27 +++---- Driver.cpp | 24 +++--- DynObj.cpp | 51 ++++++++++++- DynObj.h | 11 ++- Event.cpp | 8 +- Event.h | 19 +++++ Globals.h | 12 +-- Train.cpp | 133 ++++++++++++++++++++-------------- Train.h | 3 + sceneeditor.cpp | 15 ++-- scenenodegroups.cpp | 51 ++++++++++++- scenenodegroups.h | 24 ++++-- simulationstateserializer.cpp | 42 +++++++++-- simulationstateserializer.h | 2 + 14 files changed, 304 insertions(+), 118 deletions(-) diff --git a/Console.cpp b/Console.cpp index 42e536f6..4556b727 100644 --- a/Console.cpp +++ b/Console.cpp @@ -275,14 +275,13 @@ void Console::ValueSet(int x, double y) WriteLog( " fraction=" + std::to_string( y ) ); } } - double temp = (((((Global.fCalibrateOut[x][5] * y) + Global.fCalibrateOut[x][4]) * y + - Global.fCalibrateOut[x][3]) * - y + - Global.fCalibrateOut[x][2]) * - y + - Global.fCalibrateOut[x][1]) * - y + - Global.fCalibrateOut[x][0]; // zakres <0;1> + double temp = ((((( + Global.fCalibrateOut[x][5] * y) + + Global.fCalibrateOut[x][4]) * y + + Global.fCalibrateOut[x][3]) * y + + Global.fCalibrateOut[x][2]) * y + + Global.fCalibrateOut[x][1]) * y + + Global.fCalibrateOut[x][0]; // zakres <0;1> if( Global.iCalibrateOutDebugInfo == x ) { WriteLog( " calibrated=" + std::to_string( temp ) ); } @@ -320,11 +319,13 @@ float Console::AnalogCalibrateGet(int x) if (iMode == 4 && PoKeys55[0]) { float b = PoKeys55[0]->fAnalog[x]; - b = (((((Global.fCalibrateIn[x][5] * b) + Global.fCalibrateIn[x][4]) * b + - Global.fCalibrateIn[x][3]) * b + - Global.fCalibrateIn[x][2]) * b + - Global.fCalibrateIn[x][1]) * b + - Global.fCalibrateIn[x][0]; + b = ((((( + Global.fCalibrateIn[x][5] * b) + + Global.fCalibrateIn[x][4]) * b + + Global.fCalibrateIn[x][3]) * b + + Global.fCalibrateIn[x][2]) * b + + Global.fCalibrateIn[x][1]) * b + + Global.fCalibrateIn[x][0]; if (x == 0) return (b + 2) / 8; if (x == 1) return b/10; else return b; diff --git a/Driver.cpp b/Driver.cpp index d253e5df..14a54f55 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2021,8 +2021,12 @@ bool TController::CheckVehicles(TOrders user) pVehicles[1] = p; // zapamiętanie ostatniego fLength += p->MoverParameters->Dim.L; // dodanie długości pojazdu fMass += p->MoverParameters->TotalMass; // dodanie masy łącznie z ładunkiem - if (fVelMax < 0 ? true : p->MoverParameters->Vmax < fVelMax) - fVelMax = p->MoverParameters->Vmax; // ustalenie maksymalnej prędkości dla składu + fVelMax = min_speed( fVelMax, p->MoverParameters->Vmax ); // ustalenie maksymalnej prędkości dla składu + // reset oerlikon brakes consist flag as different type was detected + if( ( p->MoverParameters->BrakeSubsystem != TBrakeSubSystem::ss_ESt ) + && ( p->MoverParameters->BrakeSubsystem != TBrakeSubSystem::ss_LSt ) ) { + iDrivigFlags &= ~( moveOerlikons ); + } p = p->Neightbour(dir); // pojazd podłączony od wskazanej strony } if (main) @@ -2034,9 +2038,8 @@ bool TController::CheckVehicles(TOrders user) p = pVehicles[0]; while (p) { - if (TrainParams) - if (p->asDestination == "none") - p->DestinationSet(TrainParams->Relation2, TrainParams->TrainName); // relacja docelowa, jeśli nie było + if (p->asDestination == "none") + p->DestinationSet(TrainParams->Relation2, TrainParams->TrainName); // relacja docelowa, jeśli nie było if (AIControllFlag) // jeśli prowadzi komputer p->RaLightsSet(0, 0); // gasimy światła if (p->MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) @@ -3020,10 +3023,8 @@ void TController::SpeedSet() mvOccupied->DirectionForward(); //żeby EN57 jechały na drugiej nastawie { if (mvControlling->MainCtrlPos && - !mvControlling - ->StLinFlag) // jak niby jedzie, ale ma rozłączone liniowe - mvControlling->DecMainCtrl( - 2); // to na zero i czekać na przewalenie kułakowego + !mvControlling->StLinFlag) // jak niby jedzie, ale ma rozłączone liniowe + mvControlling->DecMainCtrl(2); // to na zero i czekać na przewalenie kułakowego else switch (mvControlling->MainCtrlPos) { // ruch nastawnika uzależniony jest od aktualnie ustawionej @@ -5225,10 +5226,7 @@ TController::UpdateSituation(double dt) { } if( ( mvOccupied->BrakeCtrlPos < 0 ) - && ( mvOccupied->EqvtPipePress > ( - fReady < 0.25 ? - 5.1 : - 5.2 ) ) ) { + && ( mvOccupied->EqvtPipePress > ( fReady < 0.25 ? 5.1 : 5.2 ) ) ) { mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_RP ) ); } } diff --git a/DynObj.cpp b/DynObj.cpp index 2cd111c3..a8dc1adb 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -4203,6 +4203,21 @@ void TDynamicObject::RenderSounds() { } } } + // door locks + if( MoverParameters->DoorBlockedFlag() != m_doorlocks ) { + // toggle state of the locks... + m_doorlocks = !m_doorlocks; + // ...and play relevant sounds + for( auto &door : m_doorsounds ) { + if( m_doorlocks ) { + door.lock.play( sound_flags::exclusive ); + } + else { + door.unlock.play( sound_flags::exclusive ); + } + } + } + // horns if( TestFlag( MoverParameters->WarningSignal, 1 ) ) { sHorn1.play( sound_flags::exclusive | sound_flags::looping ); @@ -5413,7 +5428,7 @@ void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName, } else if( token == "dooropen:" ) { - sound_source soundtemplate { sound_placement::general }; + sound_source soundtemplate { sound_placement::general, 25.f }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { @@ -5425,7 +5440,7 @@ void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName, } else if( token == "doorclose:" ) { - sound_source soundtemplate { sound_placement::general }; + sound_source soundtemplate { sound_placement::general, 25.f }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { @@ -5436,8 +5451,32 @@ void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName, } } + else if( token == "doorlock:" ) { + sound_source soundtemplate { sound_placement::general, 12.5f }; + soundtemplate.deserialize( parser, sound_type::single ); + soundtemplate.owner( this ); + for( auto &door : m_doorsounds ) { + // apply configuration to all defined doors, but preserve their individual offsets + auto const dooroffset { door.lock.offset() }; + door.lock = soundtemplate; + door.lock.offset( dooroffset ); + } + } + + else if( token == "doorunlock:" ) { + sound_source soundtemplate { sound_placement::general, 12.5f }; + soundtemplate.deserialize( parser, sound_type::single ); + soundtemplate.owner( this ); + for( auto &door : m_doorsounds ) { + // apply configuration to all defined doors, but preserve their individual offsets + auto const dooroffset { door.unlock.offset() }; + door.unlock = soundtemplate; + door.unlock.offset( dooroffset ); + } + } + else if( token == "doorstepopen:" ) { - sound_source soundtemplate { sound_placement::general }; + sound_source soundtemplate { sound_placement::general, 20.f }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { @@ -5449,7 +5488,7 @@ void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName, } else if( token == "doorstepclose:" ) { - sound_source soundtemplate { sound_placement::general }; + sound_source soundtemplate { sound_placement::general, 20.f }; soundtemplate.deserialize( parser, sound_type::single ); soundtemplate.owner( this ); for( auto &door : m_doorsounds ) { @@ -5539,6 +5578,8 @@ void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName, auto const location { glm::vec3 { MoverParameters->Dim.W * 0.5f, MoverParameters->Dim.H * 0.5f, offset } }; door.rsDoorClose.offset( location ); door.rsDoorOpen.offset( location ); + door.lock.offset( location ); + door.unlock.offset( location ); door.step_close.offset( location ); door.step_open.offset( location ); m_doorsounds.emplace_back( door ); @@ -5549,6 +5590,8 @@ void TDynamicObject::LoadMMediaFile( std::string BaseDir, std::string TypeName, auto const location { glm::vec3 { MoverParameters->Dim.W * -0.5f, MoverParameters->Dim.H * 0.5f, offset } }; door.rsDoorClose.offset( location ); door.rsDoorOpen.offset( location ); + door.lock.offset( location ); + door.unlock.offset( location ); door.step_close.offset( location ); door.step_open.offset( location ); m_doorsounds.emplace_back( door ); diff --git a/DynObj.h b/DynObj.h index 0bd80c0e..3a99b2f6 100644 --- a/DynObj.h +++ b/DynObj.h @@ -305,10 +305,12 @@ private: }; struct door_sounds { - sound_source rsDoorOpen { sound_placement::general, 25.f }; // Ra: przeniesione z kabiny - sound_source rsDoorClose { sound_placement::general, 25.f }; - sound_source step_open { sound_placement::general, 25.f }; - sound_source step_close { sound_placement::general, 25.f }; + sound_source rsDoorOpen { sound_placement::general }; // Ra: przeniesione z kabiny + sound_source rsDoorClose { sound_placement::general }; + sound_source lock { sound_placement::general }; + sound_source unlock { sound_placement::general }; + sound_source step_open { sound_placement::general }; + sound_source step_close { sound_placement::general }; }; struct exchange_sounds { @@ -423,6 +425,7 @@ private: std::array m_couplersounds; // always front and rear std::vector m_pantographsounds; // typically 2 but can be less (or more?) std::vector m_doorsounds; // can expect symmetrical arrangement, but don't count on it + bool m_doorlocks { false }; // sound helper, current state of door locks sound_source sDepartureSignal { sound_placement::general }; sound_source sHorn1 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; sound_source sHorn2 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; diff --git a/Event.cpp b/Event.cpp index 13646dd3..14f631fa 100644 --- a/Event.cpp +++ b/Event.cpp @@ -1764,11 +1764,15 @@ event_manager::export_as_text( std::ostream &Output ) const { Output << "// events\n"; for( auto const *event : m_events ) { - event->export_as_text( Output ); + if( event->group() == null_handle ) { + event->export_as_text( Output ); + } } Output << "// event launchers\n"; for( auto const *launcher : m_launchers.sequence() ) { - launcher->export_as_text( Output ); + if( launcher->group() == null_handle ) { + launcher->export_as_text( Output ); + } } } diff --git a/Event.h b/Event.h index 4fa9449e..9593f932 100644 --- a/Event.h +++ b/Event.h @@ -95,6 +95,10 @@ public: bool StopCommand(); void StopCommandSent(); void Append(TEvent *e); + void + group( scene::group_handle Group ); + scene::group_handle + group() const; // members std::string asName; bool m_ignored { false }; // replacement for tp_ignored @@ -114,9 +118,24 @@ public: bool m_conditionalelse { false }; // TODO: make a part of condition struct private: +// methods void Conditions( cParser *parser, std::string s ); +// members + scene::group_handle m_group { null_handle }; // group this event belongs to, if any }; +inline +void +TEvent::group( scene::group_handle Group ) { + m_group = Group; +} + +inline +scene::group_handle +TEvent::group() const { + return m_group; +} + class event_manager { public: diff --git a/Globals.h b/Globals.h index c6c39279..c94a9282 100644 --- a/Globals.h +++ b/Globals.h @@ -141,12 +141,12 @@ struct global_settings { double fBrakeStep{ 1.0 }; // krok zmiany hamulca dla klawiszy [Num3] i [Num9] // parametry kalibracyjne wejść z pulpitu double fCalibrateIn[ 6 ][ 6 ] = { - { 0, 1, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0 } }; + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0 } }; // parametry kalibracyjne wyjść dla pulpitu double fCalibrateOut[ 7 ][ 6 ] = { { 0, 1, 0, 0, 0, 0 }, diff --git a/Train.cpp b/Train.cpp index 0009c254..f059b765 100644 --- a/Train.cpp +++ b/Train.cpp @@ -4409,13 +4409,16 @@ void TTrain::UpdateMechPosition(double dt) && ( true == mvOccupied->TruckHunting ) ) { // hunting oscillation HuntingAngle = clamp_circular( HuntingAngle + 4.0 * HuntingShake.frequency * dt * mvOccupied->Vel, 360.0 ); - shakevector.x += - ( std::sin( glm::radians( HuntingAngle ) ) * dt * HuntingShake.scale ) - * interpolate( + auto const huntingamount = + interpolate( 0.0, 1.0, clamp( ( mvOccupied->Vel - HuntingShake.fadein_begin ) / ( HuntingShake.fadein_end - HuntingShake.fadein_begin ), 0.0, 1.0 ) ); + shakevector.x += + ( std::sin( glm::radians( HuntingAngle ) ) * dt * HuntingShake.scale ) + * huntingamount; + IsHunting = ( huntingamount > 0.025 ); } if( iVel > 0.5 ) { @@ -5873,60 +5876,24 @@ TTrain::update_sounds( double const Deltatime ) { && ( false == Global.CabWindowOpen ) && ( DynamicObject->GetVelocity() > 0.5 ) ) { - // frequency calculation - auto const normalizer { ( - true == rsRunningNoise.is_combined() ? - mvOccupied->Vmax * 0.01f : - 1.f ) }; - auto const frequency { - rsRunningNoise.m_frequencyoffset - + rsRunningNoise.m_frequencyfactor * mvOccupied->Vel * normalizer }; - - // volume calculation - volume = - rsRunningNoise.m_amplitudeoffset - + rsRunningNoise.m_amplitudefactor * mvOccupied->Vel; - if( std::abs( mvOccupied->nrot ) > 0.01 ) { - // hamulce wzmagaja halas - auto const brakeforceratio { ( - clamp( - mvOccupied->UnitBrakeForce / std::max( 1.0, mvOccupied->BrakeForceR( 1.0, mvOccupied->Vel ) / ( mvOccupied->NAxles * std::max( 1, mvOccupied->NBpA ) ) ), - 0.0, 1.0 ) ) }; - - volume *= 1 + 0.125 * brakeforceratio; - } - // scale volume by track quality - // TODO: track quality and/or environment factors as separate subroutine - volume *= - interpolate( - 0.8, 1.2, - clamp( - DynamicObject->MyTrack->iQualityFlag / 20.0, - 0.0, 1.0 ) ); - // for single sample sounds muffle the playback at low speeds - if( false == rsRunningNoise.is_combined() ) { - volume *= - interpolate( - 0.0, 1.0, - clamp( - mvOccupied->Vel / 40.0, - 0.0, 1.0 ) ); - } - - if( volume > 0.05 ) { - rsRunningNoise - .pitch( frequency ) - .gain( volume ) - .play( sound_flags::exclusive | sound_flags::looping ); - } - else { - rsRunningNoise.stop(); - } + update_sounds_runningnoise( rsRunningNoise ); } else { // 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 ) + && ( DynamicObject->GetVelocity() > 0.5 ) + && ( IsHunting ) ) { + + update_sounds_runningnoise( rsHuntingNoise ); + } + else { + // don't play the optional ending sound if the listener switches views + rsHuntingNoise.stop( true == FreeFlyModeFlag ); + } // McZapkie-141102: SHP i czuwak, TODO: sygnalizacja kabinowa if (mvOccupied->SecuritySystem.Status > 0) { @@ -5977,6 +5944,58 @@ TTrain::update_sounds( double const Deltatime ) { } } +void TTrain::update_sounds_runningnoise( sound_source &Sound ) { + // frequency calculation + auto const normalizer { ( + true == Sound.is_combined() ? + mvOccupied->Vmax * 0.01f : + 1.f ) }; + auto const frequency { + Sound.m_frequencyoffset + + Sound.m_frequencyfactor * mvOccupied->Vel * normalizer }; + + // volume calculation + auto volume = + Sound.m_amplitudeoffset + + Sound.m_amplitudefactor * mvOccupied->Vel; + if( std::abs( mvOccupied->nrot ) > 0.01 ) { + // hamulce wzmagaja halas + auto const brakeforceratio { ( + clamp( + mvOccupied->UnitBrakeForce / std::max( 1.0, mvOccupied->BrakeForceR( 1.0, mvOccupied->Vel ) / ( mvOccupied->NAxles * std::max( 1, mvOccupied->NBpA ) ) ), + 0.0, 1.0 ) ) }; + + volume *= 1 + 0.125 * brakeforceratio; + } + // scale volume by track quality + // TODO: track quality and/or environment factors as separate subroutine + volume *= + interpolate( + 0.8, 1.2, + clamp( + DynamicObject->MyTrack->iQualityFlag / 20.0, + 0.0, 1.0 ) ); + // for single sample sounds muffle the playback at low speeds + if( false == Sound.is_combined() ) { + volume *= + interpolate( + 0.0, 1.0, + clamp( + mvOccupied->Vel / 40.0, + 0.0, 1.0 ) ); + } + + if( volume > 0.05 ) { + Sound + .pitch( frequency ) + .gain( volume ) + .play( sound_flags::exclusive | sound_flags::looping ); + } + else { + Sound.stop(); + } +} + bool TTrain::CabChange(int iDirection) { // McZapkie-090902: zmiana kabiny 1->0->2 i z powrotem if( ( DynamicObject->Mechanik == nullptr ) @@ -6146,6 +6165,14 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName) rsRunningNoise.m_amplitudefactor /= ( 1 + mvOccupied->Vmax ); rsRunningNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); } + else if( token == "huntingnoise:" ) { + // hunting oscillation sound: + rsHuntingNoise.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax ); + rsHuntingNoise.owner( DynamicObject ); + + rsHuntingNoise.m_amplitudefactor /= ( 1 + mvOccupied->Vmax ); + rsHuntingNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); + } else if (token == "mechspring:") { // parametry bujania kamery: @@ -6217,7 +6244,7 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) &dsbReverserKey, &dsbNastawnikJazdy, &dsbNastawnikBocz, &dsbSwitch, &dsbPneumaticSwitch, &rsHiss, &rsHissU, &rsHissE, &rsHissX, &rsHissT, &rsSBHiss, &rsSBHissU, - &rsFadeSound, &rsRunningNoise, + &rsFadeSound, &rsRunningNoise, &rsHuntingNoise, &dsbHasler, &dsbBuzzer, &dsbSlipAlarm, &m_radiosound, &m_radiostop }; for( auto sound : sounds ) { diff --git a/Train.h b/Train.h index 5f80c0a3..f2c57078 100644 --- a/Train.h +++ b/Train.h @@ -142,6 +142,7 @@ class TTrain void set_paired_open_motor_connectors_button( bool const State ); // update function subroutines void update_sounds( double const Deltatime ); + void update_sounds_runningnoise( sound_source &Sound ); // command handlers // NOTE: we're currently using universal handlers and static handler map but it may be beneficial to have these implemented on individual class instance basis @@ -572,6 +573,7 @@ public: // reszta może by?publiczna float fadein_end { 0.f }; // full effect speed in km/h } HuntingShake; float HuntingAngle { 0.f }; // crude approximation of hunting oscillation; current angle of sine wave + bool IsHunting { false }; sound_source dsbReverserKey { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // hunter-121211 sound_source dsbNastawnikJazdy { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; @@ -591,6 +593,7 @@ public: // reszta może by?publiczna sound_source rsFadeSound { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; sound_source rsRunningNoise{ sound_placement::internal, EU07_SOUND_GLOBALRANGE }; + sound_source rsHuntingNoise{ sound_placement::internal, EU07_SOUND_GLOBALRANGE }; sound_source dsbHasler { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; sound_source dsbBuzzer { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; diff --git a/sceneeditor.cpp b/sceneeditor.cpp index 2961a602..5aafa4ab 100644 --- a/sceneeditor.cpp +++ b/sceneeditor.cpp @@ -37,9 +37,10 @@ basic_editor::translate( scene::basic_node *Node, glm::dvec3 const &Location, bo else { // translate entire group // TODO: contextual switch between group and item translation - auto nodegroup { scene::Groups.group( Node->group() ) }; + // TODO: translation of affected/relevant events + auto &nodegroup { scene::Groups.group( Node->group() ).nodes }; std::for_each( - nodegroup.first, nodegroup.second, + std::begin( nodegroup ), std::end( nodegroup ), [&]( auto *node ) { translate_node( node, node->location() + translation ); } ); } @@ -59,9 +60,10 @@ basic_editor::translate( scene::basic_node *Node, float const Offset ) { else { // translate entire group // TODO: contextual switch between group and item translation - auto nodegroup { scene::Groups.group( Node->group() ) }; + // TODO: translation of affected/relevant events + auto &nodegroup { scene::Groups.group( Node->group() ).nodes }; std::for_each( - nodegroup.first, nodegroup.second, + std::begin( nodegroup ), std::end( nodegroup ), [&]( auto *node ) { translate_node( node, offset ); } ); } @@ -146,10 +148,11 @@ basic_editor::rotate( scene::basic_node *Node, glm::vec3 const &Angle, float con else { // rotate entire group // TODO: contextual switch between group and item rotation + // TODO: translation of affected/relevant events auto const rotationcenter { Node->location() }; - auto nodegroup { scene::Groups.group( Node->group() ) }; + auto &nodegroup { scene::Groups.group( Node->group() ).nodes }; std::for_each( - nodegroup.first, nodegroup.second, + std::begin( nodegroup ), std::end( nodegroup ), [&]( auto *node ) { rotate_node( node, rotation ); if( node != Node ) { diff --git a/scenenodegroups.cpp b/scenenodegroups.cpp index c8720c25..68fe4123 100644 --- a/scenenodegroups.cpp +++ b/scenenodegroups.cpp @@ -10,6 +10,9 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "scenenodegroups.h" +#include "event.h" +#include "memcell.h" + namespace scene { node_groups Groups; @@ -37,7 +40,7 @@ node_groups::close() { auto lookup { m_groupmap.find( closinggroup ) }; if( ( lookup != m_groupmap.end() ) - && ( lookup->second.size() <= 1 ) ) { + && ( ( lookup->second.nodes.size() + lookup->second.events.size() ) <= 1 ) ) { erase( lookup ); } @@ -65,20 +68,62 @@ node_groups::insert( scene::group_handle const Group, scene::basic_node *Node ) if( Group == null_handle ) { return; } - auto &nodesequence { m_groupmap[ Group ] }; + auto &nodesequence { m_groupmap[ Group ].nodes }; if( std::find( std::begin( nodesequence ), std::end( nodesequence ), Node ) == std::end( nodesequence ) ) { // don't add the same node twice nodesequence.emplace_back( Node ); } } +// places provided event in specified group +void +node_groups::insert( scene::group_handle const Group, TEvent *Event ) { + + // TBD, TODO: automatically unregister the event from its current group? + Event->group( Group ); + + if( Group == null_handle ) { return; } + + auto &eventsequence { m_groupmap[ Group ].events }; + if( std::find( std::begin( eventsequence ), std::end( eventsequence ), Event ) == std::end( eventsequence ) ) { + // don't add the same node twice + eventsequence.emplace_back( Event ); + } +} + +// sends basic content of the class in legacy (text) format to provided stream +void +node_groups::export_as_text( std::ostream &Output ) const { + + for( auto const &group : m_groupmap ) { + + Output << "group\n"; + for( auto *node : group.second.nodes ) { + // HACK: auto-generated memory cells aren't exported, so we check for this + // TODO: is_exportable as basic_node method + if( ( typeid( *node ) == typeid( TMemCell ) ) + && ( false == static_cast( node )->is_exportable ) ) { + continue; + } + node->export_as_text( Output ); + } + for( auto *event : group.second.events ) { + event->export_as_text( Output ); + } + Output << "endgroup\n"; + } +} + // removes specified group from the group list and group information from the group's nodes void node_groups::erase( group_map::const_iterator Group ) { - for( auto *node : Group->second ) { + for( auto *node : Group->second.nodes ) { node->group( null_handle ); } + for( auto *event : Group->second.events ) { + event->group( null_handle ); + } m_groupmap.erase( Group ); } diff --git a/scenenodegroups.h b/scenenodegroups.h index 762f4789..0ad9a6a8 100644 --- a/scenenodegroups.h +++ b/scenenodegroups.h @@ -13,14 +13,16 @@ http://mozilla.org/MPL/2.0/. namespace scene { +struct basic_group { +// members + std::vector nodes; + std::vector events; +}; + // holds lists of grouped scene nodes class node_groups { // NOTE: during scenario deserialization encountering *.inc file causes creation of a new group on the group stack // this allows all nodes listed in this *.inc file to be grouped and potentially modified together by the editor. -private: - // types - using node_sequence = std::vector; - public: // constructors node_groups() = default; @@ -37,14 +39,20 @@ public: // places provided node in specified group void insert( scene::group_handle const Group, scene::basic_node *Node ); - std::pair + // places provided event in specified group + void + insert( scene::group_handle const Group, TEvent *Event ); + // grants direct access to specified group + scene::basic_group & group( scene::group_handle const Group ) { - auto &group { m_groupmap[ Group ] }; - return { std::begin( group ), std::end( group ) }; } + return m_groupmap[ Group ]; } + // sends basic content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; private: // types - using group_map = std::unordered_map; + using group_map = std::unordered_map; // methods // removes specified group from the group list and group information from the group's nodes void diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index 6de89e12..1a556164 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -73,6 +73,8 @@ state_serializer::deserialize( cParser &Input, scene::scratch_data &Scratchpad ) { "description", &state_serializer::deserialize_description }, { "event", &state_serializer::deserialize_event }, { "firstinit", &state_serializer::deserialize_firstinit }, + { "group", &state_serializer::deserialize_group }, + { "endgroup", &state_serializer::deserialize_endgroup }, { "light", &state_serializer::deserialize_light }, { "node", &state_serializer::deserialize_node }, { "origin", &state_serializer::deserialize_origin }, @@ -237,7 +239,10 @@ state_serializer::deserialize_event( cParser &Input, scene::scratch_data &Scratc Scratchpad.location.offset.top().z ) ); event->Load( &Input, offset ); - if( false == simulation::Events.insert( event ) ) { + if( true == simulation::Events.insert( event ) ) { + scene::Groups.insert( scene::Groups.handle(), event ); + } + else { delete event; } } @@ -256,6 +261,18 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc Scratchpad.initialized = true; } +void +state_serializer::deserialize_group( cParser &Input, scene::scratch_data &Scratchpad ) { + + scene::Groups.create(); +} + +void +state_serializer::deserialize_endgroup( cParser &Input, scene::scratch_data &Scratchpad ) { + + scene::Groups.close(); +} + void state_serializer::deserialize_light( cParser &Input, scene::scratch_data &Scratchpad ) { @@ -897,27 +914,39 @@ state_serializer::export_as_text( std::string const &Scenariofile ) const { filename = Global.asCurrentSceneryPath + filename + "_export"; std::ofstream scmfile { filename + ".scm" }; + // groups + scmfile << "// groups\n"; + scene::Groups.export_as_text( scmfile ); // tracks scmfile << "// paths\n"; for( auto const *path : Paths.sequence() ) { - path->export_as_text( scmfile ); + if( path->group() == null_handle ) { + path->export_as_text( scmfile ); + } } // traction scmfile << "// traction\n"; for( auto const *traction : Traction.sequence() ) { - traction->export_as_text( scmfile ); + if( traction->group() == null_handle ) { + traction->export_as_text( scmfile ); + } } // power grid scmfile << "// traction power sources\n"; for( auto const *powersource : Powergrid.sequence() ) { - powersource->export_as_text( scmfile ); + if( powersource->group() == null_handle ) { + powersource->export_as_text( scmfile ); + } } // models scmfile << "// instanced models\n"; for( auto const *instance : Instances.sequence() ) { - instance->export_as_text( scmfile ); + if( instance->group() == null_handle ) { + instance->export_as_text( scmfile ); + } } // sounds + // NOTE: sounds currently aren't included in groups scmfile << "// sounds\n"; Region->export_as_text( scmfile ); @@ -925,7 +954,8 @@ state_serializer::export_as_text( std::string const &Scenariofile ) const { // mem cells ctrfile << "// memory cells\n"; for( auto const *memorycell : Memory.sequence() ) { - if( true == memorycell->is_exportable ) { + if( ( true == memorycell->is_exportable ) + && ( memorycell->group() == null_handle ) ) { memorycell->export_as_text( ctrfile ); } } diff --git a/simulationstateserializer.h b/simulationstateserializer.h index 306d81b2..09938dc1 100644 --- a/simulationstateserializer.h +++ b/simulationstateserializer.h @@ -35,6 +35,8 @@ private: void deserialize_description( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_event( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_firstinit( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_group( cParser &Input, scene::scratch_data &Scratchpad ); + void deserialize_endgroup( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_light( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_node( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_origin( cParser &Input, scene::scratch_data &Scratchpad );