diff --git a/AnimModel.cpp b/AnimModel.cpp index 1f34f67a..e0f108a6 100644 --- a/AnimModel.cpp +++ b/AnimModel.cpp @@ -423,13 +423,6 @@ TAnimModel::~TAnimModel() SafeDelete(pRoot); } -bool TAnimModel::Init(TModel3d *pNewModel) -{ - fBlinkTimer = double(Random(1000 * fOffTime)) / (1000 * fOffTime); - pModel = pNewModel; - return (pModel != nullptr); -} - bool TAnimModel::Init(std::string const &asName, std::string const &asReplacableTexture) { if( asReplacableTexture.substr( 0, 1 ) == "*" ) { @@ -445,17 +438,21 @@ bool TAnimModel::Init(std::string const &asName, std::string const &asReplacable m_materialdata.textures_alpha = 0x31310031; } else{ - // tekstura nieprzezroczysta - nie renderować w cyklu + // tekstura nieprzezroczysta - nie renderować w cyklu przezroczystych m_materialdata.textures_alpha = 0x30300030; } - // przezroczystych - return (Init(TModelsManager::GetModel(asName))); + + fBlinkTimer = double( Random( 1000 * fOffTime ) ) / ( 1000 * fOffTime ); + + pModel = TModelsManager::GetModel( asName ); + return ( pModel != nullptr ); } bool TAnimModel::Load(cParser *parser, bool ter) { // rozpoznanie wpisu modelu i ustawienie świateł std::string name = parser->getToken(); - std::string texture = parser->getToken(false); // tekstura (zmienia na małe) + std::string texture = parser->getToken(); // tekstura (zmienia na małe) + replace_slashes( name ); if (!Init( name, texture )) { if (name != "notload") @@ -504,7 +501,7 @@ bool TAnimModel::Load(cParser *parser, bool ter) while( ( token != "" ) && ( token != "endmodel" ) ) { - LightSet( i, std::stod( token ) ); // stan światła jest liczbą z ułamkiem + LightSet( i, std::stof( token ) ); // stan światła jest liczbą z ułamkiem ++i; token = ""; @@ -563,15 +560,6 @@ void TAnimModel::RaAnimate( unsigned int const Framestamp ) { m_framestamp = Framestamp; }; -// calculates piece's bounding radius -void -TAnimModel::radius_() { - - if( pModel != nullptr ) { - m_area.radius = pModel->bounding_radius(); - } -} - void TAnimModel::RaPrepare() { // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza bool state; // stan światła @@ -584,7 +572,11 @@ void TAnimModel::RaPrepare() state = ( fBlinkTimer < fOnTime ); break; case ls_Dark: // zapalone, gdy ciemno - state = ( Global.fLuminance <= ( lsLights[i] - 3.0 ) ); + state = ( + Global.fLuminance <= ( + lsLights[i] == 3.f ? + DefaultDarkThresholdLevel : + ( lsLights[i] - 3.f ) ) ); break; default: // zapalony albo zgaszony state = (lightmode == ls_On); @@ -780,15 +772,17 @@ void TAnimModel::LightSet(int const n, float const v) } case ls_Dark: { // zapalenie świateł zależne od oświetlenia scenerii - if( v == 3.0 ) { +/* + if( v == 3.f ) { // standardowy próg zaplania - lsLights[ n ] = 3.0 + DefaultDarkThresholdLevel; + lsLights[ n ] = 3.f + DefaultDarkThresholdLevel; } +*/ break; } } }; -//--------------------------------------------------------------------------- + void TAnimModel::AnimUpdate(double dt) { // wykonanie zakolejkowanych animacji, nawet gdy modele nie są aktualnie wyświetlane TAnimContainer *p = TAnimModel::acAnimList; @@ -798,4 +792,72 @@ void TAnimModel::AnimUpdate(double dt) p = p->acAnimNext; // na razie bez usuwania z listy, bo głównie obrotnica na nią wchodzi } }; + +// radius() subclass details, calculates node's bounding radius +float +TAnimModel::radius_() { + + return ( + pModel ? + pModel->bounding_radius() : + 0.f ); +} + +// serialize() subclass details, sends content of the subclass to provided stream +void +TAnimModel::serialize_( std::ostream &Output ) const { + + // TODO: implement +} +// deserialize() subclass details, restores content of the subclass from provided stream +void +TAnimModel::deserialize_( std::istream &Input ) { + + // TODO: implement +} + +// export() subclass details, sends basic content of the class in legacy (text) format to provided stream +void +TAnimModel::export_as_text_( std::ostream &Output ) const { + // header + Output << "model "; + // location and rotation + Output + << location().x << ' ' + << location().y << ' ' + << location().z << ' ' + << vAngle.y << ' '; + // 3d shape + auto modelfile { ( + pModel ? + pModel->NameGet() + ".t3d" : // rainsted requires model file names to include an extension + "none" ) }; + if( modelfile.find( szModelPath ) == 0 ) { + // don't include 'models/' in the path + modelfile.erase( 0, std::string{ szModelPath }.size() ); + } + Output << modelfile << ' '; + // texture + auto texturefile { ( + m_materialdata.replacable_skins[ 1 ] != null_handle ? + GfxRenderer.Material( m_materialdata.replacable_skins[ 1 ] ).name : + "none" ) }; + if( texturefile.find( szTexturePath ) == 0 ) { + // don't include 'textures/' in the path + texturefile.erase( 0, std::string{ szTexturePath }.size() ); + } + Output << texturefile << ' '; + // light submodels activation configuration + if( iNumLights > 0 ) { + Output << "lights "; + for( int lightidx = 0; lightidx < iNumLights; ++lightidx ) { + Output << lsLights[ lightidx ] << ' '; + } + } + // footer + Output + << "endmodel" + << "\n"; +} + //--------------------------------------------------------------------------- diff --git a/AnimModel.h b/AnimModel.h index 7cdb5248..de4124ae 100644 --- a/AnimModel.h +++ b/AnimModel.h @@ -122,7 +122,7 @@ class TAnimAdvanced }; // opakowanie modelu, określające stan egzemplarza -class TAnimModel : public editor::basic_node { +class TAnimModel : public scene::basic_node { friend class opengl_renderer; @@ -133,15 +133,12 @@ public: ~TAnimModel(); // methods static void AnimUpdate( double dt ); - bool Init(TModel3d *pNewModel); bool Init(std::string const &asName, std::string const &asReplacableTexture); bool Load(cParser *parser, bool ter = false); TAnimContainer * AddContainer(std::string const &Name); TAnimContainer * GetContainer(std::string const &Name = ""); void RaAnglesSet( glm::vec3 Angles ) { - vAngle.x = Angles.x; - vAngle.y = Angles.y; - vAngle.z = Angles.z; }; + vAngle = { Angles }; }; void LightSet( int const n, float const v ); void AnimationVND( void *pData, double a, double b, double c, double d ); bool TerrainLoaded(); @@ -159,16 +156,20 @@ public: // members static TAnimContainer *acAnimList; // lista animacji z eventem, które muszą być przeliczane również bez wyświetlania -protected: - // calculates piece's bounding radius - void - radius_(); - private: // methods void RaPrepare(); // ustawienie animacji egzemplarza na wzorcu void RaAnimate( unsigned int const Framestamp ); // przeliczenie animacji egzemplarza void Advanced(); + // radius() subclass details, calculates node's bounding radius + float radius_(); + // serialize() subclass details, sends content of the subclass to provided stream + void serialize_( std::ostream &Output ) const; + // deserialize() subclass details, restores content of the subclass from provided stream + 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; + // members TAnimContainer *pRoot { nullptr }; // pojemniki sterujące, tylko dla aniomowanych submodeli TModel3d *pModel { nullptr }; diff --git a/EvLaunch.cpp b/EvLaunch.cpp index 30700599..999925d2 100644 --- a/EvLaunch.cpp +++ b/EvLaunch.cpp @@ -194,11 +194,90 @@ bool TEventLauncher::IsGlobal() const { && ( dRadius < 0.0 ) ); // bez ograniczenia zasięgu } -// calculates node's bounding radius -void +// radius() subclass details, calculates node's bounding radius +float TEventLauncher::radius_() { - m_area.radius = std::sqrt( dRadius ); + return std::sqrt( dRadius ); +} + +// serialize() subclass details, sends content of the subclass to provided stream +void +TEventLauncher::serialize_( std::ostream &Output ) const { + + // TODO: implement +} +// deserialize() subclass details, restores content of the subclass from provided stream +void +TEventLauncher::deserialize_( std::istream &Input ) { + + // TODO: implement +} + +// export() subclass details, sends basic content of the class in legacy (text) format to provided stream +void +TEventLauncher::export_as_text_( std::ostream &Output ) const { + // header + Output << "eventlauncher "; + // location + Output + << location().x << ' ' + << location().y << ' ' + << location().z << ' '; + // activation radius + Output + << ( dRadius > 0 ? std::sqrt( dRadius ) : dRadius ) << ' '; + // activation key + if( iKey != 0 ) { + auto const key { iKey & 0xff }; + auto const modifier { ( iKey & 0xff00 ) >> 8 }; + if( ( key >= GLFW_KEY_A ) && ( key <= GLFW_KEY_Z ) ) { + Output << static_cast(( 'A' + key - GLFW_KEY_A + ( ( modifier & 1 ) == 0 ? 32 : 0 ) )) << ' '; + } + else if( ( key >= GLFW_KEY_0 ) && ( key <= GLFW_KEY_9 ) ) { + Output << static_cast(( '0' + key - GLFW_KEY_0 )) << ' '; + } + } + else { + Output << "none "; + } + // activation interval or hour + if( DeltaTime != 0 ) { + // cyclical launcher + Output << -DeltaTime << ' '; + } + else { + // single activation at specified time + if( ( iHour < 0 ) + && ( iMinute < 0 ) ) { + Output << DeltaTime << ' '; + } + else { + // NOTE: activation hour might be affected by user-requested time offset + auto const timeoffset{ static_cast( Global.ScenarioTimeOffset * 60 ) }; + auto const adjustedtime{ clamp_circular( iHour * 60 + iMinute - timeoffset, 24 * 60 ) }; + Output + << ( adjustedtime / 60 ) % 24 + << ( adjustedtime % 60 ) + << ' '; + } + } + // associated event(s) + Output << ( asEvent1Name.empty() ? "none" : asEvent1Name ) << ' '; + Output << ( asEvent2Name.empty() ? "none" : asEvent2Name ) << ' '; + if( false == asMemCellName.empty() ) { + // conditional event + Output + << "condition " + << asMemCellName << ' ' + << szText << ' ' + << ( ( iCheckMask & conditional_memval1 ) != 0 ? to_string( fVal1 ) : "*" ) << ' ' + << ( ( iCheckMask & conditional_memval2 ) != 0 ? to_string( fVal2 ) : "*" ) << ' '; + } + // footer + Output + << "end" + << "\n"; } //--------------------------------------------------------------------------- diff --git a/EvLaunch.h b/EvLaunch.h index b10bed2d..83487d50 100644 --- a/EvLaunch.h +++ b/EvLaunch.h @@ -14,7 +14,7 @@ http://mozilla.org/MPL/2.0/. #include "Classes.h" #include "scenenode.h" -class TEventLauncher : public editor::basic_node { +class TEventLauncher : public scene::basic_node { public: // constructor @@ -37,12 +37,17 @@ public: int iCheckMask { 0 }; double dRadius { 0.0 }; -protected: - // calculates node's bounding radius - void - radius_(); - private: +// methods + // radius() subclass details, calculates node's bounding radius + float radius_(); + // serialize() subclass details, sends content of the subclass to provided stream + void serialize_( std::ostream &Output ) const; + // deserialize() subclass details, restores content of the subclass from provided stream + 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; + // members int iKey { 0 }; double DeltaTime { -1.0 }; diff --git a/Event.cpp b/Event.cpp index 45155d95..761c95f6 100644 --- a/Event.cpp +++ b/Event.cpp @@ -304,9 +304,9 @@ void TEvent::Load(cParser *parser, Math3D::vector3 const &org) if (token.find('#') != std::string::npos) token.erase(token.find('#')); // obcięcie unikatowości win1250_to_ascii( token ); // get rid of non-ascii chars - bEnabled = false; // nie do kolejki (dla SetVelocity też, ale jak jest do toru - // dowiązany) Params[6].asCommand = cm_PassengerStopPoint; + // nie do kolejki (dla SetVelocity też, ale jak jest do toru dowiązany) + bEnabled = false; } else if (token == "SetVelocity") { @@ -328,11 +328,6 @@ void TEvent::Load(cParser *parser, Math3D::vector3 const &org) bEnabled = false; Params[6].asCommand = cm_ShuntVelocity; } - //else if (str == "SetProximityVelocity") - //{ - // bEnabled = false; - // Params[6].asCommand = cm_SetProximityVelocity; - //} else if (token == "OutsideStation") { bEnabled = false; // ma być skanowny, aby AI nie przekraczało W5 @@ -391,6 +386,11 @@ void TEvent::Load(cParser *parser, Math3D::vector3 const &org) } } } while( token.compare( "endevent" ) != 0 ); + while( paramidx < 8 ) { + // HACK: mark unspecified lights with magic value + Params[ paramidx ].asdouble = -2.0; + ++paramidx; + } break; } case tp_Visible: // zmiana wyświetlania obiektu @@ -403,7 +403,7 @@ void TEvent::Load(cParser *parser, Math3D::vector3 const &org) case tp_Velocity: parser->getTokens(); *parser >> token; - Params[0].asdouble = atof(token.c_str()) * 0.28; + Params[0].asdouble = atof(token.c_str()) * ( 1000.0 / 3600.0 ); parser->getTokens(); *parser >> token; break; @@ -420,7 +420,6 @@ void TEvent::Load(cParser *parser, Math3D::vector3 const &org) *parser >> token; break; case tp_Exit: - asNodeName = ExchangeCharInString( asNodeName, '_', ' ' ); parser->getTokens(); *parser >> token; break; @@ -605,6 +604,191 @@ void TEvent::Load(cParser *parser, Math3D::vector3 const &org) } }; +// sends basic content of the class in legacy (text) format to provided stream +void +TEvent::export_as_text( std::ostream &Output ) const { + + if( Type == tp_Unknown ) { return; } + + // header + Output << "event "; + // name + Output << asName << ' '; + // type + std::vector const types { + "unknown", "sound", "exit", "disable", "velocity", "animation", "lights", + "updatevalues", "getvalues", "putvalues", "switch", "dynvel", "trackvel", + "multiple", "addvalues", "copyvalues", "whois", "logvalues", "visible", + "voltage", "message", "friction" }; + Output << types[ Type ] << ' '; + // delay + Output << fDelay << ' '; + // associated node + Output << ( asNodeName.empty() ? "none" : asNodeName ) << ' '; + // type-specific attributes + switch( Type ) { + case tp_AddValues: + case tp_UpdateValues: { + Output + << ( ( iFlags & update_memstring ) == 0 ? "*" : Params[ 0 ].asText ) << ' ' + << ( ( iFlags & update_memval1 ) == 0 ? "*" : to_string( Params[ 1 ].asdouble ) ) << ' ' + << ( ( iFlags & update_memval2 ) == 0 ? "*" : to_string( Params[ 2 ].asdouble ) ) << ' '; + break; + } + case tp_CopyValues: { + // NOTE: there's no way to get the original parameter value if it doesn't point to existing memcell + Output + << ( Params[ 9 ].asMemCell == nullptr ? "none" : Params[ 9 ].asMemCell->name() ) << ' ' + << ( iFlags & ( update_memstring | update_memval1 | update_memval2 ) ) << ' '; + break; + } + case tp_PutValues: { + Output + // location + << Params[ 3 ].asdouble << ' ' + << Params[ 4 ].asdouble << ' ' + << Params[ 5 ].asdouble << ' ' + // command + << Params[ 0 ].asText << ' ' + << Params[ 1 ].asdouble << ' ' + << Params[ 2 ].asdouble << ' '; + break; + } + case tp_Multiple: { + // NOTE: conditional_anyelse won't work when event cap is removed + bool hasconditionalelse { false }; + for( auto eventidx = 0; eventidx < 8; ++eventidx ) { + if( Params[ eventidx ].asEvent == nullptr ) { continue; } + if( ( iFlags & ( conditional_else << eventidx ) ) == 0 ) { + Output << Params[ eventidx ].asEvent->asName << ' '; + } + else { + // this event is executed as part of the 'else' block + hasconditionalelse = true; + } + } + // optional 'else' block + if( true == hasconditionalelse ) { + Output << "else "; + for( auto eventidx = 0; eventidx < 8; ++eventidx ) { + if( Params[ eventidx ].asEvent == nullptr ) { continue; } + if( ( iFlags & ( conditional_else << eventidx ) ) != 0 ) { + Output << Params[ eventidx ].asEvent->asName << ' '; + } + } + } + break; + } + case tp_Visible: { + Output << Params[ 0 ].asInt << ' '; + break; + } + case tp_Switch: { + Output << Params[ 0 ].asInt << ' '; + if( ( Params[ 1 ].asdouble != -1.0 ) + || ( Params[ 2 ].asdouble != -1.0 ) ) { + Output << Params[ 1 ].asdouble << ' '; + } + if( Params[ 2 ].asdouble != -1.0 ) { + Output << Params[ 2 ].asdouble << ' '; + } + break; + } + case tp_Lights: { + auto lightidx { 0 }; + while( ( lightidx < iMaxNumLights ) + && ( Params[ lightidx ].asdouble > -2.0 ) ) { + Output << Params[ lightidx ].asdouble << ' '; + ++lightidx; + } + break; + } + case tp_Animation: { + // animation type + Output << ( + Params[ 0 ].asInt == 1 ? "rotate" : + Params[ 0 ].asInt == 2 ? "translate" : + // NOTE: .vmd animation doesn't preserve file name, can't be exported. TODO: fix this + Params[ 0 ].asInt == 8 ? "digital" : + "none" ) + << ' '; + // submodel + Output << ( + Params[ 9 ].asAnimContainer != nullptr ? + Params[ 9 ].asAnimContainer->NameGet() : + "none" ) << ' '; + // animation parameters + Output + << Params[ 1 ].asdouble << ' ' + << Params[ 2 ].asdouble << ' ' + << Params[ 3 ].asdouble << ' ' + << Params[ 4 ].asdouble << ' '; + break; + } + case tp_Sound: { + // playback mode + Output << Params[ 0 ].asInt << ' '; + // optional radio channel + if( Params[ 1 ].asdouble > 0.0 ) { + Output << Params[ 1 ].asdouble << ' '; + } + break; + } + case tp_DynVel: + case tp_TrackVel: + case tp_Voltage: + case tp_Friction: { + Output << Params[ 0 ].asdouble << ' '; + break; + } + case tp_Velocity: { + Output << Params[ 0 ].asdouble * ( 3600.0 / 1000.0 ) << ' '; + break; + } + case tp_WhoIs: { + Output + << ( iFlags & ( update_memstring | update_memval1 | update_memval2 ) ) << ' '; + break; + } + default: { + break; + } + } + // optional conditions + // NOTE: for flexibility condition check and export is performed for all event types rather than only for these which support it currently + auto const isconditional { ( conditional_trackoccupied | conditional_trackfree | conditional_propability | conditional_memcompare ) }; + if( ( ( iFlags & isconditional ) != 0 ) ) { + Output << "condition "; + if( ( iFlags & conditional_trackoccupied ) != 0 ) { + Output << "trackoccupied "; + } + else if( ( iFlags & conditional_trackfree ) != 0 ) { + Output << "trackfree "; + } + else if( ( iFlags & conditional_propability ) != 0 ) { + Output + << "propability " + << Params[ 10 ].asdouble << ' '; + } + else if( ( iFlags & conditional_memcompare ) != 0 ) { + Output + << "memcompare " + << ( ( iFlags & conditional_memstring ) == 0 ? "*" : Params[ 10 ].asText ) << ' ' + << ( ( iFlags & conditional_memval1 ) == 0 ? "*" : to_string( Params[ 11 ].asdouble ) ) << ' ' + << ( ( iFlags & conditional_memval2 ) == 0 ? "*" : to_string( Params[ 12 ].asdouble ) ) << ' '; + } + } + if( fRandomDelay != 0.0 ) { + Output + << "randomdelay " + << fRandomDelay << ' '; + } + // footer + Output + << "endevent" + << "\n"; +} + void TEvent::AddToQuery( TEvent *Event, TEvent *&Start ) { TEvent *target( Start ); @@ -1002,6 +1186,10 @@ event_manager::CheckQuery() { case tp_Lights: { if( m_workevent->Params[ 9 ].asModel ) { for( i = 0; i < iMaxNumLights; ++i ) { + if( m_workevent->Params[ i ].asdouble == -2.0 ) { + // processed all supplied values, bail out + break; + } if( m_workevent->Params[ i ].asdouble >= 0 ) { // -1 zostawia bez zmiany m_workevent->Params[ 9 ].asModel->LightSet( @@ -1013,8 +1201,8 @@ event_manager::CheckQuery() { break; } case tp_Visible: { - if( m_workevent->Params[ 9 ].asEditorNode ) - m_workevent->Params[ 9 ].asEditorNode->visible( m_workevent->Params[ 0 ].asInt > 0 ); + if( m_workevent->Params[ 9 ].asSceneNode ) + m_workevent->Params[ 9 ].asSceneNode->visible( m_workevent->Params[ 0 ].asInt > 0 ); break; } case tp_Velocity: { @@ -1022,8 +1210,12 @@ event_manager::CheckQuery() { break; } case tp_Exit: { - MessageBox( 0, m_workevent->asNodeName.c_str(), " THE END ", MB_OK ); - Global.iTextMode = -1; // wyłączenie takie samo jak sekwencja F10 -> Y + // wyłączenie takie samo jak sekwencja F10 -> Y + MessageBox( + 0, + ExchangeCharInString( m_workevent->asNodeName, '_', ' ' ).c_str(), + " THE END ", + MB_OK ); return false; } case tp_Sound: { @@ -1404,7 +1596,6 @@ event_manager::InitEvents() { else { ErrorLog( "Bad event: animation event \"" + event->asName + "\" cannot find model instance \"" + event->asNodeName + "\"" ); } - event->asNodeName = ""; break; } case tp_Lights: { @@ -1414,12 +1605,11 @@ event_manager::InitEvents() { event->Params[ 9 ].asModel = instance; else ErrorLog( "Bad event: lights event \"" + event->asName + "\" cannot find model instance \"" + event->asNodeName + "\"" ); - event->asNodeName = ""; break; } case tp_Visible: { // ukrycie albo przywrócenie obiektu - editor::basic_node *node = simulation::Instances.find( event->asNodeName ); // najpierw model + scene::basic_node *node = simulation::Instances.find( event->asNodeName ); // najpierw model if( node == nullptr ) { // albo tory? node = simulation::Paths.find( event->asNodeName ); @@ -1429,12 +1619,11 @@ event_manager::InitEvents() { node = simulation::Traction.find( event->asNodeName ); } if( node != nullptr ) - event->Params[ 9 ].asEditorNode = node; + event->Params[ 9 ].asSceneNode = node; else { event->m_ignored = true; ErrorLog( "Bad event: visibility event \"" + event->asName + "\" cannot find item \"" + event->asNodeName + "\"" ); } - event->asNodeName = ""; break; } case tp_Switch: { @@ -1460,7 +1649,6 @@ event_manager::InitEvents() { else { ErrorLog( "Bad event: switch event \"" + event->asName + "\" cannot find track \"" + event->asNodeName + "\"" ); } - event->asNodeName = ""; break; } case tp_Sound: { @@ -1470,7 +1658,6 @@ event_manager::InitEvents() { event->Params[ 9 ].tsTextSound = sound; else ErrorLog( "Bad event: sound event \"" + event->asName + "\" cannot find static sound \"" + event->asNodeName + "\"" ); - event->asNodeName = ""; break; } case tp_TrackVel: { @@ -1486,7 +1673,6 @@ event_manager::InitEvents() { ErrorLog( "Bad event: track velocity event \"" + event->asName + "\" cannot find track \"" + event->asNodeName + "\"" ); } } - event->asNodeName = ""; break; } case tp_DynVel: { @@ -1500,7 +1686,6 @@ event_manager::InitEvents() { else ErrorLog( "Bad event: vehicle velocity event \"" + event->asName + "\" cannot find vehicle \"" + event->asNodeName + "\"" ); } - event->asNodeName = ""; break; } case tp_Multiple: { @@ -1548,7 +1733,6 @@ event_manager::InitEvents() { else ErrorLog( "Bad event: voltage event \"" + event->asName + "\" cannot find power source \"" + event->asNodeName + "\"" ); } - event->asNodeName = ""; break; } case tp_Message: { @@ -1597,6 +1781,20 @@ event_manager::InitLaunchers() { } } +// sends basic content of the class in legacy (text) format to provided stream +void +event_manager::export_as_text( std::ostream &Output ) const { + + Output << "// events\n"; + for( auto const *event : m_events ) { + event->export_as_text( Output ); + } + Output << "// event launchers\n"; + for( auto const *launcher : m_launchers.sequence() ) { + launcher->export_as_text( Output ); + } +} + // legacy method, verifies condition for specified event bool event_manager::EventConditon( TEvent *Event ) { diff --git a/Event.h b/Event.h index 27bad969..13d62043 100644 --- a/Event.h +++ b/Event.h @@ -17,7 +17,6 @@ http://mozilla.org/MPL/2.0/. enum TEventType { tp_Unknown, tp_Sound, - tp_SoundPos, tp_Exit, tp_Disable, tp_Velocity, @@ -31,7 +30,6 @@ enum TEventType { tp_TrackVel, tp_Multiple, tp_AddValues, -// tp_Ignored, // NOTE: refactored to separate flag tp_CopyValues, tp_WhoIs, tp_LogValues, @@ -61,7 +59,7 @@ union TParam { void *asPointer; TMemCell *asMemCell; - editor::basic_node *asEditorNode; + scene::basic_node *asSceneNode; glm::dvec3 const *asLocation; TTrack *asTrack; TAnimModel *asModel; @@ -103,6 +101,9 @@ public: TEvent(std::string const &m = ""); ~TEvent(); void Load(cParser *parser, Math3D::vector3 const &org); + // sends basic content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; static void AddToQuery( TEvent *Event, TEvent *&Start ); std::string CommandGet(); TCommandType Command(); @@ -151,6 +152,9 @@ public: // legacy method, initializes event launchers after deserialization from scenario file void InitLaunchers(); + // sends basic content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; private: // types diff --git a/MemCell.cpp b/MemCell.cpp index 7e8b379f..12128e24 100644 --- a/MemCell.cpp +++ b/MemCell.cpp @@ -164,6 +164,41 @@ void TMemCell::AssignEvents(TEvent *e) OnSent = e; }; +// serialize() subclass details, sends content of the subclass to provided stream +void +TMemCell::serialize_( std::ostream &Output ) const { + + // TODO: implement +} +// deserialize() subclass details, restores content of the subclass from provided stream +void +TMemCell::deserialize_( std::istream &Input ) { + + // TODO: implement +} + +// export() subclass details, sends basic content of the class in legacy (text) format to provided stream +void +TMemCell::export_as_text_( std::ostream &Output ) const { + // header + Output << "memcell "; + // location + Output + << location().x << ' ' + << location().y << ' ' + << location().z << ' ' + // cell data + << szText << ' ' + << fValue1 << ' ' + << fValue2 << ' ' + // associated track + << ( asTrackName.empty() ? "none" : asTrackName ) << ' ' + // footer + << "endmemcell" + << "\n"; +} + + // legacy method, initializes traction after deserialization from scenario file diff --git a/MemCell.h b/MemCell.h index adbbcbfd..de54860f 100644 --- a/MemCell.h +++ b/MemCell.h @@ -13,10 +13,11 @@ http://mozilla.org/MPL/2.0/. #include "scenenode.h" #include "names.h" -class TMemCell : public editor::basic_node { +class TMemCell : public scene::basic_node { public: std::string asTrackName; // McZapkie-100302 - zeby nazwe toru na ktory jest Putcommand wysylane pamietac + bool is_exportable { true }; // export helper; autogenerated cells don't need to be exported explicit TMemCell( scene::node_data const &Nodedata ); @@ -49,6 +50,15 @@ public: void AssignEvents(TEvent *e); private: +// methods + // serialize() subclass details, sends content of the subclass to provided stream + void serialize_( std::ostream &Output ) const; + // deserialize() subclass details, restores content of the subclass from provided stream + 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; + +// members // content std::string szText; double fValue1 { 0.0 }; diff --git a/Names.h b/Names.h index 0a7c534d..e8cc93b0 100644 --- a/Names.h +++ b/Names.h @@ -60,5 +60,8 @@ public: type_sequence & sequence() { return m_items; } + type_sequence const & + sequence() const { + return m_items; } }; diff --git a/Segment.cpp b/Segment.cpp index fdd82ece..91999747 100644 --- a/Segment.cpp +++ b/Segment.cpp @@ -18,8 +18,36 @@ http://mozilla.org/MPL/2.0/. //--------------------------------------------------------------------------- -// 101206 Ra: trapezoidalne drogi -// 110806 Ra: odwrócone mapowanie wzdłuż - Point1 == 1.0 +// helper, restores content of a 3d vector from provided input stream +// TODO: review and clean up the helper routines, there's likely some redundant ones +Math3D::vector3 LoadPoint( cParser &Input ) { + // pobranie współrzędnych punktu + Input.getTokens( 3 ); + Math3D::vector3 point; + Input + >> point.x + >> point.y + >> point.z; + + return point; +} + + +void +segment_data::deserialize( cParser &Input, Math3D::vector3 const &Offset ) { + + points[ segment_data::point::start ] = LoadPoint( Input ) + Offset; + Input.getTokens(); + Input >> rolls[ 0 ]; + points[ segment_data::point::control1 ] = LoadPoint( Input ); + points[ segment_data::point::control2 ] = LoadPoint( Input ); + points[ segment_data::point::end ] = LoadPoint( Input ) + Offset; + Input.getTokens( 2 ); + Input + >> rolls[ 1 ] + >> radius; +} + TSegment::TSegment(TTrack *owner) : pOwner( owner ) @@ -62,6 +90,7 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M // poprawienie przechyłki fRoll1 = glm::radians(fNewRoll1); // Ra: przeliczone jest bardziej przydatne do obliczeń fRoll2 = glm::radians(fNewRoll2); + bCurve = bIsCurve; if (Global.bRollFix) { // Ra: poprawianie przechyłki // Przechyłka powinna być na środku wewnętrznej szyny, a standardowo jest w osi @@ -91,7 +120,6 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M // kąt w planie, żeby nie liczyć wielokrotnie // Ra: ten kąt jeszcze do przemyślenia jest fDirection = -std::atan2(Point2.x - Point1.x, Point2.z - Point1.z); - bCurve = bIsCurve; if (bCurve) { // przeliczenie współczynników wielomianu, będzie mniej mnożeń i można policzyć pochodne vC = 3.0 * (CPointOut - Point1); // t^1 @@ -99,9 +127,9 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M vA = Point2 - Point1 - vC - vB; // t^3 fLength = ComputeLength(); } - else - fLength = (Point1 - Point2).Length(); - fStep = fNewStep; + else { + fLength = ( Point1 - Point2 ).Length(); + } if (fLength <= 0) { ErrorLog( "Bad track: zero length spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); @@ -111,17 +139,25 @@ bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, M */ } - if( ( pOwner->eType == tt_Switch ) - && ( fStep * 3.0 > fLength ) ) { - // NOTE: a workaround for too short switches (less than 3 segments) messing up animation/generation of blades - fStep = fLength / 3.0; - } - fStoop = std::atan2((Point2.y - Point1.y), fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie - SafeDeleteArray(fTsBuffer); + fStep = fNewStep; + // NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches + if( ( pOwner->eType == tt_Switch ) + && ( fStep * ( 3.0 * Global.SplineFidelity ) > fLength ) ) { + // NOTE: a workaround for too short switches (less than 3 segments) messing up animation/generation of blades + fStep = fLength / ( 3.0 * Global.SplineFidelity ); + } iSegCount = static_cast( std::ceil( fLength / fStep ) ); // potrzebne do VBO +/* + iSegCount = ( + pOwner->eType == tt_Switch ? + 6 * Global.SplineFidelity : + static_cast( std::ceil( fLength / fStep ) ) ); // potrzebne do VBO +*/ fStep = fLength / iSegCount; // update step to equalize size of individual pieces + + SafeDeleteArray( fTsBuffer ); fTsBuffer = new double[ iSegCount + 1 ]; fTsBuffer[ 0 ] = 0.0; for( int i = 1; i < iSegCount; ++i ) { diff --git a/Segment.h b/Segment.h index 4b138fe4..c2adc3a6 100644 --- a/Segment.h +++ b/Segment.h @@ -14,6 +14,24 @@ http://mozilla.org/MPL/2.0/. #include "openglgeometrybank.h" #include "utilities.h" +struct segment_data { +// types + enum point { + start = 0, + control1, + control2, + end + }; +// members + std::array points {}; + std::array rolls {}; + float radius {}; +// constructors + segment_data() = default; +// methods + void deserialize( cParser &Input, Math3D::vector3 const &Offset ); +}; + class TSegment { // aproksymacja toru (zwrotnica ma dwa takie, jeden z nich jest aktywny) private: diff --git a/Track.cpp b/Track.cpp index 596f3116..29fc9b30 100644 --- a/Track.cpp +++ b/Track.cpp @@ -343,14 +343,6 @@ void TTrack::ConnectNextNext(TTrack *pTrack, int typ) } } -Math3D::vector3 LoadPoint(cParser *parser) -{ // pobranie współrzędnych punktu - Math3D::vector3 p; - parser->getTokens(3); - *parser >> p.x >> p.y >> p.z; - return p; -} - void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) { // pobranie obiektu trajektorii ruchu Math3D::vector3 pt, vec, p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań @@ -473,20 +465,44 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) WriteLog("unvis"); Init(); // ustawia SwitchExtension double segsize = 5.0; // długość odcinka segmentowania - switch (eType) - { // Ra: łuki segmentowane co 5m albo 314-kątem foremnym - case tt_Table: // obrotnica jest prawie jak zwykły tor + + // path data + // all subtypes contain at least one path + m_paths.emplace_back(); + m_paths.back().deserialize( *parser, pOrigin ); + switch( eType ) { + case tt_Switch: + case tt_Cross: + case tt_Tributary: { + // these subtypes contain additional path + m_paths.emplace_back(); + m_paths.back().deserialize( *parser, pOrigin ); + break; + } + default: { + break; + } + } + + switch (eType) { + // Ra: łuki segmentowane co 5m albo 314-kątem foremnym + case tt_Table: { + // obrotnica jest prawie jak zwykły tor iAction |= 2; // flaga zmiany położenia typu obrotnica - case tt_Normal: - p1 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P1 - parser->getTokens(); - *parser >> r1; // pobranie przechyłki w P1 - cp1 = LoadPoint(parser); // pobranie współrzędnych punktów kontrolnych - cp2 = LoadPoint(parser); - p2 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P2 - parser->getTokens(2); - *parser >> r2 >> fRadius; // pobranie przechyłki w P1 i promienia - fRadius = fabs(fRadius); // we wpisie może być ujemny + } + case tt_Normal: { + // pobranie współrzędnych P1 + auto const &path { m_paths[ 0 ] }; + p1 = path.points[ segment_data::point::start ]; + // pobranie współrzędnych punktów kontrolnych + cp1 = path.points[ segment_data::point::control1 ]; + cp2 = path.points[ segment_data::point::control2 ]; + // pobranie współrzędnych P2 + p2 = path.points[ segment_data::point::end ]; + r1 = path.rolls[ 0 ]; + r2 = path.rolls[ 1 ]; + fRadius = std::abs( path.radius ); // we wpisie może być ujemny + if (iCategoryFlag & 1) { // zero na główce szyny p1.y += 0.18; @@ -502,7 +518,11 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) if( fRadius != 0 ) { // gdy podany promień - segsize = clamp( std::abs( fRadius ) * ( 0.02 / Global.SplineFidelity ), 2.0 / Global.SplineFidelity, 10.0 ); + segsize = + clamp( + std::abs( fRadius ) * ( 0.02 / Global.SplineFidelity ), + 2.0 / Global.SplineFidelity, + 10.0 / Global.SplineFidelity ); } else { // HACK: crude check whether claimed straight is an actual straight piece @@ -512,7 +532,11 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) } else { // HACK: divide roughly in 10 segments. - segsize = clamp( ( p1 - p2 ).Length() * ( 0.1 / Global.SplineFidelity ), 2.0 / Global.SplineFidelity, 10.0 ); + segsize = + clamp( + ( p1 - p2 ).Length() * 0.1, + 2.0 / Global.SplineFidelity, + 10.0 / Global.SplineFidelity ); } } @@ -529,6 +553,7 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) if ((r1 != 0) || (r2 != 0)) iTrapezoid = 1; // są przechyłki do uwzględniania w rysowaniu + if (eType == tt_Table) // obrotnica ma doklejkę { // SwitchExtension=new TSwitchExtension(this,1); //dodatkowe zmienne dla obrotnicy SwitchExtension->Segments[0]->Init(p1, p2, segsize); // kopia oryginalnego toru @@ -549,56 +574,92 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) fTexRatio2 = w / h; // proporcja boków } break; - - case tt_Cross: // skrzyżowanie dróg - 4 punkty z wektorami kontrolnymi - segsize = 1.0; // specjalne segmentowanie ze względu na małe promienie + } + case tt_Cross: { + // skrzyżowanie dróg - 4 punkty z wektorami kontrolnymi +// segsize = 1.0; // specjalne segmentowanie ze względu na małe promienie + } case tt_Tributary: // dopływ - case tt_Switch: // zwrotnica + case tt_Switch: { // zwrotnica iAction |= 1; // flaga zmiany położenia typu zwrotnica lub skrzyżowanie dróg // problemy z animacją iglic powstaje, gdzy odcinek prosty ma zmienną przechyłkę // wtedy dzieli się na dodatkowe odcinki (po 0.2m, bo R=0) i animację diabli biorą // Ra: na razie nie podejmuję się przerabiania iglic // SwitchExtension=new TSwitchExtension(this,eType==tt_Cross?6:2); //zwrotnica ma doklejkę + auto const &path { m_paths[ 0 ] }; + p1 = path.points[ segment_data::point::start ]; + // pobranie współrzędnych punktów kontrolnych + cp1 = path.points[ segment_data::point::control1 ]; + cp2 = path.points[ segment_data::point::control2 ]; + // pobranie współrzędnych P2 + p2 = path.points[ segment_data::point::end ]; + r1 = path.rolls[ 0 ]; + r2 = path.rolls[ 1 ]; + fRadiusTable[0] = std::abs( path.radius ); // we wpisie może być ujemny - p1 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P1 - parser->getTokens(); - *parser >> r1; - cp1 = LoadPoint(parser); - cp2 = LoadPoint(parser); - p2 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P2 - parser->getTokens(2); - *parser >> r2 >> fRadiusTable[0]; - fRadiusTable[0] = fabs(fRadiusTable[0]); // we wpisie może być ujemny if (iCategoryFlag & 1) { // zero na główce szyny p1.y += 0.18; p2.y += 0.18; // na przechyłce doliczyć jeszcze pół przechyłki? } - if (fRadiusTable[0] > 0) - segsize = clamp( 0.2 + fRadiusTable[0] * 0.02, 2.0, 5.0 ); - else if (eType != tt_Cross) // dla skrzyżowań muszą być podane kontrolne - { // jak promień zerowy, to przeliczamy punkty kontrolne - cp1 = (p1 + p1 + p2) / 3.0 - p1; // jak jest prosty, to się zoptymalizuje - cp2 = (p1 + p2 + p2) / 3.0 - p2; - segsize = 5.0; - } // ułomny prosty - if (!(cp1 == Math3D::vector3(0, 0, 0)) && !(cp2 == Math3D::vector3(0, 0, 0))) - SwitchExtension->Segments[0]->Init(p1, p1 + cp1, p2 + cp2, p2, segsize, r1, r2); - else - SwitchExtension->Segments[0]->Init(p1, p2, segsize, r1, r2); + if( eType != tt_Cross ) { + // dla skrzyżowań muszą być podane kontrolne + if( ( ( ( p1 + p1 + p2 ) / 3.0 - p1 - cp1 ).Length() < 0.02 ) + || ( ( ( p1 + p2 + p2 ) / 3.0 - p2 + cp1 ).Length() < 0.02 ) ) { + // "prostowanie" prostych z kontrolnymi, dokładność 2cm + cp1 = cp2 = Math3D::vector3( 0, 0, 0 ); + } + } + + if( fRadiusTable[ 0 ] != 0 ) { + // gdy podany promień + segsize = + clamp( + std::abs( fRadiusTable[ 0 ] ) * ( 0.02 / Global.SplineFidelity ), + 2.0 / Global.SplineFidelity, + 10.0 / Global.SplineFidelity ); + } + else { + // HACK: crude check whether claimed straight is an actual straight piece + if( ( cp1 == Math3D::vector3() ) + && ( cp2 == Math3D::vector3() ) ) { + segsize = 10.0; // for straights, 10m per segment works good enough + } + else { + // HACK: divide roughly in 10 segments. + segsize = + clamp( + ( p1 - p2 ).Length() * 0.1, + 2.0 / Global.SplineFidelity, + 10.0 / Global.SplineFidelity ); + } + } + + if( ( cp1 == Math3D::vector3( 0, 0, 0 ) ) + && ( cp2 == Math3D::vector3( 0, 0, 0 ) ) ) { + // Ra: hm, czasem dla prostego są podane... + // gdy prosty, kontrolne wyliczane przy zmiennej przechyłce + SwitchExtension->Segments[ 0 ]->Init( p1, p2, segsize, r1, r2 ); + } + else { + // gdy łuk (ustawia bCurve=true) + SwitchExtension->Segments[ 0 ]->Init( p1, cp1 + p1, cp2 + p2, p2, segsize, r1, r2 ); + } + + auto const &path2 { m_paths[ 1 ] }; + p3 = path2.points[ segment_data::point::start ]; + // pobranie współrzędnych punktów kontrolnych + cp3 = path2.points[ segment_data::point::control1 ]; + cp4 = path2.points[ segment_data::point::control2 ]; + // pobranie współrzędnych P2 + p4 = path2.points[ segment_data::point::end ]; + r3 = path2.rolls[ 0 ]; + r4 = path2.rolls[ 1 ]; + fRadiusTable[1] = std::abs( path2.radius ); // we wpisie może być ujemny - p3 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P3 - parser->getTokens(); - *parser >> r3; - cp3 = LoadPoint(parser); - cp4 = LoadPoint(parser); - p4 = LoadPoint(parser) + pOrigin; // pobranie współrzędnych P4 - parser->getTokens(2); - *parser >> r4 >> fRadiusTable[1]; - fRadiusTable[1] = fabs(fRadiusTable[1]); // we wpisie może być ujemny if (iCategoryFlag & 1) { // zero na główce szyny p3.y += 0.18; @@ -606,25 +667,54 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) // na przechyłce doliczyć jeszcze pół przechyłki? } - if (fRadiusTable[1] > 0) - segsize = clamp( 0.2 + fRadiusTable[ 1 ] * 0.02, 2.0, 5.0 ); - - else if (eType != tt_Cross) // dla skrzyżowań muszą być podane kontrolne - { // jak promień zerowy, to przeliczamy punkty kontrolne - cp3 = (p3 + p3 + p4) / 3.0 - p3; // jak jest prosty, to się zoptymalizuje - cp4 = (p3 + p4 + p4) / 3.0 - p4; - segsize = 5.0; - } // ułomny prosty - - if (!(cp3 == Math3D::vector3(0, 0, 0)) && !(cp4 == Math3D::vector3(0, 0, 0))) - { // dla skrzyżowania dróg dać odwrotnie końce, żeby brzegi generować lewym - if (eType != tt_Cross) - SwitchExtension->Segments[1]->Init(p3, p3 + cp3, p4 + cp4, p4, segsize, r3, r4); - else - SwitchExtension->Segments[1]->Init(p4, p4 + cp4, p3 + cp3, p3, segsize, r4, r3); // odwrócony + if( eType != tt_Cross ) { + // dla skrzyżowań muszą być podane kontrolne + if( ( ( ( p3 + p3 + p4 ) / 3.0 - p3 - cp3 ).Length() < 0.02 ) + || ( ( ( p3 + p4 + p4 ) / 3.0 - p4 + cp3 ).Length() < 0.02 ) ) { + // "prostowanie" prostych z kontrolnymi, dokładność 2cm + cp3 = cp4 = Math3D::vector3( 0, 0, 0 ); + } + } + + if( fRadiusTable[ 1 ] != 0 ) { + // gdy podany promień + segsize = + clamp( + std::abs( fRadiusTable[ 1 ] ) * ( 0.02 / Global.SplineFidelity ), + 2.0 / Global.SplineFidelity, + 10.0 / Global.SplineFidelity ); + } + else { + // HACK: crude check whether claimed straight is an actual straight piece + if( ( cp3 == Math3D::vector3() ) + && ( cp4 == Math3D::vector3() ) ) { + segsize = 10.0; // for straights, 10m per segment works good enough + } + else { + // HACK: divide roughly in 10 segments. + segsize = + clamp( + ( p3 - p4 ).Length() * 0.1, + 2.0 / Global.SplineFidelity, + 10.0 / Global.SplineFidelity ); + } + } + + if( ( cp3 == Math3D::vector3( 0, 0, 0 ) ) + && ( cp4 == Math3D::vector3( 0, 0, 0 ) ) ) { + // Ra: hm, czasem dla prostego są podane... + // gdy prosty, kontrolne wyliczane przy zmiennej przechyłce + SwitchExtension->Segments[ 1 ]->Init( p3, p4, segsize, r3, r4 ); + } + else { + if( eType != tt_Cross ) { + SwitchExtension->Segments[ 1 ]->Init( p3, p3 + cp3, p4 + cp4, p4, segsize, r3, r4 ); + } + else { + // dla skrzyżowania dróg dać odwrotnie końce, żeby brzegi generować lewym + SwitchExtension->Segments[ 1 ]->Init( p4, p4 + cp4, p3 + cp3, p3, segsize, r4, r3 ); // odwrócony + } } - else - SwitchExtension->Segments[1]->Init(p3, p4, segsize, r3, r4); if (eType == tt_Cross) { // Ra 2014-07: dla skrzyżowań będą dodatkowe segmenty @@ -646,10 +736,10 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) { Math3D::vector3 v1, v2; double a1, a2; - v1 = SwitchExtension->Segments[0]->FastGetPoint_1() - - SwitchExtension->Segments[0]->FastGetPoint_0(); - v2 = SwitchExtension->Segments[1]->FastGetPoint_1() - - SwitchExtension->Segments[1]->FastGetPoint_0(); + v1 = SwitchExtension->Segments[0]->FastGetPoint_1() + - SwitchExtension->Segments[0]->FastGetPoint_0(); + v2 = SwitchExtension->Segments[1]->FastGetPoint_1() + - SwitchExtension->Segments[1]->FastGetPoint_0(); a1 = atan2(v1.x, v1.z); a2 = atan2(v2.x, v2.z); a2 = a2 - a1; @@ -660,7 +750,10 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) SwitchExtension->RightSwitch = a2 < 0; // lustrzany układ OXY... } break; + } } + + // optional attributes parser->getTokens(); *parser >> token; str = token; @@ -707,9 +800,9 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) parser->getTokens(); *parser >> fVelocity; //*0.28; McZapkie-010602 if (SwitchExtension) // jeśli tor ruchomy - if (std::fabs(fVelocity) >= 1.0) //żeby zero nie ograniczało dożywotnio - SwitchExtension->fVelocity = static_cast(fVelocity); // zapamiętanie głównego ograniczenia; a - // np. -40 ogranicza tylko na bok + if (std::abs(fVelocity) >= 1.0) //żeby zero nie ograniczało dożywotnio + // zapamiętanie głównego ograniczenia; a np. -40 ogranicza tylko na bok + SwitchExtension->fVelocity = static_cast(fVelocity); } else if (str == "isolated") { // obwód izolowany, do którego tor należy @@ -777,20 +870,25 @@ void TTrack::Load(cParser *parser, Math3D::vector3 pOrigin) } // TODO: refactor this mess -bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2) +bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2, bool const Explicit ) { bool bError = false; if( NewEvent0 == nullptr ) { if( false == asEvent0Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEvent0Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; + if( true == Explicit ) { + ErrorLog( "Bad event: event \"" + asEvent0Name + "\" assigned to track \"" + m_name + "\" does not exist" ); + } } } else { if( evEvent0 == nullptr ) { evEvent0 = NewEvent0; +/* + // preserve event names, we'll need them if we want the scenario export to be unaffected by missing events and such asEvent0Name = ""; +*/ iEvents |= 1; // sumaryczna informacja o eventach } else { @@ -801,14 +899,18 @@ bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent if( NewEvent1 == nullptr ) { if( false == asEvent1Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEvent1Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; + if( true == Explicit ) { + ErrorLog( "Bad event: event \"" + asEvent1Name + "\" assigned to track \"" + m_name + "\" does not exist" ); + } } } else { if( evEvent1 == nullptr ) { evEvent1 = NewEvent1; +/* asEvent1Name = ""; +*/ iEvents |= 2; // sumaryczna informacja o eventach } else { @@ -819,14 +921,18 @@ bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent if( NewEvent2 == nullptr ) { if( false == asEvent2Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEvent2Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; + if( true == Explicit ) { + ErrorLog( "Bad event: event \"" + asEvent2Name + "\" assigned to track \"" + m_name + "\" does not exist" ); + } } } else { if( evEvent2 == nullptr ) { evEvent2 = NewEvent2; +/* asEvent2Name = ""; +*/ iEvents |= 4; // sumaryczna informacja o eventach } else { @@ -838,20 +944,25 @@ bool TTrack::AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent return ( bError == false ); } -bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2) +bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2, bool const Explicit) { bool bError = false; if( NewEvent0 == nullptr ) { if( false == asEventall0Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEventall0Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; + if( true == Explicit ) { + ErrorLog( "Bad event: event \"" + asEventall0Name + "\" assigned to track \"" + m_name + "\" does not exist" ); + } } } else { if( evEventall0 == nullptr ) { evEventall0 = NewEvent0; +/* + // preserve event names, we'll need them if we want the scenario export to be unaffected by missing events and such asEventall0Name = ""; +*/ iEvents |= 8; // sumaryczna informacja o eventach } else { @@ -862,14 +973,18 @@ bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEv if( NewEvent1 == nullptr ) { if( false == asEventall1Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEventall1Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; + if( true == Explicit ) { + ErrorLog( "Bad event: event \"" + asEventall1Name + "\" assigned to track \"" + m_name + "\" does not exist" ); + } } } else { if( evEventall1 == nullptr ) { evEventall1 = NewEvent1; +/* asEventall1Name = ""; +*/ iEvents |= 16; // sumaryczna informacja o eventach } else { @@ -880,14 +995,18 @@ bool TTrack::AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEv if( NewEvent2 == nullptr ) { if( false == asEventall2Name.empty() ) { - ErrorLog( "Bad event: event \"" + asEventall2Name + "\" assigned to track \"" + m_name + "\" does not exist" ); bError = true; + if( true == Explicit ) { + ErrorLog( "Bad event: event \"" + asEventall2Name + "\" assigned to track \"" + m_name + "\" does not exist" ); + } } } else { if( evEventall2 == nullptr ) { evEventall2 = NewEvent2; +/* asEventall2Name = ""; +*/ iEvents |= 32; // sumaryczna informacja o eventach } else { @@ -1404,6 +1523,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { {szyna[ i ].texture.x, 0.f} }; } // TODO, TBD: change all track geometry to triangles, to allow packing data in less, larger buffers + auto const bladelength { 2 * Global.SplineFidelity }; if (SwitchExtension->RightSwitch) { // nowa wersja z SPKS, ale odwrotnie lewa/prawa gfx::vertex_array vertices; @@ -1412,11 +1532,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, 2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, bladelength ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // left blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, SwitchExtension->fOffset2 ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1425,11 +1545,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, 2 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, bladelength ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // right blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -fMaxOffset + SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -fMaxOffset + SwitchExtension->fOffset1 ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1442,11 +1562,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength ); // lewa szyna normalna cała Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, 2 ); // prawa szyna za iglicą + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength, 1.0, bladelength ); // prawa szyna za iglicą Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // right blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -SwitchExtension->fOffset2 ); Geometry1.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -1455,11 +1575,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts2, nnumPts, fTexLength ); // prawa szyna normalnie cała Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, 2 ); // lewa szyna za iglicą + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts1, nnumPts, fTexLength, 1.0, bladelength ); // lewa szyna za iglicą Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); // left blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, fMaxOffset - SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, fMaxOffset - SwitchExtension->fOffset1 ); Geometry2.emplace_back( GfxRenderer.Insert( vertices, Bank, GL_TRIANGLE_STRIP ) ); vertices.clear(); } @@ -2220,9 +2340,13 @@ bool TTrack::Switch(int i, float const t, float const d) iNextDirection = SwitchExtension->iNextDirection[i]; iPrevDirection = SwitchExtension->iPrevDirection[i]; fRadius = fRadiusTable[i]; // McZapkie: wybor promienia toru - if (SwitchExtension->fVelocity <= - -2) //-1 oznacza maksymalną prędkość, a dalsze ujemne to ograniczenie na bok + if( SwitchExtension->fVelocity <= -2 ) { + //-1 oznacza maksymalną prędkość, a dalsze ujemne to ograniczenie na bok fVelocity = i ? -SwitchExtension->fVelocity : -1; + } + else { + fVelocity = SwitchExtension->fVelocity; + } if (SwitchExtension->pOwner ? SwitchExtension->pOwner->RaTrackAnimAdd(this) : true) // jeśli nie dodane do animacji { // nie ma się co bawić @@ -2455,17 +2579,18 @@ TTrack * TTrack::RaAnimate() gfx::vertex_array vertices; + auto const bladelength { 2 * Global.SplineFidelity }; if (SwitchExtension->RightSwitch) { // nowa wersja z SPKS, ale odwrotnie lewa/prawa if( m_material1 ) { // left blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, SwitchExtension->fOffset2 ); GfxRenderer.Replace( vertices, Geometry1[ 2 ] ); vertices.clear(); } if( m_material2 ) { // right blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -fMaxOffset + SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -fMaxOffset + SwitchExtension->fOffset1 ); GfxRenderer.Replace( vertices, Geometry2[ 2 ] ); vertices.clear(); } @@ -2473,13 +2598,13 @@ TTrack * TTrack::RaAnimate() else { // lewa działa lepiej niż prawa if( m_material1 ) { // right blade - SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, 2, -SwitchExtension->fOffset2 ); + SwitchExtension->Segments[ 0 ]->RenderLoft( vertices, m_origin, rpts4, -nnumPts, fTexLength, 1.0, 0, bladelength, -SwitchExtension->fOffset2 ); GfxRenderer.Replace( vertices, Geometry1[ 2 ] ); vertices.clear(); } if( m_material2 ) { // left blade - SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, 2, fMaxOffset - SwitchExtension->fOffset1 ); + SwitchExtension->Segments[ 1 ]->RenderLoft( vertices, m_origin, rpts3, -nnumPts, fTexLength, 1.0, 0, bladelength, fMaxOffset - SwitchExtension->fOffset1 ); GfxRenderer.Replace( vertices, Geometry2[ 2 ] ); vertices.clear(); } @@ -2659,32 +2784,188 @@ TTrack::endpoints() const { } } -// calculates path's bounding radius -void +// radius() subclass details, calculates node's bounding radius +float TTrack::radius_() { - auto const points = endpoints(); + auto const points { endpoints() }; + auto radius { 0.f }; for( auto &point : points ) { - m_area.radius = std::max( - m_area.radius, + radius = std::max( + radius, static_cast( glm::length( m_area.center - point ) ) ); // extra margin to prevent driven vehicle from flicking } + return radius; +} + +// serialize() subclass details, sends content of the subclass to provided stream +void +TTrack::serialize_( std::ostream &Output ) const { + + // TODO: implement +} +// deserialize() subclass details, restores content of the subclass from provided stream +void +TTrack::deserialize_( std::istream &Input ) { + + // TODO: implement +} + +// export() subclass details, sends basic content of the class in legacy (text) format to provided stream +void +TTrack::export_as_text_( std::ostream &Output ) const { + // header + Output << "track "; + // type + Output << ( + eType == tt_Normal ? ( + iCategoryFlag == 1 ? "normal" : + iCategoryFlag == 2 ? "road" : + iCategoryFlag == 4 ? "river" : + "none" ) : + eType == tt_Switch ? "switch" : + eType == tt_Cross ? "cross" : + eType == tt_Table ? "turn" : + eType == tt_Tributary ? "tributary" : + "none" ) + << ' '; + // basic attributes + Output + << Length() << ' ' + << fTrackWidth << ' ' + << fFriction << ' ' + << fSoundDistance << ' ' + << iQualityFlag << ' ' + << iDamageFlag << ' '; + // environment + Output << ( + eEnvironment == e_flat ? "flat" : + eEnvironment == e_bridge ? "bridge" : + eEnvironment == e_tunnel ? "tunnel" : + eEnvironment == e_bank ? "bank" : + eEnvironment == e_canyon ? "canyon" : + eEnvironment == e_mountains ? "mountains" : + "none" ) + << ' '; + // visibility + // NOTE: 'invis' would be less wrong than 'unvis', but potentially incompatible with old 3rd party tools + Output << ( m_visible ? "vis" : "unvis" ) << ' '; + if( m_visible ) { + // texture parameters are supplied only if the path is set as visible + auto texturefile { ( + m_material1 != null_handle ? + GfxRenderer.Material( m_material1 ).name : + "none" ) }; + if( texturefile.find( szTexturePath ) == 0 ) { + // don't include 'textures/' in the path + texturefile.erase( 0, std::string{ szTexturePath }.size() ); + } + Output + << texturefile << ' ' + << fTexLength << ' '; + + texturefile = ( + m_material2 != null_handle ? + GfxRenderer.Material( m_material2 ).name : + "none" ); + if( texturefile.find( szTexturePath ) == 0 ) { + // don't include 'textures/' in the path + texturefile.erase( 0, std::string{ szTexturePath }.size() ); + } + Output << texturefile << ' '; + + Output + << (fTexHeight1 - fTexHeightOffset ) * ( ( iCategoryFlag & 4 ) ? -1 : 1 ) << ' ' + << fTexWidth << ' ' + << fTexSlope << ' '; + } + // path data + for( auto const &path : m_paths ) { + Output + << path.points[ segment_data::point::start ].x << ' ' + << path.points[ segment_data::point::start ].y << ' ' + << path.points[ segment_data::point::start ].z << ' ' + << path.rolls[ 0 ] << ' ' + + << path.points[ segment_data::point::control1 ].x << ' ' + << path.points[ segment_data::point::control1 ].y << ' ' + << path.points[ segment_data::point::control1 ].z << ' ' + + << path.points[ segment_data::point::control2 ].x << ' ' + << path.points[ segment_data::point::control2 ].y << ' ' + << path.points[ segment_data::point::control2 ].z << ' ' + + << path.points[ segment_data::point::end ].x << ' ' + << path.points[ segment_data::point::end ].y << ' ' + << path.points[ segment_data::point::end ].z << ' ' + << path.rolls[ 1 ] << ' ' + + << path.radius << ' '; + } + // optional attributes + if( false == asEvent0Name.empty() ) { + Output << "event0 " << asEvent0Name << ' '; + } + if( false == asEvent1Name.empty() ) { + Output << "event1 " << asEvent1Name << ' '; + } + if( false == asEvent2Name.empty() ) { + Output << "event2 " << asEvent2Name << ' '; + } + if( false == asEventall0Name.empty() ) { + Output << "eventall0 " << asEventall0Name << ' '; + } + if( false == asEventall1Name.empty() ) { + Output << "eventall1 " << asEventall1Name << ' '; + } + if( false == asEventall2Name.empty() ) { + Output << "eventall2 " << asEventall2Name << ' '; + } + if( ( SwitchExtension ) + && ( SwitchExtension->fVelocity != -1.0 ) ) { + Output << "velocity " << SwitchExtension->fVelocity << ' '; + } + else { + if( fVelocity != -1.0 ) { + Output << "velocity " << fVelocity << ' '; + } + } + if( pIsolated ) { + Output << "isolated " << pIsolated->asName << ' '; + } + if( fOverhead != -1.0 ) { + Output << "overhead " << fOverhead << ' '; + } + // footer + Output + << "endtrack" + << "\n"; } void TTrack::MovedUp1(float const dh) { // poprawienie przechyłki wymaga wydłużenia podsypki fTexHeight1 += dh; + fTexHeightOffset += dh; }; -void TTrack::VelocitySet(float v) -{ // ustawienie prędkości z ograniczeniem do pierwotnej wartości (zapisanej w scenerii) - if (SwitchExtension ? SwitchExtension->fVelocity >= 0.0 : false) - { // zwrotnica może mieć odgórne ograniczenie, nieprzeskakiwalne eventem - if (v > SwitchExtension->fVelocity ? true : v < 0.0) - return void(fVelocity = - SwitchExtension->fVelocity); // maksymalnie tyle, ile było we wpisie +// ustawienie prędkości z ograniczeniem do pierwotnej wartości (zapisanej w scenerii) +void TTrack::VelocitySet(float v) { + // TBD, TODO: add a variable to preserve potential speed limit set by the track configuration on basic track pieces + if( ( SwitchExtension ) + && ( SwitchExtension->fVelocity != -1 ) ) { + // zwrotnica może mieć odgórne ograniczenie, nieprzeskakiwalne eventem + fVelocity = + min_speed( + v, + ( SwitchExtension->fVelocity > 0 ? + SwitchExtension->fVelocity : // positive limit applies to both switch tracks + ( SwitchExtension->CurrentIndex == 0 ? + -1 : // negative limit applies only to the diverging track + -SwitchExtension->fVelocity ) ) ); + } + else { + fVelocity = v; // nie ma ograniczenia } - fVelocity = v; // nie ma ograniczenia }; double TTrack::VelocityGet() @@ -2800,11 +3081,13 @@ path_table::InitTracks() { track->AssignEvents( simulation::Events.FindEvent( trackname + ":event0" ), simulation::Events.FindEvent( trackname + ":event1" ), - simulation::Events.FindEvent( trackname + ":event2" ) ); + simulation::Events.FindEvent( trackname + ":event2" ), + false ); track->AssignallEvents( simulation::Events.FindEvent( trackname + ":eventall0" ), simulation::Events.FindEvent( trackname + ":eventall1" ), - simulation::Events.FindEvent( trackname + ":eventall2" ) ); + simulation::Events.FindEvent( trackname + ":eventall2" ), + false ); } switch (track->eType) { @@ -2948,6 +3231,8 @@ path_table::InitTracks() { scene::node_data nodedata; nodedata.name = isolated->asName; auto *memorycell = new TMemCell( nodedata ); // to nie musi mieć nazwy, nazwa w wyszukiwarce wystarczy + // NOTE: autogenerated cells aren't exported; they'll be autogenerated anew when exported file is loaded + memorycell->is_exportable = false; simulation::Memory.insert( memorycell ); isolated->pMemCell = memorycell; // wskaźnik komóki przekazany do odcinka izolowanego } diff --git a/Track.h b/Track.h index f7494dc3..c47836b8 100644 --- a/Track.h +++ b/Track.h @@ -124,7 +124,7 @@ private: }; // trajektoria ruchu - opakowanie -class TTrack : public editor::basic_node { +class TTrack : public scene::basic_node { friend class opengl_renderer; @@ -142,6 +142,7 @@ private: float fTexRatio1 = 1.0f; // proporcja boków tekstury nawierzchni (żeby zaoszczędzić na rozmiarach tekstur...) float fTexRatio2 = 1.0f; // proporcja boków tekstury chodnika (żeby zaoszczędzić na rozmiarach tekstur...) float fTexHeight1 = 0.6f; // wysokość brzegu względem trajektorii + float fTexHeightOffset = 0.f; // potential adjustment to account for the path roll float fTexWidth = 0.9f; // szerokość boku float fTexSlope = 0.9f; @@ -152,6 +153,8 @@ private: geometryhandle_sequence Geometry1; // geometry chunks textured with texture 1 geometryhandle_sequence Geometry2; // geometry chunks textured with texture 2 + std::vector m_paths; // source data for owned paths + public: typedef std::deque dynamics_sequence; dynamics_sequence Dynamics; @@ -231,8 +234,8 @@ public: SwitchExtension->iRoads - 1 : 1 ); } void Load(cParser *parser, Math3D::vector3 pOrigin); - bool AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2); - bool AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2); + bool AssignEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2, bool const Explicit = true ); + bool AssignallEvents(TEvent *NewEvent0, TEvent *NewEvent1, TEvent *NewEvent2, bool const Explicit = true ); bool AssignForcedEvents(TEvent *NewEventPlus, TEvent *NewEventMinus); bool CheckDynamicObject(TDynamicObject *Dynamic); bool AddDynamicObject(TDynamicObject *Dynamic); @@ -270,10 +273,16 @@ public: double VelocityGet(); void ConnectionsLog(); -protected: - // calculates path's bounding radius - void - radius_(); +private: + // radius() subclass details, calculates node's bounding radius + float radius_(); + // serialize() subclass details, sends content of the subclass to provided stream + void serialize_( std::ostream &Output ) const; + // deserialize() subclass details, restores content of the subclass from provided stream + 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; + }; diff --git a/Traction.cpp b/Traction.cpp index cf7705a7..e993ff0e 100644 --- a/Traction.cpp +++ b/Traction.cpp @@ -529,18 +529,6 @@ double TTraction::VoltageGet(double u, double i) return 0.0; // gdy nie podłączony wcale? }; -// calculates path's bounding radius -void -TTraction::radius_() { - - auto const points = endpoints(); - for( auto &point : points ) { - m_area.radius = std::max( - m_area.radius, - static_cast( glm::length( m_area.center - point ) ) ); - } -} - glm::vec3 TTraction::wire_color() const { @@ -580,6 +568,7 @@ TTraction::wire_color() const { color.r *= Global.DayLight.ambient[ 0 ]; color.g *= Global.DayLight.ambient[ 1 ]; color.b *= Global.DayLight.ambient[ 2 ]; + color *= 0.5f; } else { // tymczasowo pokazanie zasilanych odcinków @@ -675,6 +664,77 @@ TTraction::wire_color() const { return color; } +// radius() subclass details, calculates node's bounding radius +float +TTraction::radius_() { + + auto const points { endpoints() }; + auto radius { 0.f }; + for( auto &point : points ) { + radius = std::max( + radius, + static_cast( glm::length( m_area.center - point ) ) ); + } + return radius; +} + +// serialize() subclass details, sends content of the subclass to provided stream +void +TTraction::serialize_( std::ostream &Output ) const { + + // TODO: implement +} +// deserialize() subclass details, restores content of the subclass from provided stream +void +TTraction::deserialize_( std::istream &Input ) { + + // TODO: implement +} + +// export() subclass details, sends basic content of the class in legacy (text) format to provided stream +void +TTraction::export_as_text_( std::ostream &Output ) const { + // header + Output << "traction "; + // basic attributes + Output + << asPowerSupplyName << ' ' + << NominalVoltage << ' ' + << MaxCurrent << ' ' + << ( fResistivity * 1000 ) << ' ' + << ( + Material == 2 ? "al" : + Material == 0 ? "none" : + "cu" ) << ' ' + << WireThickness << ' ' + << DamageFlag << ' '; + // path data + Output + << pPoint1.x << ' ' << pPoint1.y << ' ' << pPoint1.z << ' ' + << pPoint2.x << ' ' << pPoint2.y << ' ' << pPoint2.z << ' ' + << pPoint3.x << ' ' << pPoint3.y << ' ' << pPoint3.z << ' ' + << pPoint4.x << ' ' << pPoint4.y << ' ' << pPoint4.z << ' '; + // minimum height + Output << ( ( pPoint3.y - pPoint1.y + pPoint4.y - pPoint2.y ) * 0.5 - fHeightDifference ) << ' '; + // segment length + Output << static_cast( iNumSections ? glm::length( pPoint1 - pPoint2 ) / iNumSections : 0.0 ) << ' '; + // wire data + Output + << Wires << ' ' + << WireOffset << ' '; + // visibility + // NOTE: 'invis' would be less wrong than 'unvis', but potentially incompatible with old 3rd party tools + Output << ( m_visible ? "vis" : "unvis" ) << ' '; + // optional attributes + if( false == asParallel.empty() ) { + Output << "parallel " << asParallel << ' '; + } + // footer + Output + << "endtraction" + << "\n"; +} + // legacy method, initializes traction after deserialization from scenario file diff --git a/Traction.h b/Traction.h index c8ccb432..120a7599 100644 --- a/Traction.h +++ b/Traction.h @@ -18,7 +18,7 @@ http://mozilla.org/MPL/2.0/. class TTractionPowerSource; -class TTraction : public editor::basic_node { +class TTraction : public scene::basic_node { friend class opengl_renderer; @@ -73,13 +73,18 @@ class TTraction : public editor::basic_node { void PowerSet(TTractionPowerSource *ps); double VoltageGet(double u, double i); -protected: - // calculates piece's bounding radius - void - radius_(); - private: +// methods glm::vec3 wire_color() const; + // radius() subclass details, calculates node's bounding radius + float radius_(); + // serialize() subclass details, sends content of the subclass to provided stream + void serialize_( std::ostream &Output ) const; + // deserialize() subclass details, restores content of the subclass from provided stream + 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; + }; diff --git a/TractionPower.cpp b/TractionPower.cpp index 8b0d309e..48ff6d4a 100644 --- a/TractionPower.cpp +++ b/TractionPower.cpp @@ -141,6 +141,52 @@ void TTractionPowerSource::PowerSet(TTractionPowerSource *ps) // else ErrorLog("nie może być więcej punktów zasilania niż dwa"); }; +// serialize() subclass details, sends content of the subclass to provided stream +void +TTractionPowerSource::serialize_( std::ostream &Output ) const { + + // TODO: implement +} + +// deserialize() subclass details, restores content of the subclass from provided stream +void +TTractionPowerSource::deserialize_( std::istream &Input ) { + + // TODO: implement +} + +// export() subclass details, sends basic content of the class in legacy (text) format to provided stream +void +TTractionPowerSource::export_as_text_( std::ostream &Output ) const { + // header + Output << "tractionpowersource "; + // placement + Output + << location().x << ' ' + << location().y << ' ' + << location().z << ' '; + // basic attributes + Output + << NominalVoltage << ' ' + << VoltageFrequency << ' ' + << InternalRes << ' ' + << MaxOutputCurrent << ' ' + << FastFuseTimeOut << ' ' + << FastFuseRepetition << ' ' + << SlowFuseTimeOut << ' '; + // optional attributes + if( true == Recuperation ) { + Output << "recuperation "; + } + if( true == bSection ) { + Output << "section "; + } + // footer + Output + << "end" + << "\n"; +} + // legacy method, calculates changes in simulation state over specified time diff --git a/TractionPower.h b/TractionPower.h index 1a30c320..33ab8983 100644 --- a/TractionPower.h +++ b/TractionPower.h @@ -13,9 +13,33 @@ http://mozilla.org/MPL/2.0/. #include "scenenode.h" #include "names.h" -class TTractionPowerSource : public editor::basic_node { +class TTractionPowerSource : public scene::basic_node { - private: +public: +// constructor + TTractionPowerSource( scene::node_data const &Nodedata ); +// methods + void Init(double const u, double const i); + bool Load(cParser *parser); + bool Update(double dt); + double CurrentGet(double res); + void VoltageSet(double const v) { + NominalVoltage = v; }; + void PowerSet(TTractionPowerSource *ps); +// members + TTractionPowerSource *psNode[ 2 ] = { nullptr, nullptr }; // zasilanie na końcach dla sekcji + bool bSection = false; // czy jest sekcją + +private: +// methods + // serialize() subclass details, sends content of the subclass to provided stream + void serialize_( std::ostream &Output ) const; + // deserialize() subclass details, restores content of the subclass from provided stream + 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; + +// members double NominalVoltage = 0.0; double VoltageFrequency = 0.0; double InternalRes = 0.2; @@ -34,20 +58,6 @@ class TTractionPowerSource : public editor::basic_node { double FuseTimer = 0.0; int FuseCounter = 0; -public: - // zmienne publiczne - TTractionPowerSource *psNode[ 2 ] = { nullptr, nullptr }; // zasilanie na końcach dla sekcji - bool bSection = false; // czy jest sekcją - - TTractionPowerSource( scene::node_data const &Nodedata ); - - void Init(double const u, double const i); - bool Load(cParser *parser); - bool Update(double dt); - double CurrentGet(double res); - void VoltageSet(double const v) { - NominalVoltage = v; }; - void PowerSet(TTractionPowerSource *ps); }; diff --git a/World.cpp b/World.cpp index 361ad5a9..37be858b 100644 --- a/World.cpp +++ b/World.cpp @@ -311,49 +311,47 @@ bool TWorld::Init( GLFWwindow *Window ) { void TWorld::OnKeyDown(int cKey) { // dump keypress info in the log - if( !Global.iPause ) { - // podczas pauzy klawisze nie działają - std::string keyinfo; - auto keyname = glfwGetKeyName( cKey, 0 ); - if( keyname != nullptr ) { - keyinfo += std::string( keyname ); - } - else { - switch( cKey ) { + // podczas pauzy klawisze nie działają + std::string keyinfo; + auto keyname = glfwGetKeyName( cKey, 0 ); + if( keyname != nullptr ) { + keyinfo += std::string( keyname ); + } + else { + switch( cKey ) { - case GLFW_KEY_SPACE: { keyinfo += "Space"; break; } - case GLFW_KEY_ENTER: { keyinfo += "Enter"; break; } - case GLFW_KEY_ESCAPE: { keyinfo += "Esc"; break; } - case GLFW_KEY_TAB: { keyinfo += "Tab"; break; } - case GLFW_KEY_INSERT: { keyinfo += "Insert"; break; } - case GLFW_KEY_DELETE: { keyinfo += "Delete"; break; } - case GLFW_KEY_HOME: { keyinfo += "Home"; break; } - case GLFW_KEY_END: { keyinfo += "End"; break; } - case GLFW_KEY_F1: { keyinfo += "F1"; break; } - case GLFW_KEY_F2: { keyinfo += "F2"; break; } - case GLFW_KEY_F3: { keyinfo += "F3"; break; } - case GLFW_KEY_F4: { keyinfo += "F4"; break; } - case GLFW_KEY_F5: { keyinfo += "F5"; break; } - case GLFW_KEY_F6: { keyinfo += "F6"; break; } - case GLFW_KEY_F7: { keyinfo += "F7"; break; } - case GLFW_KEY_F8: { keyinfo += "F8"; break; } - case GLFW_KEY_F9: { keyinfo += "F9"; break; } - case GLFW_KEY_F10: { keyinfo += "F10"; break; } - case GLFW_KEY_F11: { keyinfo += "F11"; break; } - case GLFW_KEY_F12: { keyinfo += "F12"; break; } - case GLFW_KEY_PAUSE: { keyinfo += "Pause"; break; } - } + case GLFW_KEY_SPACE: { keyinfo += "Space"; break; } + case GLFW_KEY_ENTER: { keyinfo += "Enter"; break; } + case GLFW_KEY_ESCAPE: { keyinfo += "Esc"; break; } + case GLFW_KEY_TAB: { keyinfo += "Tab"; break; } + case GLFW_KEY_INSERT: { keyinfo += "Insert"; break; } + case GLFW_KEY_DELETE: { keyinfo += "Delete"; break; } + case GLFW_KEY_HOME: { keyinfo += "Home"; break; } + case GLFW_KEY_END: { keyinfo += "End"; break; } + case GLFW_KEY_F1: { keyinfo += "F1"; break; } + case GLFW_KEY_F2: { keyinfo += "F2"; break; } + case GLFW_KEY_F3: { keyinfo += "F3"; break; } + case GLFW_KEY_F4: { keyinfo += "F4"; break; } + case GLFW_KEY_F5: { keyinfo += "F5"; break; } + case GLFW_KEY_F6: { keyinfo += "F6"; break; } + case GLFW_KEY_F7: { keyinfo += "F7"; break; } + case GLFW_KEY_F8: { keyinfo += "F8"; break; } + case GLFW_KEY_F9: { keyinfo += "F9"; break; } + case GLFW_KEY_F10: { keyinfo += "F10"; break; } + case GLFW_KEY_F11: { keyinfo += "F11"; break; } + case GLFW_KEY_F12: { keyinfo += "F12"; break; } + case GLFW_KEY_PAUSE: { keyinfo += "Pause"; break; } } - if( keyinfo.empty() == false ) { + } + if( keyinfo.empty() == false ) { - std::string keymodifiers; - if( Global.shiftState ) - keymodifiers += "[Shift]+"; - if( Global.ctrlState ) - keymodifiers += "[Ctrl]+"; + std::string keymodifiers; + if( Global.shiftState ) + keymodifiers += "[Shift]+"; + if( Global.ctrlState ) + keymodifiers += "[Ctrl]+"; - WriteLog( "Key pressed: " + keymodifiers + "[" + keyinfo + "]" ); - } + WriteLog( "Key pressed: " + keymodifiers + "[" + keyinfo + "]" ); } // actual key processing @@ -504,6 +502,14 @@ void TWorld::OnKeyDown(int cKey) { } break; } + case GLFW_KEY_F11: { + // scenery export + if( Global.ctrlState + && Global.shiftState ) { + simulation::State.export_as_text( Global.SceneryFile ); + } + break; + } case GLFW_KEY_F12: { // quick debug mode toggle if( Global.ctrlState diff --git a/scene.cpp b/scene.cpp index be68f31f..ea251a9b 100644 --- a/scene.cpp +++ b/scene.cpp @@ -228,6 +228,17 @@ basic_cell::deserialize( std::istream &Input ) { || ( false == m_lines.empty() ) ); } +// sends content of the class in legacy (text) format to provided stream +void +basic_cell::export_as_text( std::ostream &Output ) const { + + // text format export dumps only relevant basic objects + // sounds + for( auto const *sound : m_sounds ) { + sound->export_as_text( Output ); + } +} + // adds provided shape to the cell void basic_cell::insert( shape_node Shape ) { @@ -530,7 +541,7 @@ basic_cell::create_geometry( gfx::geometrybank_handle const &Bank ) { // adjusts cell bounding area to enclose specified node void -basic_cell::enclose_area( editor::basic_node *Node ) { +basic_cell::enclose_area( scene::basic_node *Node ) { m_area.radius = std::max( m_area.radius, @@ -645,6 +656,16 @@ basic_section::deserialize( std::istream &Input ) { } } +// sends content of the class in legacy (text) format to provided stream +void +basic_section::export_as_text( std::ostream &Output ) const { + + // text format export dumps only relevant basic objects from non-empty cells + for( auto const &cell : m_cells ) { + cell.export_as_text( Output ); + } +} + // adds provided shape to the section void basic_section::insert( shape_node Shape ) { @@ -949,6 +970,18 @@ basic_region::deserialize( std::string const &Scenariofile ) { return true; } +// sends content of the class in legacy (text) format to provided stream +void +basic_region::export_as_text( std::ostream &Output ) const { + + for( auto *section : m_sections ) { + // text format export dumps only relevant basic objects from non-empty sections + if( section != nullptr ) { + section->export_as_text( Output ); + } + } +} + // legacy method, links specified path piece with potential neighbours void basic_region::TrackJoin( TTrack *Track ) { diff --git a/scene.h b/scene.h index c7f2dd6e..8a435ef1 100644 --- a/scene.h +++ b/scene.h @@ -88,6 +88,9 @@ public: // restores content of the class from provided stream void deserialize( std::istream &Input ); + // sends content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; // adds provided shape to the cell void insert( shape_node Shape ); @@ -149,7 +152,7 @@ private: using eventlauncher_sequence = std::vector; // methods void - enclose_area( editor::basic_node *Node ); + enclose_area( scene::basic_node *Node ); // members scene::bounding_area m_area { glm::dvec3(), static_cast( 0.5 * M_SQRT2 * EU07_CELLSIZE ) }; bool m_active { false }; // whether the cell holds any actual data @@ -199,6 +202,9 @@ public: // restores content of the class from provided stream void deserialize( std::istream &Input ); + // sends content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; // adds provided shape to the section void insert( shape_node Shape ); @@ -289,6 +295,9 @@ public: // restores content of the class from file with specified name. returns: true on success, false otherwise bool deserialize( std::string const &Scenariofile ); + // sends content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; // legacy method, links specified path piece with potential neighbours void TrackJoin( TTrack *Track ); diff --git a/scenenode.cpp b/scenenode.cpp index 588281c3..19c3ca5a 100644 --- a/scenenode.cpp +++ b/scenenode.cpp @@ -142,7 +142,7 @@ shape_node::deserialize( std::istream &Input ) { // restores content of the node from provided input stream shape_node & -shape_node::deserialize( cParser &Input, scene::node_data const &Nodedata ) { +shape_node::import( cParser &Input, scene::node_data const &Nodedata ) { // import common data m_name = Nodedata.name; @@ -523,7 +523,7 @@ lines_node::deserialize( std::istream &Input ) { // restores content of the node from provded input stream lines_node & -lines_node::deserialize( cParser &Input, scene::node_data const &Nodedata ) { +lines_node::import( cParser &Input, scene::node_data const &Nodedata ) { // import common data m_name = Nodedata.name; @@ -684,12 +684,9 @@ memory_node::deserialize( cParser &Input, node_data const &Nodedata ) { memorycell.Load( &Input ); } */ -} // scene -namespace editor { - basic_node::basic_node( scene::node_data const &Nodedata ) : m_name( Nodedata.name ) { @@ -700,23 +697,69 @@ basic_node::basic_node( scene::node_data const &Nodedata ) : std::numeric_limits::max() ); } +// sends content of the class to provided stream +void +basic_node::serialize( std::ostream &Output ) const { + // bounding area + m_area.serialize( Output ); + // visibility + sn_utils::ls_float64( Output, m_rangesquaredmin ); + sn_utils::ls_float64( Output, m_rangesquaredmax ); + sn_utils::s_bool( Output, m_visible ); + // name + sn_utils::s_str( Output, m_name ); + // template method implementation + serialize_( Output ); +} + +// restores content of the class from provided stream +void +basic_node::deserialize( std::istream &Input ) { + // bounding area + m_area.deserialize( Input ); + // visibility + m_rangesquaredmin = sn_utils::ld_float64( Input ); + m_rangesquaredmax = sn_utils::ld_float64( Input ); + m_visible = sn_utils::d_bool( Input ); + // name + m_name = sn_utils::d_str( Input ); + // template method implementation + deserialize_( Input ); +} + +// sends basic content of the class in legacy (text) format to provided stream +void +basic_node::export_as_text( std::ostream &Output ) const { + + Output + // header + << "node" + // visibility + << ' ' << ( m_rangesquaredmax < std::numeric_limits::max() ? std::sqrt( m_rangesquaredmax ) : -1 ) + << ' ' << std::sqrt( m_rangesquaredmin ) + // name + << ' ' << m_name << ' '; + // template method implementation + export_as_text_( Output ); +} + float const & basic_node::radius() { if( m_area.radius == -1.0 ) { // calculate if needed - radius_(); + m_area.radius = radius_(); } return m_area.radius; } // radius() subclass details, calculates node's bounding radius // by default nodes are 'virtual don't extend from their center point -void +float basic_node::radius_() { - m_area.radius = 0.f; + return 0.f; } -} // editor +} // scene //--------------------------------------------------------------------------- diff --git a/scenenode.h b/scenenode.h index f99c361d..3b922698 100644 --- a/scenenode.h +++ b/scenenode.h @@ -112,7 +112,7 @@ public: deserialize( std::istream &Input ); // restores content of the node from provided input stream shape_node & - deserialize( cParser &Input, scene::node_data const &Nodedata ); + import( cParser &Input, scene::node_data const &Nodedata ); // imports data from provided submodel shape_node & convert( TSubModel const *Submodel ); @@ -201,7 +201,7 @@ public: deserialize( std::istream &Input ); // restores content of the node from provided input stream lines_node & - deserialize( cParser &Input, scene::node_data const &Nodedata ); + import( cParser &Input, scene::node_data const &Nodedata ); // adds content of provided node to already enclosed geometry. returns: true if merge could be performed bool merge( lines_node &Lines ); @@ -295,12 +295,9 @@ private: TTrack * m_path; }; */ -} // scene -namespace editor { - // base interface for nodes which can be actvated in scenario editor struct basic_node { @@ -310,6 +307,15 @@ public: // destructor virtual ~basic_node() = default; // methods + // sends content of the class to provided stream + void + serialize( std::ostream &Output ) const; + // restores content of the class from provided stream + void + deserialize( std::istream &Input ); + // sends basic content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; std::string const & name() const; void @@ -324,15 +330,23 @@ public: visible() const; protected: -// methods - // radius() subclass details, calculates node's bounding radius - virtual void radius_(); // members scene::bounding_area m_area; - bool m_visible { true }; double m_rangesquaredmin { 0.0 }; // visibility range, min double m_rangesquaredmax { 0.0 }; // visibility range, max + bool m_visible { true }; // visibility flag std::string m_name; + +private: +// methods + // radius() subclass details, calculates node's bounding radius + virtual float radius_(); + // serialize() subclass details, sends content of the subclass to provided stream + virtual void serialize_( std::ostream &Output ) const = 0; + // deserialize() subclass details, restores content of the subclass from provided stream + virtual void deserialize_( std::istream &Input ) = 0; + // export() subclass details, sends basic content of the class in legacy (text) format to provided stream + virtual void export_as_text_( std::ostream &Output ) const = 0; }; inline @@ -365,6 +379,6 @@ basic_node::visible() const { return m_visible; } -} // editor +} // scene //--------------------------------------------------------------------------- diff --git a/simulation.cpp b/simulation.cpp index 4499ba68..8e6cb57d 100644 --- a/simulation.cpp +++ b/simulation.cpp @@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/. #include "uilayer.h" #include "renderer.h" + namespace simulation { state_manager State; @@ -57,8 +58,6 @@ state_manager::deserialize( std::string const &Scenariofile ) { // as long as the scenario file wasn't rainsted-created base file override Region->serialize( Scenariofile ); } - - Global.iPause &= ~0x10; // koniec pauzy wczytywania return true; } @@ -409,7 +408,7 @@ state_manager::deserialize_node( cParser &Input, scene::scratch_data &Scratchpad if( false == Scratchpad.binary.terrain ) { simulation::Region->insert_shape( - scene::shape_node().deserialize( + scene::shape_node().import( Input, nodedata ), Scratchpad, true ); @@ -426,7 +425,7 @@ state_manager::deserialize_node( cParser &Input, scene::scratch_data &Scratchpad if( false == Scratchpad.binary.terrain ) { simulation::Region->insert_lines( - scene::lines_node().deserialize( + scene::lines_node().import( Input, nodedata ), Scratchpad ); } @@ -439,7 +438,7 @@ state_manager::deserialize_node( cParser &Input, scene::scratch_data &Scratchpad auto *memorycell { deserialize_memorycell( Input, Scratchpad, nodedata ) }; if( false == simulation::Memory.insert( memorycell ) ) { - ErrorLog( "Bad scenario: memory cell with duplicate name \"" + memorycell->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); + ErrorLog( "Bad scenario: memory memorycell with duplicate name \"" + memorycell->name() + "\" encountered in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); } /* // TODO: implement this @@ -897,6 +896,65 @@ state_manager::transform( glm::dvec3 Location, scene::scratch_data const &Scratc return Location; } + +// stores class data in specified file, in legacy (text) format +void +state_manager::export_as_text( std::string const &Scenariofile ) const { + + if( Scenariofile == "$.scn" ) { + ErrorLog( "Bad file: scenery export not supported for file \"$.scn\"" ); + } + else { + WriteLog( "Scenery data export in progress..." ); + } + + auto filename { Scenariofile }; + while( filename[ 0 ] == '$' ) { + // trim leading $ char rainsted utility may add to the base name for modified .scn files + filename.erase( 0, 1 ); + } + erase_extension( filename ); + filename = Global.asCurrentSceneryPath + filename + "_export"; + + std::ofstream scmfile { filename + ".scm" }; + // tracks + scmfile << "// paths\n"; + for( auto const *path : Paths.sequence() ) { + path->export_as_text( scmfile ); + } + // traction + scmfile << "// traction\n"; + for( auto const *traction : Traction.sequence() ) { + traction->export_as_text( scmfile ); + } + // power grid + scmfile << "// traction power sources\n"; + for( auto const *powersource : Powergrid.sequence() ) { + powersource->export_as_text( scmfile ); + } + // models + scmfile << "// instanced models\n"; + for( auto const *instance : Instances.sequence() ) { + instance->export_as_text( scmfile ); + } + // sounds + scmfile << "// sounds\n"; + Region->export_as_text( scmfile ); + + std::ofstream ctrfile { filename + ".ctr" }; + // mem cells + ctrfile << "// memory cells\n"; + for( auto const *memorycell : Memory.sequence() ) { + if( true == memorycell->is_exportable ) { + memorycell->export_as_text( ctrfile ); + } + } + // events + Events.export_as_text( ctrfile ); + + WriteLog( "Scenery data export done." ); +} + } // simulation //--------------------------------------------------------------------------- diff --git a/simulation.h b/simulation.h index 0a344aa8..91f3785f 100644 --- a/simulation.h +++ b/simulation.h @@ -36,6 +36,9 @@ public: update( double Deltatime, int Iterationcount ); bool deserialize( std::string const &Scenariofile ); + // stores class data in specified file, in legacy (text) format + void + export_as_text( std::string const &Scenariofile ) const; private: // methods @@ -69,6 +72,7 @@ private: void skip_until( cParser &Input, std::string const &Token ); // transforms provided location by specifed rotation and offset glm::dvec3 transform( glm::dvec3 Location, scene::scratch_data const &Scratchpad ); + }; extern state_manager State; diff --git a/sound.cpp b/sound.cpp index bd903e66..da071a6a 100644 --- a/sound.cpp +++ b/sound.cpp @@ -293,6 +293,43 @@ sound_source::deserialize_soundset( cParser &Input ) { } } +// sends content of the class in legacy (text) format to provided stream +// NOTE: currently exports only the main sound +void +sound_source::export_as_text( std::ostream &Output ) const { + + if( sound( sound_id::main ).buffer == null_handle ) { return; } + + // generic node header + Output + << "node " + // visibility + << m_range << ' ' + << 0 << ' ' + // name + << m_name << ' '; + // sound node header + Output + << "sound "; + // location + Output + << m_offset.x << ' ' + << m_offset.y << ' ' + << m_offset.z << ' '; + // sound data + auto soundfile { audio::renderer.buffer( sound( sound_id::main ).buffer ).name }; + if( soundfile.find( szSoundPath ) == 0 ) { + // don't include 'sounds/' in the path + soundfile.erase( 0, std::string{ szSoundPath }.size() ); + } + Output + << soundfile << ' '; + // footer + Output + << "endsound" + << "\n"; +} + // copies list of sounds from provided source sound_source & sound_source::copy_sounds( sound_source const &Source ) { diff --git a/sound.h b/sound.h index 004e662b..7a3b1cde 100644 --- a/sound.h +++ b/sound.h @@ -60,6 +60,9 @@ public: deserialize( cParser &Input, sound_type const Legacytype, int const Legacyparameters = 0, int const Chunkrange = 100 ); sound_source & deserialize( std::string const &Input, sound_type const Legacytype, int const Legacyparameters = 0 ); + // sends content of the class in legacy (text) format to provided stream + void + export_as_text( std::ostream &Output ) const; // copies list of sounds from provided source sound_source & copy_sounds( sound_source const &Source ); diff --git a/uilayer.cpp b/uilayer.cpp index b31d716e..5c16c0cf 100644 --- a/uilayer.cpp +++ b/uilayer.cpp @@ -98,6 +98,7 @@ ui_layer::on_key( int const Key, int const Action ) { case GLFW_KEY_F8: case GLFW_KEY_F9: case GLFW_KEY_F10: + case GLFW_KEY_F11: case GLFW_KEY_F12: { // ui mode selectors if( ( true == Global.ctrlState )