mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-19 03:49:19 +02:00
fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds
- automatic couplers don't catch above the safe collision speed (#86): with crash damage on they couple at a closing speed of 15 km/h at most, a faster hit locks the coupling until the vehicles part and the collision runs its course. On the way: CollisionVelocity() read a head-on 10 + 10 km/h as 14.1 km/h (quaternions turn by half the angle), it's now taken from the touching ends; CollisionDetect() took the other vehicle's speed the wrong way round for vehicles facing each other. Head-on hits now derail sooner, at the intended limit, - a single horn lever can't sound both tones at once (eu07/maszyna#392), - a skid ends once the vehicle stops on locked wheels (eu07/maszyna#479), - electronic destination signs are blanked at the end of the timetable (eu07/maszyna#446), - horns need the air or the voltage to sound (eu07/maszyna#290, #76): HornType=[Pneumatic]|Electric and Horn3Type in Cntrl.; a pneumatic horn needs 0.2 MPa in the main reservoir (or the main pipe without one), an electric one the low voltage; the lever stays where it was put, - compressor start delay (#67): CompressorStartDelay [s] in Brake., counted from the compressor getting its power, after the engine starts say; none by default, - the channel and volume buttons of a radio without power do nothing (eu07/maszyna#429); the power check of the radio is TMoverParameters::RadioPowered(), - a lever moved smoothly sounds the positions it passes (#83). Sonar findings in the functions changed: nesting, nested conditionals, commented-out code, std::format, references to const. Unit tests: test::pair for two vehicles running into each other, test_couplers, test_horn, test_wheelslip, test_compressor, test_radio, test_gauge_positions.
This commit is contained in:
@@ -56,8 +56,10 @@ TEST_CASE("brakes: service braking stops a heavier wagon too") {
|
||||
functions on the vehicle state the class calls, and the test calls them the same way: see
|
||||
`vehicle/handover.h`, which `TController::TakeControl()` uses, and `test_handover.cpp`.
|
||||
- `vehicle` creates the vehicle the way `TDynamicObject::Init()` does and steps it the way the
|
||||
simulation loop does, on a straight track of the given friction and gradient. Coupled vehicles
|
||||
aren't supported yet.
|
||||
simulation loop does, on a straight track of the given friction and gradient. `pair` puts two of
|
||||
them on that track facing each other with the given ends and runs them together, as the
|
||||
simulation runs the vehicles of a scene: the coupler and buffer forces, collisions and automatic
|
||||
coupling come from the physics (`test_couplers.cpp`). Longer consists aren't supported yet.
|
||||
- Check behaviour (speeds, pressures, states the player sees) rather than internal flags, and make
|
||||
the test check its own preconditions (`REQUIRE` that the braking skidded, before checking what a
|
||||
skid leads to).
|
||||
|
||||
80
tests/unit/test_compressor.cpp
Normal file
80
tests/unit/test_compressor.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
using test::definition;
|
||||
using test::vehicle;
|
||||
|
||||
// the compressor waiting a while after getting its power before it starts (CompressorStartDelay,
|
||||
// MaSzyna-EU07/maszyna#67): in a diesel it starts some time after the engine, rather than with it
|
||||
|
||||
namespace {
|
||||
|
||||
double const step{0.01};
|
||||
|
||||
// a compressor fed from the main circuit, which starts on its own, and an empty main reservoir for it to fill
|
||||
vehicle compressor_vehicle(double const Delay) {
|
||||
auto definition{definition::load("wagon")};
|
||||
definition.set("Brake", "Vv", 1.0).set("Brake", "MinCP", 0.65).set("Brake", "MaxCP", 0.8);
|
||||
definition.set("Brake", "CompressorSpeed", 0.01).set("Brake", "CompressorPower", "Main");
|
||||
definition.set("Cntrl", "CompressorStart", "Automatic");
|
||||
if (Delay > 0.0) {
|
||||
definition.set("Brake", "CompressorStartDelay", Delay);
|
||||
}
|
||||
vehicle vehicle{definition};
|
||||
vehicle->CompressedVolume = 0.0;
|
||||
vehicle->LastSwitchingTime = 10.0; // nothing was switched lately
|
||||
return vehicle;
|
||||
}
|
||||
|
||||
// runs the compressor for Seconds, the main circuit as it is, the time since the last switching going on as in
|
||||
// ComputeTotalForce()
|
||||
void run_compressor(vehicle &Vehicle, double const Seconds) {
|
||||
for (auto i{std::lround(Seconds / step)}; i > 0; --i) {
|
||||
Vehicle->LastSwitchingTime += step;
|
||||
Vehicle->CompressorCheck(step);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("compressor: without a start delay the compressor starts with its power") {
|
||||
auto vehicle{compressor_vehicle(0.0)};
|
||||
run_compressor(vehicle, 1.0);
|
||||
REQUIRE_FALSE(vehicle->CompressorFlag); // no power, no compressor
|
||||
|
||||
vehicle->Mains = true;
|
||||
run_compressor(vehicle, step);
|
||||
CHECK(vehicle->CompressorFlag);
|
||||
}
|
||||
|
||||
TEST_CASE("compressor: the compressor starts the delay after getting its power") {
|
||||
auto vehicle{compressor_vehicle(5.0)};
|
||||
vehicle->Mains = true;
|
||||
|
||||
run_compressor(vehicle, 4.9);
|
||||
CHECK_FALSE(vehicle->CompressorFlag);
|
||||
CHECK(vehicle->Compressor == doctest::Approx(0.0));
|
||||
|
||||
run_compressor(vehicle, 0.2);
|
||||
CHECK(vehicle->CompressorFlag);
|
||||
run_compressor(vehicle, 10.0);
|
||||
CHECK(vehicle->Compressor > 0.0); // and fills the reservoir
|
||||
}
|
||||
|
||||
TEST_CASE("compressor: the delay starts over once the power is gone") {
|
||||
auto vehicle{compressor_vehicle(5.0)};
|
||||
vehicle->Mains = true;
|
||||
run_compressor(vehicle, 10.0);
|
||||
REQUIRE(vehicle->CompressorFlag);
|
||||
|
||||
vehicle->Mains = false; // the engine stopped
|
||||
run_compressor(vehicle, 1.0);
|
||||
CHECK_FALSE(vehicle->CompressorFlag);
|
||||
|
||||
vehicle->Mains = true; // and started again
|
||||
run_compressor(vehicle, 4.9);
|
||||
CHECK_FALSE(vehicle->CompressorFlag);
|
||||
run_compressor(vehicle, 0.2);
|
||||
CHECK(vehicle->CompressorFlag);
|
||||
}
|
||||
422
tests/unit/test_couplers.cpp
Normal file
422
tests/unit/test_couplers.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
using test::definition;
|
||||
using test::vehicle;
|
||||
|
||||
namespace {
|
||||
|
||||
// the wagon from data/wagon.json with automatic couplers at both ends, which couple the given connections
|
||||
definition automatic_definition(int Flag = coupling::coupler | coupling::brakehose) {
|
||||
return definition::load("wagon").set("BuffCoupl", "CType", "Automatic").set("BuffCoupl", "AutomaticFlag", Flag);
|
||||
}
|
||||
|
||||
// the wagon from data/wagon.json with automatic couplers at both ends, running at the given speed [km/h]
|
||||
vehicle automatic_wagon(double Velocity, std::string const &Name) {
|
||||
return vehicle{automatic_definition(), Velocity, Name};
|
||||
}
|
||||
|
||||
// the same wagon, twice as heavy
|
||||
vehicle heavy_automatic_wagon(double Velocity, std::string const &Name) {
|
||||
return vehicle{automatic_definition().set("Param", "M", 2 * 42200), Velocity, Name};
|
||||
}
|
||||
|
||||
bool derailed(vehicle &Vehicle) {
|
||||
return TestFlag(Vehicle->DamageFlag, dtrain_out);
|
||||
}
|
||||
|
||||
// runs the pair for Seconds, returns whether the vehicles were coupled at any point
|
||||
bool couple_during(test::pair &Pair, double Seconds) {
|
||||
auto coupled{false};
|
||||
for (auto i{std::lround(Seconds / 0.01)}; i > 0; --i) {
|
||||
Pair.step();
|
||||
coupled = coupled || Pair.coupled();
|
||||
}
|
||||
return coupled;
|
||||
}
|
||||
|
||||
// sets the crash damage setting for the test, restores it after
|
||||
class crash_damage_setting {
|
||||
public:
|
||||
explicit crash_damage_setting(bool Enabled) : m_previous{Global.crash_damage} { Global.crash_damage = Enabled; }
|
||||
~crash_damage_setting() { Global.crash_damage = m_previous; }
|
||||
crash_damage_setting(crash_damage_setting const &) = delete;
|
||||
crash_damage_setting &operator=(crash_damage_setting const &) = delete;
|
||||
|
||||
private:
|
||||
bool m_previous;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// issue #86: automatic couplers used to catch at any closing speed, so the vehicles never collided nor derailed
|
||||
|
||||
TEST_CASE("couplers: automatic couplers catch a standing wagon hit at a walking pace") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto moving{automatic_wagon(10.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
REQUIRE(moving->Couplers[end::front].type() == TCouplerType::Automatic);
|
||||
REQUIRE(moving.speed() == doctest::Approx(10.0));
|
||||
|
||||
// the front of the moving wagon runs into the rear of the standing one
|
||||
CHECK(moving->CollisionVelocity(end::front, standing.mover(), end::rear) == doctest::Approx(10.0));
|
||||
CHECK(moving->AutomaticCouplingSafe(end::front, standing.mover(), end::rear));
|
||||
CHECK(standing->AutomaticCouplingSafe(end::rear, moving.mover(), end::front));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: automatic couplers don't catch a wagon hit too fast, the wagons collide instead") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto moving{automatic_wagon(30.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
REQUIRE(moving.speed() == doctest::Approx(30.0));
|
||||
|
||||
CHECK(moving->CollisionVelocity(end::front, standing.mover(), end::rear) > TMoverParameters::SafeCollisionVelocity);
|
||||
CHECK_FALSE(moving->AutomaticCouplingSafe(end::front, standing.mover(), end::rear));
|
||||
CHECK_FALSE(standing->AutomaticCouplingSafe(end::rear, moving.mover(), end::front));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: automatic couplers catch a wagon running ahead only a little slower") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto faster{automatic_wagon(60.0, "faster")};
|
||||
auto slower{automatic_wagon(50.0, "slower")};
|
||||
|
||||
CHECK(faster->CollisionVelocity(end::front, slower.mover(), end::rear) == doctest::Approx(10.0));
|
||||
CHECK(slower->CollisionVelocity(end::rear, faster.mover(), end::front) == doctest::Approx(10.0));
|
||||
CHECK(faster->AutomaticCouplingSafe(end::front, slower.mover(), end::rear));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: without crash damage automatic couplers catch at any speed") {
|
||||
crash_damage_setting const crashdamage{false};
|
||||
auto moving{automatic_wagon(30.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
|
||||
CHECK(moving->AutomaticCouplingSafe(end::front, standing.mover(), end::rear));
|
||||
}
|
||||
|
||||
// the closing speed used to treat the speed of each vehicle as a vector turned by half its heading, so for vehicles
|
||||
// facing each other it came out as the root of the sum of the squares of their speeds, rather than the sum
|
||||
|
||||
TEST_CASE("couplers: the closing speed of a rear-end collision is the difference of the speeds") {
|
||||
auto faster{automatic_wagon(40.0, "faster")};
|
||||
auto slower{automatic_wagon(25.0, "slower")};
|
||||
|
||||
// running forwards, the front of the faster wagon hits the rear of the slower one
|
||||
CHECK(faster->CollisionVelocity(end::front, slower.mover(), end::rear) == doctest::Approx(15.0));
|
||||
CHECK(slower->CollisionVelocity(end::rear, faster.mover(), end::front) == doctest::Approx(15.0));
|
||||
// the wagons running ahead of each other are parting
|
||||
CHECK(slower->CollisionVelocity(end::front, faster.mover(), end::rear) == doctest::Approx(-15.0));
|
||||
|
||||
// backing up, the rear of the faster wagon hits the front of the slower one
|
||||
auto backingfaster{automatic_wagon(-40.0, "backing faster")};
|
||||
auto backingslower{automatic_wagon(-25.0, "backing slower")};
|
||||
CHECK(backingfaster->CollisionVelocity(end::rear, backingslower.mover(), end::front) == doctest::Approx(15.0));
|
||||
CHECK(backingslower->CollisionVelocity(end::front, backingfaster.mover(), end::rear) == doctest::Approx(15.0));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: the closing speed of a head-on collision is the sum of the speeds") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto first{automatic_wagon(10.0, "first")};
|
||||
auto second{automatic_wagon(10.0, "second")};
|
||||
|
||||
// both wagons run forwards, their fronts meet
|
||||
CHECK(first->CollisionVelocity(end::front, second.mover(), end::front) == doctest::Approx(20.0));
|
||||
CHECK(second->CollisionVelocity(end::front, first.mover(), end::front) == doctest::Approx(20.0));
|
||||
// 20 km/h is above the limit, the couplers don't catch (the old formula gave 14.1 km/h and coupled them)
|
||||
CHECK(first->CollisionVelocity(end::front, second.mover(), end::front) > TMoverParameters::SafeCollisionVelocity);
|
||||
CHECK_FALSE(first->AutomaticCouplingSafe(end::front, second.mover(), end::front));
|
||||
CHECK_FALSE(second->AutomaticCouplingSafe(end::front, first.mover(), end::front));
|
||||
|
||||
// both wagons back up, their rears meet
|
||||
auto firstbacking{automatic_wagon(-10.0, "first backing")};
|
||||
auto secondbacking{automatic_wagon(-10.0, "second backing")};
|
||||
CHECK(firstbacking->CollisionVelocity(end::rear, secondbacking.mover(), end::rear) == doctest::Approx(20.0));
|
||||
|
||||
// running back to back, the wagons part
|
||||
CHECK(first->CollisionVelocity(end::rear, second.mover(), end::rear) == doctest::Approx(-20.0));
|
||||
CHECK(first->AutomaticCouplingSafe(end::rear, second.mover(), end::rear));
|
||||
|
||||
// slow enough, the couplers catch
|
||||
auto slowfirst{automatic_wagon(5.0, "slow first")};
|
||||
auto slowsecond{automatic_wagon(5.0, "slow second")};
|
||||
CHECK(slowfirst->CollisionVelocity(end::front, slowsecond.mover(), end::front) == doctest::Approx(10.0));
|
||||
CHECK(slowfirst->AutomaticCouplingSafe(end::front, slowsecond.mover(), end::front));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: the closing speed of a hit on a standing vehicle is the speed of the moving one, whichever way it faces") {
|
||||
auto moving{automatic_wagon(20.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
|
||||
// the standing wagon faces away from the moving one
|
||||
CHECK(moving->CollisionVelocity(end::front, standing.mover(), end::rear) == doctest::Approx(20.0));
|
||||
CHECK(standing->CollisionVelocity(end::rear, moving.mover(), end::front) == doctest::Approx(20.0));
|
||||
// the standing wagon faces the moving one
|
||||
CHECK(moving->CollisionVelocity(end::front, standing.mover(), end::front) == doctest::Approx(20.0));
|
||||
CHECK(standing->CollisionVelocity(end::front, moving.mover(), end::front) == doctest::Approx(20.0));
|
||||
// the moving wagon backs up into the standing one
|
||||
auto backing{automatic_wagon(-20.0, "backing")};
|
||||
CHECK(backing->CollisionVelocity(end::rear, standing.mover(), end::front) == doctest::Approx(20.0));
|
||||
CHECK(backing->CollisionVelocity(end::rear, standing.mover(), end::rear) == doctest::Approx(20.0));
|
||||
// the moving wagon runs away from the standing one
|
||||
CHECK(moving->CollisionVelocity(end::rear, standing.mover(), end::front) == doctest::Approx(-20.0));
|
||||
}
|
||||
|
||||
// the coupling as the simulation runs it: two vehicles on a track, closing in on each other until the couplers (or
|
||||
// buffers) meet, the coupler forces and the collision computed by the physics, step by step
|
||||
|
||||
TEST_CASE("couplers: automatic couplers catch right up to the safe closing speed") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
|
||||
auto below{automatic_wagon(14.9, "below")};
|
||||
CHECK(below->CollisionVelocity(end::front, standing.mover(), end::rear) == doctest::Approx(14.9));
|
||||
CHECK(below->AutomaticCouplingSafe(end::front, standing.mover(), end::rear));
|
||||
|
||||
auto above{automatic_wagon(15.1, "above")};
|
||||
CHECK(above->CollisionVelocity(end::front, standing.mover(), end::rear) == doctest::Approx(15.1));
|
||||
CHECK_FALSE(above->AutomaticCouplingSafe(end::front, standing.mover(), end::rear));
|
||||
auto faster{automatic_wagon(15.5, "faster")};
|
||||
CHECK_FALSE(faster->AutomaticCouplingSafe(end::front, standing.mover(), end::rear));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: a wagon rolling slowly into a standing one couples with it") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto moving{automatic_wagon(5.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
test::pair pair{moving, end::front, standing, end::rear, 1.0};
|
||||
|
||||
pair.run(3.0);
|
||||
|
||||
REQUIRE(pair.coupled());
|
||||
CHECK(moving->Couplers[end::front].CouplingFlag == (coupling::coupler | coupling::brakehose));
|
||||
CHECK(standing->Couplers[end::rear].CouplingFlag == (coupling::coupler | coupling::brakehose));
|
||||
CHECK(moving->Couplers[end::front].Connected == &standing.mover());
|
||||
CHECK_FALSE(derailed(moving));
|
||||
CHECK_FALSE(derailed(standing));
|
||||
// coupled, they run on together
|
||||
pair.run(2.0);
|
||||
CHECK(moving.speed() == doctest::Approx(standing.speed()).epsilon(0.05));
|
||||
CHECK(pair.gap() < 0.2);
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: a wagon hitting a standing one too fast doesn't couple, nor once the hit has slowed it down") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto moving{automatic_wagon(30.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
test::pair pair{moving, end::front, standing, end::rear, 1.0};
|
||||
|
||||
auto touched{false};
|
||||
auto parted{false};
|
||||
auto coupled{false};
|
||||
auto locked{true};
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
pair.step();
|
||||
// as far as the couplers know: the distance between them at the start of the step
|
||||
auto const distance{moving->Neighbours[end::front].distance};
|
||||
touched = touched || distance < 0.0;
|
||||
parted = parted || (touched && distance > 0.05);
|
||||
coupled = coupled || pair.coupled();
|
||||
if (touched && false == parted) {
|
||||
locked = locked && false == moving->Couplers[end::front].AutomaticCouplingAllowed && false == standing->Couplers[end::rear].AutomaticCouplingAllowed;
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(touched);
|
||||
CHECK_FALSE(coupled);
|
||||
// the hit derailed and stopped them, the couplers next to each other, the automatic coupling locked all along
|
||||
CHECK_FALSE(parted);
|
||||
CHECK(locked);
|
||||
CHECK(derailed(moving));
|
||||
CHECK(derailed(standing));
|
||||
CHECK(std::abs(moving.speed()) < 3.0);
|
||||
CHECK(std::abs(standing.speed()) < 3.0);
|
||||
CHECK_FALSE(couple_during(pair, 5.0));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: the automatic coupling locked by a hit is unlocked once the vehicles part") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto moving{automatic_wagon(3.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
// as a hit too fast or manual uncoupling leaves them
|
||||
moving->Couplers[end::front].AutomaticCouplingAllowed = false;
|
||||
standing->Couplers[end::rear].AutomaticCouplingAllowed = false;
|
||||
|
||||
SUBCASE("parted by more than 5 cm, they couple on the next approach") {
|
||||
test::pair pair{moving, end::front, standing, end::rear, 0.5};
|
||||
pair.step();
|
||||
CHECK(moving->Couplers[end::front].AutomaticCouplingAllowed);
|
||||
CHECK(couple_during(pair, 3.0));
|
||||
}
|
||||
SUBCASE("kept together, they don't") {
|
||||
test::pair pair{moving, end::front, standing, end::rear, 0.0};
|
||||
auto coupled{false};
|
||||
auto parted{false};
|
||||
for (int i = 0; i < 300 && false == parted; ++i) {
|
||||
pair.step();
|
||||
parted = pair.gap() > 0.05;
|
||||
coupled = coupled || (false == parted && pair.coupled());
|
||||
}
|
||||
CHECK_FALSE(coupled);
|
||||
}
|
||||
}
|
||||
|
||||
// the couplers refuse a hit by the closing speed when they meet, the derail check (CollisionDetect()) takes the one
|
||||
// left when the buffers are pressed in all the way, after their springs have slowed the hit down: for the wagons here
|
||||
// by about 7 km/h. so a hit a little over the safe closing speed only bounces the vehicles apart
|
||||
|
||||
TEST_CASE("couplers: a hit a little too fast for the couplers bounces the vehicles apart, uncoupled and on the rails") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto moving{automatic_wagon(20.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
test::pair pair{moving, end::front, standing, end::rear, 1.0};
|
||||
|
||||
CHECK_FALSE(couple_during(pair, 3.0));
|
||||
CHECK_FALSE(derailed(moving));
|
||||
CHECK_FALSE(derailed(standing));
|
||||
CHECK(pair.gap() > 0.5);
|
||||
CHECK(standing.speed() > moving.speed());
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: a hit too fast for the couplers derails the vehicles over the limit adjusted for their mass ratio") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
|
||||
SUBCASE("equal masses: both derail") {
|
||||
auto moving{automatic_wagon(30.0, "moving")};
|
||||
auto standing{automatic_wagon(0.0, "standing")};
|
||||
test::pair pair{moving, end::front, standing, end::rear, 1.0};
|
||||
CHECK_FALSE(couple_during(pair, 5.0));
|
||||
CHECK(derailed(moving));
|
||||
CHECK(derailed(standing));
|
||||
}
|
||||
SUBCASE("half the mass: between the safe closing speed and twice that, only the lighter one derails") {
|
||||
auto light{automatic_wagon(27.0, "light")};
|
||||
auto heavy{heavy_automatic_wagon(0.0, "heavy")};
|
||||
test::pair pair{light, end::front, heavy, end::rear, 1.0};
|
||||
CHECK_FALSE(couple_during(pair, 5.0));
|
||||
CHECK(derailed(light));
|
||||
CHECK_FALSE(derailed(heavy));
|
||||
}
|
||||
SUBCASE("half the mass: above twice the safe closing speed both derail") {
|
||||
auto light{automatic_wagon(45.0, "light")};
|
||||
auto heavy{heavy_automatic_wagon(0.0, "heavy")};
|
||||
test::pair pair{light, end::front, heavy, end::rear, 1.0};
|
||||
CHECK_FALSE(couple_during(pair, 5.0));
|
||||
CHECK(derailed(light));
|
||||
CHECK(derailed(heavy));
|
||||
}
|
||||
SUBCASE("half the mass: below the safe closing speed the couplers catch, nothing derails") {
|
||||
auto light{automatic_wagon(12.0, "light")};
|
||||
auto heavy{heavy_automatic_wagon(0.0, "heavy")};
|
||||
test::pair pair{light, end::front, heavy, end::rear, 1.0};
|
||||
CHECK(couple_during(pair, 5.0));
|
||||
CHECK_FALSE(derailed(light));
|
||||
CHECK_FALSE(derailed(heavy));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: wagons running head-on into each other couple only up to the safe closing speed") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
|
||||
SUBCASE("7 + 7 km/h, the couplers catch") {
|
||||
auto first{automatic_wagon(7.0, "first")};
|
||||
auto second{automatic_wagon(7.0, "second")};
|
||||
test::pair pair{first, end::front, second, end::front, 1.0};
|
||||
CHECK(couple_during(pair, 3.0));
|
||||
CHECK_FALSE(derailed(first));
|
||||
CHECK_FALSE(derailed(second));
|
||||
}
|
||||
SUBCASE("8 + 8 km/h, the wagons bounce apart") {
|
||||
auto first{automatic_wagon(8.0, "first")};
|
||||
auto second{automatic_wagon(8.0, "second")};
|
||||
test::pair pair{first, end::front, second, end::front, 1.0};
|
||||
CHECK_FALSE(couple_during(pair, 3.0));
|
||||
CHECK_FALSE(derailed(first));
|
||||
CHECK_FALSE(derailed(second));
|
||||
// both backwards, away from each other
|
||||
CHECK(first.speed() < 0.0);
|
||||
CHECK(second.speed() < 0.0);
|
||||
}
|
||||
SUBCASE("backing up, 8 + 8 km/h, the wagons bounce apart") {
|
||||
auto first{automatic_wagon(-8.0, "first")};
|
||||
auto second{automatic_wagon(-8.0, "second")};
|
||||
test::pair pair{first, end::rear, second, end::rear, 1.0};
|
||||
CHECK_FALSE(couple_during(pair, 3.0));
|
||||
CHECK(first.speed() > 0.0);
|
||||
CHECK(second.speed() > 0.0);
|
||||
}
|
||||
SUBCASE("15 + 15 km/h, both derail") {
|
||||
auto first{automatic_wagon(15.0, "first")};
|
||||
auto second{automatic_wagon(15.0, "second")};
|
||||
test::pair pair{first, end::front, second, end::front, 1.0};
|
||||
CHECK_FALSE(couple_during(pair, 5.0));
|
||||
CHECK(derailed(first));
|
||||
CHECK(derailed(second));
|
||||
}
|
||||
}
|
||||
|
||||
// the collision used to take the speed of a vehicle facing the other way as if it faced the same way, so a wagon
|
||||
// pushed away by the buffers of the vehicle hitting it head-on counted as running into it: the hit slowed it down,
|
||||
// and the hitter bounced back as from a wall
|
||||
|
||||
TEST_CASE("couplers: a wagon hit head-on runs off the same as one hit from behind") {
|
||||
// without automatic couplers or crash damage, the collision alone
|
||||
crash_damage_setting const crashdamage{false};
|
||||
auto speeds_after_hit = [](int StruckEnd) {
|
||||
auto moving{vehicle{definition::load("wagon"), 20.0, "moving"}};
|
||||
auto struck{vehicle{definition::load("wagon"), 0.0, "struck"}};
|
||||
test::pair pair{moving, end::front, struck, StruckEnd, 0.5};
|
||||
auto touched{false};
|
||||
for (int i = 0; i < 300 && false == (touched && pair.gap() > 0.3); ++i) {
|
||||
pair.step();
|
||||
touched = touched || pair.gap() < 0.0;
|
||||
}
|
||||
REQUIRE(touched);
|
||||
REQUIRE(pair.gap() > 0.3);
|
||||
REQUIRE_FALSE(pair.coupled());
|
||||
return std::make_pair(moving.speed(), struck.speed());
|
||||
};
|
||||
|
||||
auto const [hitterfrombehind, struckfrombehind]{speeds_after_hit(end::rear)};
|
||||
auto const [hitterheadon, struckheadon]{speeds_after_hit(end::front)};
|
||||
|
||||
// hit from behind, the struck wagon runs off forwards, faster than the hitter
|
||||
CHECK(struckfrombehind > 10.0);
|
||||
CHECK(hitterfrombehind < struckfrombehind);
|
||||
// hit head-on, it runs off backwards, as fast
|
||||
CHECK(struckheadon == doctest::Approx(-struckfrombehind).epsilon(0.02));
|
||||
CHECK(hitterheadon == doctest::Approx(hitterfrombehind).epsilon(0.05));
|
||||
}
|
||||
|
||||
// the other conditions of automatic coupling
|
||||
|
||||
TEST_CASE("couplers: an automatic coupler doesn't catch a coupler of another type") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto moving{automatic_wagon(5.0, "moving")};
|
||||
auto standing{vehicle{definition::load("wagon"), 0.0, "standing"}};
|
||||
REQUIRE(standing->Couplers[end::rear].type() == TCouplerType::Screw);
|
||||
test::pair pair{moving, end::front, standing, end::rear, 1.0};
|
||||
|
||||
CHECK_FALSE(couple_during(pair, 3.0));
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: automatic couplers of different control systems couple without the control connection") {
|
||||
crash_damage_setting const crashdamage{true};
|
||||
auto const flag{coupling::coupler | coupling::brakehose | coupling::control};
|
||||
auto moving{vehicle{automatic_definition(flag).set("BuffCoupl", "ControlType", "first"), 5.0, "moving"}};
|
||||
|
||||
SUBCASE("the same control system") {
|
||||
auto standing{vehicle{automatic_definition(flag).set("BuffCoupl", "ControlType", "first"), 0.0, "standing"}};
|
||||
REQUIRE(standing->Couplers[end::rear].control_type == "first");
|
||||
test::pair pair{moving, end::front, standing, end::rear, 1.0};
|
||||
REQUIRE(couple_during(pair, 3.0));
|
||||
CHECK(pair.coupling() == flag);
|
||||
}
|
||||
SUBCASE("a different one") {
|
||||
auto standing{vehicle{automatic_definition(flag).set("BuffCoupl", "ControlType", "second"), 0.0, "standing"}};
|
||||
REQUIRE(standing->Couplers[end::rear].control_type == "second");
|
||||
test::pair pair{moving, end::front, standing, end::rear, 1.0};
|
||||
REQUIRE(couple_during(pair, 3.0));
|
||||
CHECK(pair.coupling() == (coupling::coupler | coupling::brakehose));
|
||||
}
|
||||
}
|
||||
46
tests/unit/test_gauge_positions.cpp
Normal file
46
tests/unit/test_gauge_positions.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
|
||||
#include "vehicle/gaugepositions.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
// the sounds of the positions of a cab control (MaSzyna-EU07/maszyna#83): a brake lever moved smoothly (Num3/Num9)
|
||||
// passes its positions instead of stopping at them, and sounds each one it reaches, as it does when put there
|
||||
|
||||
namespace {
|
||||
|
||||
// a brake lever with sounds at positions 0, 2 and 5; values in hundredths of a position
|
||||
std::map<int, std::string> lever_sounds() {
|
||||
return {{0, "release"}, {2, "running"}, {5, "service"}};
|
||||
}
|
||||
|
||||
std::string reached(int const Previous, int const Desired) {
|
||||
auto positions{lever_sounds()};
|
||||
auto const position{gauge_positions::last_reached(positions, Previous, Desired)};
|
||||
return position != positions.end() ? position->second : "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("gauge positions: a lever put at a position sounds it") {
|
||||
CHECK(reached(100, 200) == "running");
|
||||
CHECK(reached(600, 500) == "service");
|
||||
}
|
||||
|
||||
TEST_CASE("gauge positions: a lever moved smoothly past a position sounds it (MaSzyna-EU07/maszyna#83)") {
|
||||
CHECK(reached(190, 205) == "running"); // moving up
|
||||
CHECK(reached(510, 495) == "service"); // moving down
|
||||
}
|
||||
|
||||
TEST_CASE("gauge positions: of the positions passed in one go the last one reached sounds") {
|
||||
CHECK(reached(50, 520) == "service"); // up past 2 and 5
|
||||
CHECK(reached(520, 10) == "running"); // down past 5 and 2, short of 0
|
||||
CHECK(reached(520, 0) == "release");
|
||||
}
|
||||
|
||||
TEST_CASE("gauge positions: moving between the positions or off one sounds nothing") {
|
||||
CHECK(reached(210, 290) == "");
|
||||
CHECK(reached(200, 230) == ""); // leaving a position isn't reaching it
|
||||
CHECK(reached(200, 170) == "");
|
||||
}
|
||||
99
tests/unit/test_horn.cpp
Normal file
99
tests/unit/test_horn.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
|
||||
#include "vehicle.h"
|
||||
|
||||
#include "vehicle/horn.h"
|
||||
|
||||
// the horn keys of the cab: low tone (A) and high tone (shift+A), held to sound
|
||||
|
||||
TEST_CASE("horn: a single lever for both tones sounds one tone at a time (eu07/maszyna#392)") {
|
||||
auto signal{0};
|
||||
signal = horn::pressed(signal, horn::low, true);
|
||||
REQUIRE(signal == horn::low);
|
||||
|
||||
// the lever moved over to the high tone while the low tone key is still held
|
||||
signal = horn::pressed(signal, horn::high, true);
|
||||
CHECK(signal == horn::high);
|
||||
|
||||
// letting go of the low tone key leaves the high tone sounding
|
||||
signal = horn::released(signal, horn::low);
|
||||
CHECK(signal == horn::high);
|
||||
|
||||
signal = horn::released(signal, horn::high);
|
||||
CHECK(signal == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: separate buttons for the tones can sound both at once") {
|
||||
auto signal{0};
|
||||
signal = horn::pressed(signal, horn::low, false);
|
||||
signal = horn::pressed(signal, horn::high, false);
|
||||
CHECK(signal == (horn::low | horn::high));
|
||||
|
||||
signal = horn::released(signal, horn::low);
|
||||
CHECK(signal == horn::high);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: releasing a tone leaves the other bits of the signal alone") {
|
||||
auto const third{4}; // the third horn, operated separately
|
||||
CHECK(horn::released(horn::low | third, horn::low) == third);
|
||||
CHECK(horn::pressed(third, horn::high, true) == (horn::high | third));
|
||||
}
|
||||
|
||||
// what the horns need to sound (eu07/maszyna#290): a pneumatic horn the air of the main reservoir, an electric one
|
||||
// the low voltage. the horn lever stays where the driver put it, only the horn is silent
|
||||
namespace {
|
||||
|
||||
test::vehicle horn_vehicle(test::definition Definition) {
|
||||
// a main reservoir of its own
|
||||
return test::vehicle{Definition.set("Brake", "Vv", 1.0).set("Brake", "MinCP", 0.65).set("Brake", "MaxCP", 0.8)};
|
||||
}
|
||||
|
||||
int const allhorns{horn::low | horn::high | 4};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("horn: a pneumatic horn sounds on the air of the main reservoir only") {
|
||||
auto vehicle{horn_vehicle(test::definition::load("wagon"))};
|
||||
REQUIRE(vehicle->HornSupply == horn_supply::pneumatic); // the default
|
||||
|
||||
vehicle->Compressor = 0.65;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == allhorns);
|
||||
vehicle->Compressor = 0.0; // the reservoir is empty
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: a vehicle without a main reservoir takes the horn air from the main pipe") {
|
||||
test::vehicle vehicle{test::definition::load("wagon")};
|
||||
REQUIRE(vehicle->VeselVolume == 0.0);
|
||||
|
||||
vehicle->ScndPipePress = 0.5;
|
||||
CHECK(vehicle->SoundingHorns(horn::low) == horn::low);
|
||||
vehicle->ScndPipePress = 0.0;
|
||||
CHECK(vehicle->SoundingHorns(horn::low) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: an electric horn sounds on the low voltage, whatever the air") {
|
||||
auto vehicle{horn_vehicle(test::definition::load("wagon").set("Cntrl", "HornType", "Electric"))};
|
||||
REQUIRE(vehicle->HornSupply == horn_supply::electric);
|
||||
REQUIRE(vehicle->Horn3Supply == horn_supply::electric); // the third horn follows the others unless told otherwise
|
||||
|
||||
vehicle->Compressor = 0.0;
|
||||
vehicle->Power24vIsAvailable = true;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == allhorns);
|
||||
vehicle->Power24vIsAvailable = false;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: the third horn can have a supply of its own, an electric horn of a diesel say") {
|
||||
auto vehicle{horn_vehicle(test::definition::load("wagon").set("Cntrl", "Horn3Type", "Electric"))};
|
||||
REQUIRE(vehicle->HornSupply == horn_supply::pneumatic);
|
||||
REQUIRE(vehicle->Horn3Supply == horn_supply::electric);
|
||||
|
||||
vehicle->Compressor = 0.0;
|
||||
vehicle->Power24vIsAvailable = true;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == 4);
|
||||
vehicle->Compressor = 0.65;
|
||||
vehicle->Power24vIsAvailable = false;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == (horn::low | horn::high));
|
||||
}
|
||||
28
tests/unit/test_radio.cpp
Normal file
28
tests/unit/test_radio.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
// the radio works only switched on and with the low voltage (eu07/maszyna#429): its channel and volume buttons,
|
||||
// the calls and the radio-stop all ask RadioPowered()
|
||||
|
||||
TEST_CASE("radio: a radio switched on with the low voltage works") {
|
||||
test::vehicle vehicle{test::definition::load("wagon")};
|
||||
vehicle->Radio = true;
|
||||
SUBCASE("24 V") { vehicle->Power24vIsAvailable = true; }
|
||||
SUBCASE("110 V") { vehicle->Power110vIsAvailable = true; }
|
||||
CHECK(vehicle->RadioPowered());
|
||||
}
|
||||
|
||||
TEST_CASE("radio: a radio switched off or without the low voltage doesn't") {
|
||||
test::vehicle vehicle{test::definition::load("wagon")};
|
||||
SUBCASE("switched off") {
|
||||
vehicle->Radio = false;
|
||||
vehicle->Power24vIsAvailable = true;
|
||||
}
|
||||
SUBCASE("batteries off") {
|
||||
vehicle->Radio = true;
|
||||
vehicle->Power24vIsAvailable = false;
|
||||
vehicle->Power110vIsAvailable = false;
|
||||
}
|
||||
CHECK_FALSE(vehicle->RadioPowered());
|
||||
}
|
||||
62
tests/unit/test_wheelslip.cpp
Normal file
62
tests/unit/test_wheelslip.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
using test::definition;
|
||||
using test::track;
|
||||
using test::vehicle;
|
||||
|
||||
namespace {
|
||||
|
||||
struct braking_result {
|
||||
bool skidded{false}; // the wheels locked while braking
|
||||
bool stopped{false}; // came (almost) to a stop on the braking rail
|
||||
};
|
||||
|
||||
// brakes the wagon from 40 km/h on the Braking rail until it's (nearly) stopped, then has it stand on the
|
||||
// Standing one for 30 seconds
|
||||
braking_result brake_to_stop_then_stand(vehicle &Coach, track const &Braking, track const &Standing) {
|
||||
braking_result result;
|
||||
Coach.hold_brake_pipe(5.0);
|
||||
Coach.run(5.0, Braking);
|
||||
Coach.hold_brake_pipe(3.5); // service braking
|
||||
// a skidding wagon brakes on the sliding adhesion only, give it time
|
||||
for (auto step = 0; step < 60000; ++step) { // 600 s in 0.01 s steps
|
||||
result.skidded |= Coach->SlippingWheels;
|
||||
if (auto const speed{std::abs(Coach.speed())}; speed > 0.0 && speed < 0.1) {
|
||||
// almost stopped; a skidding wagon is still skidding at this point
|
||||
result.stopped = true;
|
||||
break;
|
||||
}
|
||||
Coach.step(Braking);
|
||||
}
|
||||
Coach.run(30.0, Standing);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// the engine doesn't let a stopped, braked vehicle slip (the Vel > 0.1 trap in TMoverParameters::ComputeTotalForce),
|
||||
// so a wagon which stopped in a skid should be no different from one which stopped without it. it used to stay
|
||||
// skidding instead, with its brakes capped by the sliding adhesion, and slid or crept away (eu07/maszyna#479)
|
||||
TEST_CASE("wheelslip: a wagon which stopped in a skid holds like one which stopped without it") {
|
||||
track const dry{0.15, 0.0};
|
||||
track const slippery{0.02, 0.0};
|
||||
// held only by the brakes: the sliding adhesion on this rail is too low for the slope
|
||||
track const slipperyslope{0.02, 0.08};
|
||||
|
||||
vehicle normal{definition::load("wagon"), 40.0};
|
||||
auto const normalbraking{brake_to_stop_then_stand(normal, dry, slipperyslope)};
|
||||
vehicle skidding{definition::load("wagon"), 40.0};
|
||||
auto const skiddingbraking{brake_to_stop_then_stand(skidding, slippery, slipperyslope)};
|
||||
|
||||
// the test means something only when the braking went as planned
|
||||
REQUIRE(normalbraking.stopped);
|
||||
REQUIRE_FALSE(normalbraking.skidded);
|
||||
REQUIRE(skiddingbraking.stopped);
|
||||
REQUIRE(skiddingbraking.skidded);
|
||||
|
||||
CHECK(std::abs(normal.speed()) < 0.1);
|
||||
CHECK(std::abs(skidding.speed()) < 0.1);
|
||||
CHECK_FALSE(skidding->SlippingWheels);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "stdafx.h"
|
||||
#include "vehicle.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
@@ -159,6 +160,18 @@ vehicle::vehicle(definition const &Definition, double Velocity, std::string cons
|
||||
}
|
||||
|
||||
void vehicle::step(track const &Track, double Dt) {
|
||||
compute_forces(Dt);
|
||||
compute_movement(Track, Dt, TLocation{0.0, 0.0, 0.0});
|
||||
}
|
||||
|
||||
void vehicle::compute_forces(double Dt) {
|
||||
if (m_pipepressure >= 0.0) {
|
||||
m_mover->Pipe->CreatePress(m_pipepressure);
|
||||
}
|
||||
m_mover->ComputeTotalForce(Dt);
|
||||
}
|
||||
|
||||
double vehicle::compute_movement(track const &Track, double Dt, TLocation const &Location) {
|
||||
TTrackShape shape;
|
||||
shape.Len = 1000.0;
|
||||
shape.dHtrack = Track.gradient;
|
||||
@@ -167,13 +180,8 @@ void vehicle::step(track const &Track, double Dt) {
|
||||
param.friction = Track.friction;
|
||||
param.CategoryFlag = 1;
|
||||
TTractionParam const traction;
|
||||
TLocation const location{0.0, 0.0, 0.0};
|
||||
TRotation const rotation{0.0, 0.0, 0.0};
|
||||
if (m_pipepressure >= 0.0) {
|
||||
m_mover->Pipe->CreatePress(m_pipepressure);
|
||||
}
|
||||
m_mover->ComputeTotalForce(Dt);
|
||||
m_mover->ComputeMovement(Dt, Dt, shape, param, traction, location, rotation);
|
||||
return m_mover->ComputeMovement(Dt, Dt, shape, param, traction, Location, rotation);
|
||||
}
|
||||
|
||||
void vehicle::run(double Seconds, track const &Track, double Dt) {
|
||||
@@ -184,4 +192,88 @@ void vehicle::run(double Seconds, track const &Track, double Dt) {
|
||||
}
|
||||
}
|
||||
|
||||
// the physics reaches the vehicle next to it through TDynamicObject::MoverParameters only (neighbour_data). the
|
||||
// tests can't make a TDynamicObject, whose constructor and members come with the rest of the engine, so the
|
||||
// neighbour is a zeroed block of its size with just that pointer set: enough for the physics, which reads nothing
|
||||
// else of it, and never used as a whole object
|
||||
static_assert(alignof(TDynamicObject) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__);
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<std::byte[]> make_standin(TMoverParameters &Mover) {
|
||||
auto standin{std::make_unique<std::byte[]>(sizeof(TDynamicObject))}; // zeroed
|
||||
reinterpret_cast<TDynamicObject *>(standin.get())->MoverParameters = &Mover; // NOSONAR see above
|
||||
return standin;
|
||||
}
|
||||
|
||||
TDynamicObject *as_neighbour(std::byte *Standin) {
|
||||
return reinterpret_cast<TDynamicObject *>(Standin); // NOSONAR see above
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
pair::pair(vehicle &First, int FirstEnd, vehicle &Second, int SecondEnd, double Gap) {
|
||||
auto const &first{First.mover()};
|
||||
auto const &second{Second.mover()};
|
||||
// the first vehicle in the middle, its FirstEnd towards the second one
|
||||
m_vehicles[0] = {&First, FirstEnd, 0.0, FirstEnd == end::front ? 1.0 : -1.0, make_standin(First.mover())};
|
||||
m_vehicles[1] = {&Second, SecondEnd, 0.5 * first.Dim.L + Gap + 0.5 * second.Dim.L, SecondEnd == end::front ? -1.0 : 1.0, make_standin(Second.mover())};
|
||||
}
|
||||
|
||||
pair::~pair() {
|
||||
for (auto const &placed : m_vehicles) {
|
||||
// the vehicles outlive the pair, they mustn't point at its stand-ins
|
||||
for (auto &neighbour : placed.owner->mover().Neighbours) {
|
||||
neighbour = neighbour_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pair::find_neighbours() {
|
||||
// as TDynamicObject::update_neighbours()
|
||||
for (auto const &placed : m_vehicles) {
|
||||
placed.owner->mover().Loc = TLocation{placed.position, 0.0, 0.0};
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto const &self{m_vehicles[i]};
|
||||
auto const &other{m_vehicles[1 - i]};
|
||||
auto &mover{self.owner->mover()};
|
||||
auto const &othermover{other.owner->mover()};
|
||||
auto &neighbour{mover.Neighbours[self.end]};
|
||||
neighbour.vehicle = as_neighbour(other.standin.get());
|
||||
neighbour.vehicle_end = other.end;
|
||||
neighbour.distance = static_cast<float>(TMoverParameters::CouplerDist(&mover, &othermover) - mover.Couplers[self.end].adapter_length -
|
||||
othermover.Couplers[other.end].adapter_length);
|
||||
mover.Neighbours[1 - self.end] = neighbour_data();
|
||||
}
|
||||
}
|
||||
|
||||
void pair::step(track const &Track, double Dt) {
|
||||
find_neighbours();
|
||||
for (auto const &placed : m_vehicles) {
|
||||
placed.owner->compute_forces(Dt);
|
||||
}
|
||||
for (auto &placed : m_vehicles) {
|
||||
placed.position += placed.heading * placed.owner->compute_movement(Track, Dt, TLocation{placed.position, 0.0, 0.0});
|
||||
}
|
||||
}
|
||||
|
||||
void pair::run(double Seconds, track const &Track, double Dt) {
|
||||
// whole steps, as in vehicle::run()
|
||||
auto const steps{std::lround(Seconds / Dt)};
|
||||
for (long i = 0; i < steps; ++i) {
|
||||
step(Track, Dt);
|
||||
}
|
||||
}
|
||||
|
||||
double pair::gap() const {
|
||||
auto const &first{m_vehicles[0].owner->mover()};
|
||||
auto const &second{m_vehicles[1].owner->mover()};
|
||||
return std::abs(m_vehicles[1].position - m_vehicles[0].position) - 0.5 * (first.Dim.L + second.Dim.L);
|
||||
}
|
||||
|
||||
int pair::coupling() const {
|
||||
return m_vehicles[0].owner->mover().Couplers[m_vehicles[0].end].CouplingFlag;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -64,8 +65,57 @@ public:
|
||||
double speed() const { return m_mover->V * 3.6; }
|
||||
|
||||
private:
|
||||
friend class pair;
|
||||
|
||||
// the two halves of a step: the forces, taken by all the vehicles in the scene first, then the movement,
|
||||
// the vehicle placed at Location. returns the distance travelled [m], positive forwards
|
||||
void compute_forces(double Dt);
|
||||
double compute_movement(track const &Track, double Dt, TLocation const &Location);
|
||||
|
||||
std::unique_ptr<TMoverParameters> m_mover;
|
||||
double m_pipepressure{-1.0};
|
||||
};
|
||||
|
||||
// two vehicles on a straight track, the FirstEnd of the first one facing the SecondEnd of the second one Gap metres
|
||||
// away (from coupler to coupler), with nothing else around. a step runs them the way the simulation runs the
|
||||
// vehicles of a scene (vehicle_table::update()): each one finds its neighbour, then both take the forces, the
|
||||
// coupler and buffer ones among them (CouplerForce()), then both move and handle the collisions (CollisionDetect()).
|
||||
// they couple and part on their own, as the couplers let them; the speed of each runs along its own axis
|
||||
// test::pair pair{moving, end::front, standing, end::rear, 1.0 /* m */};
|
||||
// pair.run(5.0 /* s */);
|
||||
// CHECK(pair.coupled());
|
||||
class pair {
|
||||
public:
|
||||
pair(vehicle &First, int FirstEnd, vehicle &Second, int SecondEnd, double Gap);
|
||||
~pair();
|
||||
pair(pair const &) = delete;
|
||||
pair &operator=(pair const &) = delete;
|
||||
// the vehicles point at the stand-ins of the pair, which stay where they are
|
||||
pair(pair &&) = delete;
|
||||
pair &operator=(pair &&) = delete;
|
||||
|
||||
// advances both vehicles by one step of Dt
|
||||
void step(track const &Track = {}, double Dt = 0.01);
|
||||
// advances both vehicles by Seconds in steps of Dt
|
||||
void run(double Seconds, track const &Track = {}, double Dt = 0.01);
|
||||
// distance between the facing couplers [m], negative while the buffers are pressed in
|
||||
double gap() const;
|
||||
// type of the coupling between the vehicles, coupling::faux when they aren't coupled
|
||||
int coupling() const;
|
||||
bool coupled() const { return coupling() != coupling::faux; }
|
||||
|
||||
private:
|
||||
struct placement {
|
||||
vehicle *owner{nullptr};
|
||||
int end{0}; // the end facing the other vehicle
|
||||
double position{0.0}; // of the middle of the vehicle along the track [m]
|
||||
double heading{1.0}; // +1 when the vehicle runs forwards towards the growing position, -1 otherwise
|
||||
std::unique_ptr<std::byte[]> standin; // what the other vehicle sees as this one, see vehicle.cpp
|
||||
};
|
||||
|
||||
void find_neighbours();
|
||||
|
||||
std::array<placement, 2> m_vehicles;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
||||
Reference in New Issue
Block a user