diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index cb17e30a..954e277b 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/parser.h" #include "simulation/simulation.h" +#include #include //--------------------------------------------------------------------------- @@ -51,8 +52,13 @@ double ComputeCollision(double &v1, double &v2, double m1, double m2, double bet double sum = m1 + m2; double w1 = (m2 * v2 * 2.0 + v1 * (m1 - m2)) / sum; double w2 = (m1 * v1 * 2.0 + v2 * (m2 - m1)) / sum; - v1 = w1 * std::sqrt(1.0 - beta); // niejawna zmiana predkosci wskutek zderzenia - v2 = w2 * std::sqrt(1.0 - beta); + // the collision changes the velocities directly: the bounce loses energy in the velocities relative to the + // centre of mass only, the pair keeps its momentum. scaling the velocities themselves braked the vehicles + // down with every step their couplers spent stretched or pressed in all the way (eu07/maszyna#553) + auto const centre{(m1 * v1 + m2 * v2) / sum}; + auto const restitution{std::sqrt(1.0 - beta)}; + v1 = std::lerp(centre, w1, restitution); + v2 = std::lerp(centre, w2, restitution); return m1 * (w2 - w1) * (1 - beta); } } @@ -10695,12 +10701,15 @@ void TMoverParameters::LoadFIZ_BuffCoupl(std::string const &line, int const Inde coupler->beta = 0.55; */ thiscoupler->SpringKC = 4500 * 1000; - thiscoupler->DmaxC = 0.05; thiscoupler->FmaxC = 850 * 1000; thiscoupler->SpringKB = 9200 * 1000; - thiscoupler->DmaxB = 0.05; thiscoupler->FmaxB = 320 * 1000; thiscoupler->beta = 0.55; + // the spring force is taken up to Dmax only, past it the vehicles collide (CollisionDetect()); so the joint + // carries the full FmaxC either way before it gives. with 0.05 m it carried 225 kN, short of what a loaded + // articulated wagon in a freight train takes, which then collided at every step (eu07/maszyna#553) + thiscoupler->DmaxC = std::max(0.05, thiscoupler->FmaxC / thiscoupler->SpringKC); + thiscoupler->DmaxB = std::max(0.05, thiscoupler->FmaxC / thiscoupler->SpringKB); } if (Index == 0) diff --git a/tests/unit/test_couplers.cpp b/tests/unit/test_couplers.cpp index 8787a419..2a0ff6e5 100644 --- a/tests/unit/test_couplers.cpp +++ b/tests/unit/test_couplers.cpp @@ -456,3 +456,72 @@ TEST_CASE("couplers: the coupler force of vehicles facing each other lands on th CHECK(second->Couplers[end::front].CForce == 0.0); } } + +// eu07/maszyna#553: articulated wagons (T3000e, N-234 and the like) set up with articulated couplers barely moved in a +// freight train. the joint took the spring force up to 5 cm only, 225 kN, so it overstretched under the train and the +// sections collided at every step; and each collision scaled the velocities of both vehicles down, rather than only +// the velocity of one relative to the other, braking the whole train + +namespace { + +// the wagon from data/wagon.json, loaded, with articulated couplers at both ends +vehicle articulated_wagon(double Velocity, std::string const &Name) { + return vehicle{definition::load("wagon").set("Param", "M", 60000).set("BuffCoupl", "CType", "Articulated"), Velocity, Name}; +} + +// couples the facing ends of the two vehicles for good, as the scenery does for an articulated set +void join(vehicle &First, int FirstEnd, vehicle &Second, int SecondEnd) { + REQUIRE(First->Attach(FirstEnd, SecondEnd, &Second.mover(), coupling::coupler, true, false)); + REQUIRE(Second->Attach(SecondEnd, FirstEnd, &First.mover(), coupling::coupler, true, false)); +} + +} // namespace + +TEST_CASE("couplers: an articulated joint takes its full tensile force within its travel") { + auto wagon{articulated_wagon(0.0, "wagon")}; + auto const &joint{wagon->Couplers[end::front]}; + REQUIRE(joint.type() == TCouplerType::Articulated); + + CHECK(joint.SpringKC * joint.DmaxC >= doctest::Approx(joint.FmaxC)); + CHECK(joint.SpringKB * joint.DmaxB >= doctest::Approx(joint.FmaxC)); + // and no less than it had before + CHECK(joint.DmaxC >= 0.05); + CHECK(joint.DmaxB >= 0.05); +} + +TEST_CASE("couplers: an articulated set keeps its momentum when one section jerks the other") { + // the front section moving, the rear one barely (so that its brakes are released), joined: the jerk overstretches the joint, the sections collide + // and run on together at half the speed, as the momentum they have between them dictates + auto front_section{articulated_wagon(20.0, "front")}; + auto rear_section{articulated_wagon(0.1, "rear")}; + join(rear_section, end::front, front_section, end::rear); + test::pair pair{rear_section, end::front, front_section, end::rear, 0.0}; + REQUIRE(pair.coupled()); + // brakes released, as a locomotive keeps them + front_section.hold_brake_pipe(5.0); + rear_section.hold_brake_pipe(5.0); + + pair.run(3.0, test::track{0.15, 0.0}); + + CHECK(front_section.speed() == doctest::Approx(rear_section.speed()).epsilon(0.1)); + // rolling resistance takes a little in the 3 s, the collisions used to take most of it + CHECK(0.5 * (front_section.speed() + rear_section.speed()) > 9.0); + CHECK(0.5 * (front_section.speed() + rear_section.speed()) < 10.05 + 1e-6); + CHECK(pair.coupled()); +} + +TEST_CASE("couplers: a collision of coupled vehicles keeps the momentum of the pair") { + // the same jerk through screw couplers, which collide when stretched beyond their travel as well + auto front_wagon{vehicle{definition::load("wagon"), 30.0, "front"}}; + auto rear_wagon{vehicle{definition::load("wagon"), 0.1, "rear"}}; + join(rear_wagon, end::front, front_wagon, end::rear); + test::pair pair{rear_wagon, end::front, front_wagon, end::rear, 0.0}; + REQUIRE(pair.coupled()); + front_wagon.hold_brake_pipe(5.0); + rear_wagon.hold_brake_pipe(5.0); + + pair.run(3.0, test::track{0.15, 0.0}); + + CHECK(0.5 * (front_wagon.speed() + rear_wagon.speed()) > 13.5); + CHECK(0.5 * (front_wagon.speed() + rear_wagon.speed()) < 15.05 + 1e-6); +}