diff --git a/rendering/geometrybank.cpp b/rendering/geometrybank.cpp index fcd731dd..e853f4dc 100644 --- a/rendering/geometrybank.cpp +++ b/rendering/geometrybank.cpp @@ -300,8 +300,8 @@ geometry_bank::replace( gfx::vertex_array &Vertices, userdata_array &Userdata, g // TBD: we could bail out with an error instead if such request occurs chunk.vertices.resize( Offset + Vertices.size(), gfx::basic_vertex() ); chunk.userdata.resize( Offset + Userdata.size(), gfx::vertex_userdata() ); - chunk.vertices.insert( std::end( chunk.vertices ), std::begin( Vertices ), std::end( Vertices ) ); - chunk.userdata.insert( std::end( chunk.userdata ), std::begin( Userdata ), std::end( Userdata ) ); + std::copy( std::begin( Vertices ), std::end( Vertices ), std::begin( chunk.vertices ) + Offset ); + std::copy( std::begin( Userdata ), std::end( Userdata ), std::begin( chunk.userdata ) + Offset ); } // template method implementation replace_( Geometry ); diff --git a/scene/scene.cpp b/scene/scene.cpp index 8ebff031..7655ace6 100644 --- a/scene/scene.cpp +++ b/scene/scene.cpp @@ -416,6 +416,40 @@ basic_cell::insert( TMemCell *Memorycell ) { // NOTE: memory cells are virtual 'points' hence they don't ever expand cell range } +// removes provided path from the cell +void +basic_cell::erase( TTrack *Path ) { + + m_paths.erase( + std::remove( std::begin( m_paths ), std::end( m_paths ), Path ), + std::end( m_paths ) ); + // drop the path from the animation list, the cell won't update it anymore + if( tTrackAnim == Path ) { + tTrackAnim = Path->SwitchExtension->pNextAnim; + Path->SwitchExtension->pNextAnim = nullptr; + } + else { + for( auto *track = tTrackAnim; track != nullptr; track = track->SwitchExtension->pNextAnim ) { + if( track->SwitchExtension->pNextAnim == Path ) { + track->SwitchExtension->pNextAnim = Path->SwitchExtension->pNextAnim; + Path->SwitchExtension->pNextAnim = nullptr; + break; + } + } + } + Path->RaOwnerSet( nullptr ); + // TODO: update cell bounding area +} + +// removes provided path from the lookup directory of the cell +void +basic_cell::unregister_end( TTrack *Path ) { + + m_directories.paths.erase( + std::remove( std::begin( m_directories.paths ), std::end( m_directories.paths ), Path ), + std::end( m_directories.paths ) ); +} + // removes provided model instance from the cell void basic_cell::erase( TAnimModel *Instance ) { diff --git a/scene/scene.h b/scene/scene.h index 9f5201f8..7b590182 100644 --- a/scene/scene.h +++ b/scene/scene.h @@ -145,6 +145,12 @@ public: // registers provided traction piece in the lookup directory of the cell void register_end( TTraction *Traction ); + // removes provided path from the cell + void + erase( TTrack *Path ); + // removes provided path from the lookup directory of the cell + void + unregister_end( TTrack *Path ); // removes provided model instance from the cell void erase( TAnimModel *Instance ); @@ -308,6 +314,11 @@ public: void register_node( Type_ *Node, glm::dvec3 const &Point ) { cell( Point ).register_end( Node ); } + // removes provided node from the lookup directory of the section enclosing specified point + template + void + unregister_node( Type_ *Node, glm::dvec3 const &Point ) { + cell( Point ).unregister_end( Node ); } // find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance std::tuple find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ); @@ -435,6 +446,14 @@ public: auto const location{ Node->location() }; if( point_inside( location ) ) { section( location ).erase( Node ); } } + // removes specified node from the region and from the lookup directories of its ends + template + void + erase_and_unregister( Type_ *Node ) { + for( auto const &point : Node->endpoints() ) { + if( point_inside( point ) ) { + section( point ).unregister_node( Node, point ); } } + erase( Node ); } // find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance std::tuple find_vehicle( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ); diff --git a/world/Track.cpp b/world/Track.cpp index 6155116c..75f0ec6f 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -382,8 +382,7 @@ void TTrack::ConnectNextNext(TTrack *pTrack, int typ) void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) { // pobranie obiektu trajektorii ruchu - glm::dvec3 pt, vec, p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań - double a1, a2, r1, r2, r3, r4; + double a1, a2; std::string str; size_t i; //,state; //Ra: teraz już nie ma początkowego stanu zwrotnicy we wpisie std::string token; @@ -501,8 +500,6 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) else if (Global.iWriteLogEnabled & 4) WriteLog("unvis"); Init(); // ustawia SwitchExtension - double segsize = 5.0; // długość odcinka segmentowania - // path data // all subtypes contain at least one path m_paths.emplace_back(); @@ -521,6 +518,198 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) } } + init_segments( true ); + + // optional attributes + parser->getTokens(); + *parser >> token; + str = token; + while (str != "endtrack") + { + if (str == "event0") + { + parser->getTokens(); + *parser >> token; + m_events0.emplace_back( token, nullptr ); + } + else if (str == "event1") + { + parser->getTokens(); + *parser >> token; + m_events1.emplace_back( token, nullptr ); + } + else if (str == "event2") + { + parser->getTokens(); + *parser >> token; + m_events2.emplace_back( token, nullptr ); + } + else if (str == "eventall0") + { + parser->getTokens(); + *parser >> token; + m_events0all.emplace_back( token, nullptr ); + } + else if (str == "eventall1") + { + parser->getTokens(); + *parser >> token; + m_events1all.emplace_back( token, nullptr ); + } + else if (str == "eventall2") + { + parser->getTokens(); + *parser >> token; + m_events2all.emplace_back( token, nullptr ); + } + else if (str == "velocity") + { + parser->getTokens(); + *parser >> fVelocity; //*0.28; McZapkie-010602 + if (SwitchExtension) // jeśli tor ruchomy + 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 + parser->getTokens(); + *parser >> token; + Isolated.push_back(TIsolated::Find(token)); + } + else if (str == "angle1") + { // kąt ścięcia końca od strony 1 + // NOTE: not used/implemented + parser->getTokens(); + *parser >> a1; + //Segment->AngleSet(0, a1); + } + else if (str == "angle2") + { // kąt ścięcia końca od strony 2 + // NOTE: not used/implemented + parser->getTokens(); + *parser >> a2; + //Segment->AngleSet(1, a2); + } + else if (str == "fouling1") + { // wskazanie modelu ukresu w kierunku 1 + // NOTE: not used/implemented + parser->getTokens(); + *parser >> token; + // nFouling[0]= + } + else if (str == "fouling2") + { // wskazanie modelu ukresu w kierunku 2 + // NOTE: not used/implemented + parser->getTokens(); + *parser >> token; + // nFouling[1]= + } + else if (str == "overhead") + { // informacja o stanie sieci: 0-jazda bezprądowa, >0-z opuszczonym i ograniczeniem prędkości + parser->getTokens(); + *parser >> fOverhead; + if (fOverhead > 0.0) + iAction |= 0x40; // flaga opuszczenia pantografu (tor uwzględniany w skanowaniu jako + // ograniczenie dla pantografujących) + } + else if( str == "vradius" ) { + // y-axis track radius + // NOTE: not used/implemented + parser->getTokens(); + *parser >> fVerticalRadius; + } + else if( str == "trackbed" ) { + // switch trackbed texture + auto const trackbedtexture { parser->getToken() }; + if( eType == tt_Switch ) { + SwitchExtension->m_material3 = GfxRenderer->Fetch_Material( trackbedtexture ); + } + } + else if( str == "railprofile" ) { + // rail profile + auto const railprofile { parser->getToken() }; + if( iCategoryFlag == 1 ) { + m_profile1 = fetch_track_rail_profile( railprofile ); + } + } + else if( str == "friction" ) { + // memory cell holding friction value modifiers + m_friction.first = parser->getToken(); + } + else if( str == "sleepermodel" ) { + // sleepermodel + // - frequency: meters between consecutive sleeper instances (must be > 0) + // - model: path to the .e3d sleeper model + // - skin: replacable skin path, or "none" for the model's defaults + // - offset: local-space offset applied per-instance (x=left/right, y=forward/back, z=up/down) + // - ballastZ: vertical shift applied to the auto-generated trackbed (ballast). negative pushes ballast down. + float frequency { 0.f }; + float offsetx { 0.f }, offsety { 0.f }, offsetz { 0.f }; + float ballastz { 0.f }; + parser->getTokens( 1, false ); *parser >> frequency; + auto modelpath { parser->getToken( false ) }; + auto skinpath { parser->getToken( false ) }; + parser->getTokens( 3, false ); *parser >> offsetx >> offsety >> offsetz; + parser->getTokens( 1, false ); *parser >> ballastz; + + if( frequency <= 0.01f ) { + ErrorLog( "Bad track: invalid sleepermodel frequency (" + std::to_string( frequency ) + ") for track \"" + m_name + "\"" ); + } + else { + replace_slashes( modelpath ); + m_sleeper_enabled = true; + m_sleeper_frequency = frequency; + m_sleeper_model_name = modelpath; + m_sleeper_skin_name = skinpath; + m_sleeper_offset = glm::vec3( offsetx, offsety, offsetz ); + m_sleeper_ballast_z = ballastz; + // model and skin are resolved (and instance transforms baked) in build_sleeper_transforms, + // called after segment initialisation so the path geometry is final. + } + } + else + ErrorLog("Bad track: unknown property: \"" + str + "\" defined for track \"" + m_name + "\""); + parser->getTokens(); + *parser >> token; + str = token; + } + // alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru + if ((i = m_name.find("@")) != std::string::npos && i < m_name.length()) + { + Isolated.push_back(TIsolated::Find(m_name.substr(i + 1, m_name.length()))); + m_name = m_name.substr(0, i - 1); // usunięcie z nazwy + } + + update_location(); + // sleeper transforms are baked later in create_geometry(), once the owning cell has + // assigned this track its m_origin (otherwise the local-space matrices would be relative + // to a stale origin and the renderer would draw sleepers in the wrong place). +} + +// calculates path location from the current segment +void TTrack::update_location() { + + location( ( + CurrentSegment()->FastGetPoint_0() + + CurrentSegment()->FastGetPoint( 0.5 ) + + CurrentSegment()->FastGetPoint_1() ) + / 3.0 ); +} + +// (re)creates path segments from the source path data in m_paths +// Initial: true when called during deserialization; the editor passes false to keep the current switch state +void TTrack::init_segments( bool const Initial ) { + + glm::dvec3 p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań + double r1, r2, r3, r4; + double segsize = 5.0; // długość odcinka segmentowania + + // TSegment::Init() raises the trackbed for the roll fix through MovedUp1(); drop the previous adjustment + // so repeated initialization doesn't accumulate it + fTexHeight1 -= fTexHeightOffset; + fTexHeightOffset = 0.f; + switch (eType) { // Ra: łuki segmentowane co 5m albo 314-kątem foremnym case tt_Table: { @@ -773,7 +962,9 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) } } - Switch(0); // na stałe w położeniu 0 - nie ma początkowego stanu zwrotnicy we wpisie + if( Initial ) { + Switch(0); + } // na stałe w położeniu 0 - nie ma początkowego stanu zwrotnicy we wpisie if( eType == tt_Switch ) // Ra: zamienić później na iloczyn wektorowy @@ -796,177 +987,6 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) break; } } - - // optional attributes - parser->getTokens(); - *parser >> token; - str = token; - while (str != "endtrack") - { - if (str == "event0") - { - parser->getTokens(); - *parser >> token; - m_events0.emplace_back( token, nullptr ); - } - else if (str == "event1") - { - parser->getTokens(); - *parser >> token; - m_events1.emplace_back( token, nullptr ); - } - else if (str == "event2") - { - parser->getTokens(); - *parser >> token; - m_events2.emplace_back( token, nullptr ); - } - else if (str == "eventall0") - { - parser->getTokens(); - *parser >> token; - m_events0all.emplace_back( token, nullptr ); - } - else if (str == "eventall1") - { - parser->getTokens(); - *parser >> token; - m_events1all.emplace_back( token, nullptr ); - } - else if (str == "eventall2") - { - parser->getTokens(); - *parser >> token; - m_events2all.emplace_back( token, nullptr ); - } - else if (str == "velocity") - { - parser->getTokens(); - *parser >> fVelocity; //*0.28; McZapkie-010602 - if (SwitchExtension) // jeśli tor ruchomy - 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 - parser->getTokens(); - *parser >> token; - Isolated.push_back(TIsolated::Find(token)); - } - else if (str == "angle1") - { // kąt ścięcia końca od strony 1 - // NOTE: not used/implemented - parser->getTokens(); - *parser >> a1; - //Segment->AngleSet(0, a1); - } - else if (str == "angle2") - { // kąt ścięcia końca od strony 2 - // NOTE: not used/implemented - parser->getTokens(); - *parser >> a2; - //Segment->AngleSet(1, a2); - } - else if (str == "fouling1") - { // wskazanie modelu ukresu w kierunku 1 - // NOTE: not used/implemented - parser->getTokens(); - *parser >> token; - // nFouling[0]= - } - else if (str == "fouling2") - { // wskazanie modelu ukresu w kierunku 2 - // NOTE: not used/implemented - parser->getTokens(); - *parser >> token; - // nFouling[1]= - } - else if (str == "overhead") - { // informacja o stanie sieci: 0-jazda bezprądowa, >0-z opuszczonym i ograniczeniem prędkości - parser->getTokens(); - *parser >> fOverhead; - if (fOverhead > 0.0) - iAction |= 0x40; // flaga opuszczenia pantografu (tor uwzględniany w skanowaniu jako - // ograniczenie dla pantografujących) - } - else if( str == "vradius" ) { - // y-axis track radius - // NOTE: not used/implemented - parser->getTokens(); - *parser >> fVerticalRadius; - } - else if( str == "trackbed" ) { - // switch trackbed texture - auto const trackbedtexture { parser->getToken() }; - if( eType == tt_Switch ) { - SwitchExtension->m_material3 = GfxRenderer->Fetch_Material( trackbedtexture ); - } - } - else if( str == "railprofile" ) { - // rail profile - auto const railprofile { parser->getToken() }; - if( iCategoryFlag == 1 ) { - m_profile1 = fetch_track_rail_profile( railprofile ); - } - } - else if( str == "friction" ) { - // memory cell holding friction value modifiers - m_friction.first = parser->getToken(); - } - else if( str == "sleepermodel" ) { - // sleepermodel - // - frequency: meters between consecutive sleeper instances (must be > 0) - // - model: path to the .e3d sleeper model - // - skin: replacable skin path, or "none" for the model's defaults - // - offset: local-space offset applied per-instance (x=left/right, y=forward/back, z=up/down) - // - ballastZ: vertical shift applied to the auto-generated trackbed (ballast). negative pushes ballast down. - float frequency { 0.f }; - float offsetx { 0.f }, offsety { 0.f }, offsetz { 0.f }; - float ballastz { 0.f }; - parser->getTokens( 1, false ); *parser >> frequency; - auto modelpath { parser->getToken( false ) }; - auto skinpath { parser->getToken( false ) }; - parser->getTokens( 3, false ); *parser >> offsetx >> offsety >> offsetz; - parser->getTokens( 1, false ); *parser >> ballastz; - - if( frequency <= 0.01f ) { - ErrorLog( "Bad track: invalid sleepermodel frequency (" + std::to_string( frequency ) + ") for track \"" + m_name + "\"" ); - } - else { - replace_slashes( modelpath ); - m_sleeper_enabled = true; - m_sleeper_frequency = frequency; - m_sleeper_model_name = modelpath; - m_sleeper_skin_name = skinpath; - m_sleeper_offset = glm::vec3( offsetx, offsety, offsetz ); - m_sleeper_ballast_z = ballastz; - // model and skin are resolved (and instance transforms baked) in build_sleeper_transforms, - // called after segment initialisation so the path geometry is final. - } - } - else - ErrorLog("Bad track: unknown property: \"" + str + "\" defined for track \"" + m_name + "\""); - parser->getTokens(); - *parser >> token; - str = token; - } - // alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru - if ((i = m_name.find("@")) != std::string::npos && i < m_name.length()) - { - Isolated.push_back(TIsolated::Find(m_name.substr(i + 1, m_name.length()))); - m_name = m_name.substr(0, i - 1); // usunięcie z nazwy - } - - // calculate path location - location( ( - CurrentSegment()->FastGetPoint_0() - + CurrentSegment()->FastGetPoint( 0.5 ) - + CurrentSegment()->FastGetPoint_1() ) - / 3.0 ); - // sleeper transforms are baked later in create_geometry(), once the owning cell has - // assigned this track its m_origin (otherwise the local-space matrices would be relative - // to a stale origin and the renderer would draw sleepers in the wrong place). } bool TTrack::AssignEvents() { @@ -1333,6 +1353,22 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const } // wypełnianie tablic VBO +// discards references to current render geometry and generates it anew in specified bank +// NOTE: geometry banks can't release chunks, so the old data stays in the bank, unused +void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { + + if( Bank == null_handle ) { return; } + + Geometry1.clear(); + Geometry2.clear(); + if( SwitchExtension ) { + SwitchExtension->Geometry3 = gfx::geometry_handle{}; + } + m_sleeper_local_transforms.clear(); + + create_geometry( Bank ); +} + void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::userdata_array empty_userdata; // bake per-instance sleeper transforms now that the owning cell has assigned m_origin. @@ -2319,6 +2355,8 @@ TTrack::export_as_text_( std::ostream &Output ) const { << fTexSlope << ' '; } // path data + // NOTE: default stream precision would round coordinates to centimetres or worse, breaking path connections + auto const precision { Output.precision( std::numeric_limits::digits10 ) }; for( auto const &path : m_paths ) { Output << path.points[ segment_data::point::start ].x << ' ' @@ -2341,6 +2379,7 @@ TTrack::export_as_text_( std::ostream &Output ) const { << path.radius << ' '; } + Output.precision( precision ); // optional attributes std::vector< std::pair< std::string, event_sequence const * > > const eventsequences { { "event0", &m_events0 }, { "eventall0", &m_events0all }, @@ -2379,13 +2418,17 @@ TTrack::export_as_text_( std::ostream &Output ) const { } if( eType == tt_Switch && SwitchExtension->m_material3 != null_handle) { - auto texturefile { GfxRenderer->Material( m_material2 )->GetName() }; + auto texturefile { GfxRenderer->Material( SwitchExtension->m_material3 )->GetName() }; if( texturefile.find( paths::textures ) == 0 ) { // don't include 'textures/' in the path texturefile.erase( 0, std::string{ paths::textures }.size() ); } Output << "trackbed " << texturefile << ' '; } + if( iCategoryFlag == 1 + && false == m_profile1.first.empty() ) { + Output << "railprofile " << m_profile1.first << ' '; + } if( false == m_friction.first.empty() ) { Output << "friction " << m_friction.first << ' '; } diff --git a/world/Track.h b/world/Track.h index 9bf38e0a..33379dfd 100644 --- a/world/Track.h +++ b/world/Track.h @@ -154,6 +154,7 @@ class TTrack : public scene::basic_node friend opengl33_renderer; // NOTE: temporary arrangement friend itemproperties_panel; + friend class editor_track; public: std::vector Isolated; // obwód izolowany obsługujący zajęcia/zwolnienia grupy torów @@ -278,6 +279,10 @@ public: RouteCount() const { return SwitchExtension != nullptr ? SwitchExtension->iRoads - 1 : 1; } void Load(cParser *parser, glm::dvec3 const &pOrigin); + // (re)creates path segments from the source path data in m_paths + void init_segments( bool const Initial ); + // calculates path location from the current segment + void update_location(); bool AssignEvents(); bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus); void QueueEvents( event_sequence const &Events, TDynamicObject const *Owner ); @@ -300,6 +305,8 @@ public: double ActiveLength(); void create_geometry( gfx::geometrybank_handle const &Bank ); // wypełnianie VBO + // discards current render geometry and generates it anew in specified bank + void rebuild_geometry( gfx::geometrybank_handle const &Bank ); void create_map_geometry(std::vector &Bank, const gfx::geometrybank_handle Extra); void get_map_active_paths(map_colored_paths &handles); void get_map_paths_for_state(map_colored_paths &handles, int state);