diff --git a/application/editormode.h b/application/editormode.h index c7dbd3911..4b395ae68 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -639,6 +639,7 @@ class editor_mode : public application_mode, private editor_track::observer glm::mat4 m_track_gizmo{1.0f}; bool m_track_gizmo_using{false}; bool m_track_dirty{false}; + bool m_track_drag_on_the_way{false}; // committed, but not as the end of the drag std::chrono::steady_clock::time_point m_track_last_commit; editor_track::state m_track_field_before; std::array, 3> m_track_materials{}; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 0ae732d86..2523929d8 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -442,12 +442,14 @@ void editor_mode::render_track_gizmo() void editor_mode::commit_track_drag(bool const Force) { - if (false == m_track_dirty || m_track_drag.empty()) + // on letting go the rest of what follows the dragged tracks is yet to come, even with all of the drag committed + if ((false == m_track_dirty && false == (Force && m_track_drag_on_the_way)) || m_track_drag.empty()) return; auto const now = std::chrono::steady_clock::now(); if (false == Force && now - m_track_last_commit < std::chrono::milliseconds(100)) return; - editor_track::commit(m_track_drag); + editor_track::commit(m_track_drag, false == Force); + m_track_drag_on_the_way = false == Force; m_track_dirty = false; m_track_last_commit = now; } diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index b70501747..8e6339913 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -426,7 +426,7 @@ bool editor_track::can_edit_geometry(TTrack const &Track, std::string &Reason) return true; } -void editor_track::commit(std::vector const &Tracks) +void editor_track::commit(std::vector const &Tracks, bool const Dragging) { if (Tracks.empty()) return; @@ -457,6 +457,8 @@ void editor_track::commit(std::vector const &Tracks) add_unique(affected, neighbour); for (auto *track : affected) update_transition(*track); + if (false == Dragging) + add_followers(affected); for (auto *track : affected) rebuild_geometry(*track); for (auto *track : Tracks) @@ -478,6 +480,7 @@ void editor_track::commit_parameters(TTrack &Track) add_unique(affected, neighbour); for (auto *track : affected) update_transition(*track); + add_followers(affected); for (auto *track : affected) rebuild_geometry(*track); Track.mark_dirty(); @@ -2819,14 +2822,25 @@ void editor_track::retire(TTrack &Track) simulation::Region->erase_and_unregister(&Track); Track.m_editorremoved = true; for (auto *neighbour : adjoining) - { update_transition(*neighbour); - rebuild_geometry(*neighbour); - } + auto affected{adjoining}; + add_followers(affected); + for (auto *track : affected) + rebuild_geometry(*track); if (s_observer != nullptr) s_observer->track_retired(Track); } +void editor_track::add_followers(std::vector &Tracks) +{ + ++TTrack::s_layoutstamp; + for (auto *track : TTrack::switch_followers(Tracks)) + add_unique(Tracks, track); + for (auto *track : simulation::Paths.sequence()) + if (false == track->m_editorremoved && track->rail_joints_moved()) + add_unique(Tracks, track); +} + void editor_track::rebuild_geometry(TTrack &Track) { auto §ion{simulation::Region->section(Track.location())}; diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 14f9e9a40..efb5fdac9 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -93,7 +93,8 @@ class editor_track static bool can_edit_geometry(TTrack const &Track, std::string &Reason); static bool is_supported(TTrack const &Track); - static void commit(std::vector const &Tracks); + // Dragging: on the way, what follows them from further off waits for the commit at the end of it + static void commit(std::vector const &Tracks, bool const Dragging = false); static void commit_parameters(TTrack &Track); static glm::dvec3 point_position(TTrack const &Track, point_ref const &Point); @@ -350,6 +351,8 @@ class editor_track static void bind_switch_path(TTrack &Switch, int const Path); static void store_switch_path(TTrack &Switch, int const Path); static void rebuild_geometry(TTrack &Track); + // adds the tracks whose shape goes by a change of the given from further off: by the switches they meet, and by rail joints running on through them + static void add_followers(std::vector &Tracks); static TTrack *clone(TTrack const &Template); static TTrack *load_text(std::string const &Text, TTrack const *Template, std::string const &Name); static void move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End, std::function const &Member); diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp index 06f4663b0..b55de1644 100644 --- a/tests/unit/test_track_uneven.cpp +++ b/tests/unit/test_track_uneven.cpp @@ -38,6 +38,28 @@ void add_chord(std::vector &Output, std::vector const &Line, int } // namespace +TEST_CASE("uneven track: the cab keeps what the ride on the track doesn't stand in for") +{ + auto const replaced = [](double const Up, double const Side, double const Cross) { + return track_uneven::replaced_shake({Up, Side, Cross}); + }; + // all of it, as "uneven 18" + CHECK(replaced(18.0, 18.0, 18.0).sway); + CHECK(replaced(18.0, 18.0, 18.0).jolts); + // nothing at all, as on a track with dips at its joints alone + CHECK_FALSE(replaced(0.0, 0.0, 0.0).sway); + CHECK_FALSE(replaced(0.0, 0.0, 0.0).jolts); + // up and down only: there is nothing to sway with + CHECK_FALSE(replaced(18.0, 0.0, 0.0).sway); + CHECK(replaced(18.0, 0.0, 0.0).jolts); + // sideways only + CHECK(replaced(0.0, 18.0, 0.0).sway); + CHECK(replaced(0.0, 18.0, 0.0).jolts); + // cross level only turns the body + CHECK_FALSE(replaced(0.0, 0.0, 18.0).sway); + CHECK_FALSE(replaced(0.0, 0.0, 18.0).jolts); +} + TEST_CASE("uneven track: measures as the Id-14 limits for its value") { // appendix 1, class C at 80 km/h: 18 mm vertically, 17 mm sideways, 16 mm of twist on 5 m, gauge +10/-8 mm, 20 mm of cross level, J = 5.3 diff --git a/tools/track_uneven_grade.py b/tools/track_uneven_grade.py index 1994559d0..81df2ce2a 100644 --- a/tools/track_uneven_grade.py +++ b/tools/track_uneven_grade.py @@ -261,20 +261,29 @@ def plain_neighbours(t, joints): return [other for key in ("p1", "p2") for other, _, _ in joints.at(t["paths"][0][key]) if other is not t and other["kind"] == "normal"] +def joined_at(t, point, joints, seen): + # the plain tracks joined at a point, and the far ends of the straight paths of the switches joined there + plain, further = [], [] + for other, part, side in joints.at(point): + if other is t or id(part) in seen: + continue + seen.add(id(part)) + if other["kind"] != "switch": + plain.append(other) + elif other["paths"].index(part) != other["bent"]: + further.append(part["p2" if side == "p1" else "p1"]) + return plain, further + + def plain_past_switches(t, point, joints): # the plain tracks reached from a point of a track along the straight paths of switches points, seen, found = [point], set(), [] for _ in range(6): onward = [] for reached in points: - for other, part, side in joints.at(reached): - if other is t or id(part) in seen: - continue - seen.add(id(part)) - if other["kind"] != "switch": - found.append(other) - elif other["paths"].index(part) != other["bent"]: - onward.append(part["p2" if side == "p1" else "p1"]) + plain, further = joined_at(t, reached, joints, seen) + found += plain + onward += further points = onward return found diff --git a/track_uneven_doc.md b/track_uneven_doc.md index 1c3163c50..e4822b971 100644 --- a/track_uneven_doc.md +++ b/track_uneven_doc.md @@ -69,12 +69,12 @@ The rails, the top of the ballast, `sleepermodel`, the axles, the roll of the bo Models from includes (e.g. switch drives and timbers), the terrain and the catenary don't follow it, so at large values a rail shifts away from such a model by a few centimetres. -On a track with unevenness the track itself shakes the cab, in place of two of the sources of shake that were there before. It goes by the track under the vehicle, and a track with only dips at its joints counts too. On tracks with no unevenness everything works as before. +On a track with unevenness the track itself shakes the cab, in place of two of the sources of shake that were there before, as far as it has something to put in their place. It goes by the track under the vehicle, and on a switch by the path set and what its joined tracks allow. Dips at the joints and cross level replace nothing, as they don't shake the cab all the way along or sideways. On tracks with no unevenness everything works as before. | source of the cab shake | on a track with unevenness | |--------------------------------------------------------------------|----------------------------| -| side sway from the lateral acceleration: curve radius and cant, jolts sideways on a damaged switch | off | -| random jolts over 25 km/h | off | +| side sway from the lateral acceleration: curve radius and cant, jolts sideways on a damaged switch | off with a sideways value | +| random jolts over 25 km/h | off with a vertical or a sideways value | | the track itself, from the bend under the axles | added | | vertical jolts at the joints of a damaged track | stays | | fore and aft, when accelerating and braking | stays | diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index 305e12020..c4838395f 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -3381,7 +3381,6 @@ bool TDynamicObject::Update(double dt, double dt1) && MoverParameters->eimc[eimc_p_abed] < 0.001) FzadED = 0; //pętla bezpieczeństwa - bez ED auto const FzadPN = Fzad - FrED * MoverParameters->MED_FrED_factor; - //np = 0; std::vector PrzekrF( np ); float nPrzekrF = 0; bool test = true; @@ -8019,12 +8018,12 @@ TDynamicObject::update_shake( double const Timedelta ) { shakevector *= 0; auto const iVel { std::min( GetVelocity(), 150.0 ) }; - // an uneven track shakes the cab by itself, in place of the sway drawn from the curve and the jolts drawn at random - auto const uneven { MyTrack != nullptr && MyTrack->is_uneven() }; + // an uneven track shakes the cab by itself, in place of the sway drawn from the curve and the jolts drawn at random, as far as it does + auto const replaced { track_uneven::replaced_shake( MyTrack != nullptr ? MyTrack->uneven_underway() : track_uneven::amounts {} ) }; if( iVel > 0.5 ) { // acceleration-driven base shake shakevector += glm::dvec3( - uneven ? 0.0 : -MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway + replaced.sway ? 0.0 : -MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway -MoverParameters->AccVert * Timedelta * Global.ShakingMultiplierUD, -MoverParameters->AccSVBased * Timedelta * 1.5 * Global.ShakingMultiplierBF); // accent acceleration/deceleration // unevenness of the track under the outer axles @@ -8037,7 +8036,7 @@ TDynamicObject::update_shake( double const Timedelta ) { auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) }; - if( false == uneven && LocalRandom( iVel ) > 25.0 ) { + if( false == replaced.jolts && LocalRandom( iVel ) > 25.0 ) { // extra shake at increased velocity shake += ShakeSpring.ComputateForces( glm::dvec3( @@ -8066,11 +8065,12 @@ TDynamicObject::update_shake( double const Timedelta ) { bool TDynamicObject::axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ) const { - // by the outer axle on its side of the vehicle: the two can be on different tracks - auto const &outer { Axle.offset >= 0.0 ? Axle0 : Axle1 }; - if( auto const railphase { outer.RailPhase( Axle.offset - std::copysign( 0.5 * fAxleDist, Axle.offset ) ) }; railphase >= 0.0 ) { + // from the follower on its side of the vehicle, as the two can be on different tracks. They stand fAxleDist apart, at the bogie pivots, and + // the offset of the axle is the place of its wheel set, so the two of a bogie come out half of ADist to either side of the pivot + auto const &follower { Axle.offset >= 0.0 ? Axle0 : Axle1 }; + if( auto const railphase { follower.RailPhase( Axle.offset - std::copysign( 0.5 * fAxleDist, Axle.offset ) ) }; railphase >= 0.0 ) { // the rails under the axle, which aren't always those under the track the vehicle is on - auto const passed { Axle.atjoints && std::abs( railphase - Axle.distance ) > 0.5 * outer.GetTrack()->fSoundDistance }; + auto const passed { Axle.atjoints && std::abs( railphase - Axle.distance ) > 0.5 * follower.GetTrack()->fSoundDistance }; Axle.distance = railphase; Axle.atjoints = true; return passed; diff --git a/world/Segment.h b/world/Segment.h index 099480e36..05522e602 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -59,7 +59,8 @@ class TSegment int path { 0 }; // which path of a switch // where the path meets the next one: the middle of their ends and their common vector across std::array>, 2> joints; - } m_setting; + }; + setting m_setting; double fLength { -1.0 }; // długość policzona std::vector fTsBuffer; // wartości parametru krzywej t dla równych odcinków s std::vector fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach diff --git a/world/Track.cpp b/world/Track.cpp index 49138b4eb..c9c7ae36d 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1375,7 +1375,7 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const // wypełnianie tablic VBO void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { - ++s_layoutstamp; + m_drawnrun = {}; Geometry1.clear(); Geometry2.clear(); @@ -1392,6 +1392,9 @@ void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::userdata_array empty_userdata; share_joints(); + if( rail_phase( 0.0 ) >= 0.0 ) { + m_drawnrun = m_railrun; + } // bake per-instance sleeper transforms now that the owning cell has assigned m_origin. // safe to call here even if the track has no sleepermodel (early-outs internally). if( m_sleeper_enabled && m_sleeper_local_transforms.empty() ) { diff --git a/world/Track.h b/world/Track.h index 72cbd8847..dc6be7412 100644 --- a/world/Track.h +++ b/world/Track.h @@ -348,6 +348,8 @@ public: // the unevenness the track declares, on a path of a switch track_uneven::amounts uneven_declared( int const Path = 0 ) const; bool is_uneven() const; + // the unevenness along what the vehicles take now: the path set on a switch, with what the switches joined and the tracks by them allow + track_uneven::amounts uneven_underway(); // the unevenness at a distance along a path, meeting the tracks joined at its ends track_uneven::amounts uneven_at( double const Distance, int const Path = 0 ); // how much of the displacement a point of the profile at the depth takes @@ -358,6 +360,10 @@ public: double joint_dip( double const Distance ); // m // distance from the rail joint behind; negative: the track has no joints that dip double rail_phase( double const Distance ); + // whether the rail joints are elsewhere than the geometry of the track has them + bool rail_joints_moved(); + // the uneven tracks whose values go by the switches among the given: all the switches joined to those, and the tracks they meet + static std::vector switch_followers( std::vector const &Tracks ); void ConnectionsLog(); bool DoubleSlip() const; static void fetch_default_profiles(); @@ -397,6 +403,7 @@ private: }; void settle_rail_run(); rail_run m_railrun; + rail_run m_drawnrun; // as the geometry has it; stamp -1: it has no joints that dip // the value along each path of a switch: the lowest of that path in the switches joined, 0 next to a track with none. // And at the ends of the paths, by what meets there: the other path at the common end, the path of a switch joined struct switch_stray { @@ -411,10 +418,10 @@ private: // the lowest value of the paths ending at the point track_uneven::amounts switch_paths_at( glm::dvec3 const &Point ) const; // what a plain track comes to at its end, where it meets the other one - track_uneven::amounts uneven_met( TTrack *Other, glm::dvec3 const &Point ); + track_uneven::amounts uneven_met( TTrack *Other, glm::dvec3 const &Point ) const; // the value of a switch where a track joined to it meets it track_uneven::amounts switch_uneven_at( glm::dvec3 const &Point ); - static int s_layoutstamp; // bumped when tracks are joined or changed, invalidates the two above + static int s_layoutstamp; // bumped when tracks are joined or changed, invalidates the two above. Once per change: a run is walked again after each void create_road_profile( gfx::vertex_array &Output, bool const Forcetransition = false ); void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false ); /// diff --git a/world/TrackUneven.cpp b/world/TrackUneven.cpp index 78505ac3a..10bf48d62 100644 --- a/world/TrackUneven.cpp +++ b/world/TrackUneven.cpp @@ -10,6 +10,8 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "world/Track.h" +#include + // The part of TTrack that goes with the unevenness of a track and with what it meets at its ends: joined tracks and switches. // The rules themselves are in TrackUneven.h, here they get applied to the tracks. @@ -64,7 +66,7 @@ void keep_to_plain_track( std::array &Values, TTrack c } // the value along each path: the lowest of that path in the group, 0 in a component that a plain track joined has none of -std::array group_path_values( std::vector const &Group ) { +std::array group_path_values( std::span const Group ) { std::array values { Group.front()->uneven_declared( 0 ), Group.front()->uneven_declared( 1 ) }; for( auto const *track : Group ) { @@ -173,6 +175,11 @@ bool TTrack::is_uneven() const { return any( uneven_declared( 0 ) ) || any( uneven_declared( 1 ) ) || joint_depth() > 0.0; } +track_uneven::amounts TTrack::uneven_underway() { + + return is_switch( this ) ? switch_uneven().paths[ SwitchExtension->CurrentIndex ] : uneven_declared(); +} + double TTrack::uneven_bed_share( double const Depth ) const { return track_uneven::bed_share( Depth, std::abs( track_rail_profile( m_profile1.second ).front().position.y ), fTexHeight1 ); @@ -187,7 +194,7 @@ track_uneven::amounts TTrack::uneven_at( double const Distance, int const Path ) return track_uneven::along( uneven_declared(), uneven_met( trPrev, end_point( *Segment, 0 ) ), uneven_met( trNext, end_point( *Segment, 1 ) ), Distance, Length() ); } -track_uneven::amounts TTrack::uneven_met( TTrack *Other, glm::dvec3 const &Point ) { +track_uneven::amounts TTrack::uneven_met( TTrack *Other, glm::dvec3 const &Point ) const { if( Other == nullptr ) { return {}; @@ -278,6 +285,32 @@ double TTrack::rail_phase( double const Distance ) { return track_uneven::rail_phase( m_railrun.origin + m_railrun.way * Distance, fSoundDistance ); } +bool TTrack::rail_joints_moved() { + + return m_drawnrun.stamp != -1 + && rail_phase( 0.0 ) >= 0.0 + && ( m_railrun.origin != m_drawnrun.origin || m_railrun.way != m_drawnrun.way ); +} + +std::vector TTrack::switch_followers( std::vector const &Tracks ) { + + std::vector followers; + auto const follow = [ &followers ]( TTrack *Track ) { + if( Track != nullptr && Track->is_uneven() && std::ranges::find( followers, Track ) == followers.end() ) { + followers.push_back( Track ); + } }; + for( auto *track : Tracks ) { + if( false == is_switch( track ) ) { + continue; + } + for( auto *member : switch_group( *track ) ) { + follow( member ); + std::ranges::for_each( joined_to_switch( *member ), follow ); + } + } + return followers; +} + void TTrack::settle_rail_run() { // the next track of the run past an end of a track, and which of its ends is joined there diff --git a/world/TrackUneven.h b/world/TrackUneven.h index 2c32ad660..8e0f07750 100644 --- a/world/TrackUneven.h +++ b/world/TrackUneven.h @@ -43,6 +43,18 @@ inline amounts least( amounts const &Left, amounts const &Right ) { return { std::min( Left.up, Right.up ), std::min( Left.side, Right.side ), std::min( Left.cross, Right.cross ) }; } +// which of the other sources of the cab shake the ride on a track stands in for +struct shake_sources { + bool sway { false }; // from the lateral acceleration + bool jolts { false }; // drawn at random +}; + +// sideways only with something going sideways. Cross level turns the body, and the dips at the joints come and go: neither makes up for any of them +inline shake_sources replaced_shake( amounts const &Amount ) { + + return { Amount.side > 0.0, Amount.up > 0.0 || Amount.side > 0.0 }; +} + struct wave { double x; double z;