mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-02 02:19:13 +01:00
fix(physics): automatic couplers don't catch above the safe collision… (#217)
* fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds fix(cab): the line contactors lamp goes out with the pressure switch (eu07/maszyna#470) The lamp of the open line contactors, which tells the start can be made, went out at 1 bar in the brake cylinders, while the pressure switch cuts the start off only at 2 bar (or below 3.6 bar in the main pipe), so with the brakes partly applied the start could be made with the lamp out. With a pressure switch fitted the lamp now follows it; without one it goes out at 1 bar as before. The same holds for the lamp of the second unit, which used to keep its last state between 1 bar and the cut-off. The condition is TMoverParameters::LineContactorsLampLit(). Tests: tests/unit/test_line_contactors.cpp. refactor: Sonar findings of PR 217 - horn_supply::pneumatic shadowed the pneumatic enumerator of the sound flags (cpp:S1117), it's horn_supply::air; the supplies read with using enum (S6177), - CouplerForce(): the automatic coupling refused or caught without another level of nested ifs (S134), the same outcome: too fast, the coupling is locked; caught, coupled and locked; otherwise left to try again, - CompressorCheck(): the power the compressor doesn't draw yet noted without a TODO (S1135), - tests: the previous crash damage setting in an in-class initializer (S3230), the loops which stop early with break rather than in their conditions (S886), the stand-ins of test::pair in a std::vector rather than an array (S5945), units in the comments of the example without /* */ (S1103). test: the cases the changes of the branch left without a test - Cntrl.: BDelay1 to BDelay4 reach the brake delays (now through std::format), the default start types of the ground and converter overload relays of a multiple unit and of other vehicles (out of the nested conditionals), - the compressor: fed from the low voltage it waits from the low voltage coming on; the pressure switch stops it above the cut-out pressure, without one the excess air is vented (the vented fraction rewritten), - the horns: a vehicle without any air sounds as before, the threshold pressure, an electric horn on 110 V, - the positions of a cab control below zero, and a control without position sounds. refactor(cab): the kind of the train heating switch as a flag Read once with the vehicle definition, rather than compared as text in each of the switch commands. feat(cab): train heating switch springing back (MaSzyna-EU07/maszyna#85) TrainHeating=[toggle]|impulse in the Switches: section of the vehicle definition makes the train heating switch an unstable one: it goes up to switch the heating on, down to switch it off, and back to the middle once let go, instead of staying where it was put. The position of such a switch isn't set from the heating state when the cab is entered. Tests: tests/unit/test_switches.cpp. feat(sound): mirror sounds (MaSzyna-EU07/maszyna#84, eu07/maszyna#322) The sound definitions of a vehicle (.mmd) take mirroropen: and mirrorclose:, played as a mirror sets off unfolding or folding, and the locations section mirrors: <offset>... end, the offsets along the vehicle of the mirror pairs, as for the doors (negative ahead of the centre), each pair at the sides of the vehicle. As the animation, only the mirrors at the occupied cab sound. The start of a move is mirror_moves::motion in vehicle/mirrors.h. Tests: tests/unit/test_mirrors.cpp. feat(cab): joint oil and fuel pump switch (MaSzyna-EU07/maszyna#81) A cab control oilfuelpump_sw: switches both the oil and the fuel pump at once, with the new command oilfuelpumptoggle (no key by default). As a two-state switch it turns both pumps off when both are on and both on otherwise; as an impulse switch it's held to start them, like the separate pump switches. Its position follows the pumps when the cab is entered. refactor(editor): text fields filled without strncpy (MaSzyna-EU07/maszyna#112) The last three strncpy calls Sonar flags (cpp:S5816), in the road and track editors, copy the text into the ImGui field buffers with std::string::copy and terminate it right after; the text gets cut to the buffer as before. 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. * fix: review findings of PR 217 - the pressures of the mover are in bar (MinCP=7.0 in the vehicle definitions), so the 0.2 the pneumatic horns needed was 0.2 bar; it's 2 bar now, and the tests take their pressures in bar too, - the lamps of the ground relay and of the motor overload relays follow the pressure switch as the line contactors one does, TMoverParameters::MainCircuitLampsEnabled(), so the lamps of one cab go out together, - the joint oil and fuel pump switch leaves out a pump started automatically, which ignores the switching: with one, it couldn't turn the other off, and showed off with both running (OilFuelPumpsEnabled()), - the kind of the train heating switch comes from the occupied vehicle on entering the cab too, as in the switch commands; in a multiple unit these are different vehicles, - CouplerForce() put the force taken back by the other vehicle on its far coupler when the vehicles faced each other (Couplers[1 - End] rather than the coupler it's coupled with), - the velocity difference along the coupler axis computed once, CouplerVelocityDifference(), for CouplerForce() and CollisionVelocity(), - the attachcoupler flag added without the SetFlag() call with 0, the index of the mirror moves noted as by side. Tests: the coupler force of vehicles facing each other, the joint switch with an automatic pump, the lamps of the main circuit, the horn and compressor pressures in bar. * fix: coupler force of vehicles facing each other taken back along the other vehicle's axis CForce runs along the axis of its own vehicle, so the force the other vehicle takes back on its coupler of the pair is turned around once more when the vehicles face each other (-CF * DirPatch()), as that vehicle computes it itself. Tests: the coupler forces of the pair facing each other and facing the same way.
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).
|
||||
|
||||
39
tests/unit/test_cntrl.cpp
Normal file
39
tests/unit/test_cntrl.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
using test::definition;
|
||||
using test::vehicle;
|
||||
|
||||
// the Cntrl. section of the vehicle definition, as read by TMoverParameters::LoadFIZ_Cntrl()
|
||||
|
||||
TEST_CASE("cntrl: the brake delays of the settings come from BDelay1 to BDelay4") {
|
||||
vehicle wagon{definition::load("wagon").set("Cntrl", "BDelay1", 10.0).set("Cntrl", "BDelay2", 20.0).set("Cntrl", "BDelay3", 30.0).set("Cntrl", "BDelay4", 40.0)};
|
||||
CHECK(wagon->BrakeDelay[0] == doctest::Approx(10.0));
|
||||
// the vehicle varies the others a little, by 0.83 to 0.9 (CheckLocomotiveParameters())
|
||||
for (auto setting{1}; setting < 4; ++setting) {
|
||||
CAPTURE(setting);
|
||||
auto const given{10.0 * (setting + 1)};
|
||||
CHECK(wagon->BrakeDelay[setting] >= 0.83 * given);
|
||||
CHECK(wagon->BrakeDelay[setting] <= 0.9 * given);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("cntrl: the relays of a multiple unit start on their own, of other vehicles by hand") {
|
||||
SUBCASE("multiple unit") {
|
||||
vehicle unit{definition::load("wagon").set("Param", "Type", "ezt")};
|
||||
REQUIRE(unit->TrainType == dt_EZT);
|
||||
CHECK(unit->GroundRelayStart == start_t::automatic);
|
||||
CHECK(unit->ConverterOverloadRelayStart == start_t::converter); // activates when the converter is switched off
|
||||
}
|
||||
SUBCASE("other vehicle") {
|
||||
vehicle wagon{definition::load("wagon")};
|
||||
CHECK(wagon->GroundRelayStart == start_t::manual);
|
||||
CHECK(wagon->ConverterOverloadRelayStart == start_t::manual);
|
||||
}
|
||||
SUBCASE("given in the definition") {
|
||||
vehicle unit{definition::load("wagon").set("Param", "Type", "ezt").set("Cntrl", "GroundRelayStart", "Manual").set("Cntrl", "ConverterOverloadRelayStart", "Automatic")};
|
||||
CHECK(unit->GroundRelayStart == start_t::manual);
|
||||
CHECK(unit->ConverterOverloadRelayStart == start_t::automatic);
|
||||
}
|
||||
}
|
||||
115
tests/unit/test_compressor.cpp
Normal file
115
tests/unit/test_compressor.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#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 which starts on its own, fed from the main circuit unless given another Power, its pressure
|
||||
// switch between MinCP and MaxCP, and an empty main reservoir for it to fill
|
||||
vehicle compressor_vehicle(double const Delay, std::string const &Power = "Main", double const MinCP = 6.5) {
|
||||
auto definition{definition::load("wagon")};
|
||||
definition.set("Brake", "Vv", 1.0).set("Brake", "MinCP", MinCP).set("Brake", "MaxCP", 8.0);
|
||||
definition.set("Brake", "CompressorSpeed", 0.01);
|
||||
if (false == Power.empty()) {
|
||||
definition.set("Brake", "CompressorPower", Power);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
TEST_CASE("compressor: a compressor fed from the low voltage waits from the low voltage coming on") {
|
||||
auto vehicle{compressor_vehicle(5.0, "")}; // the default supply: the converter, the 110 V
|
||||
vehicle->Mains = true; // which alone doesn't feed it
|
||||
run_compressor(vehicle, 10.0);
|
||||
REQUIRE_FALSE(vehicle->CompressorFlag);
|
||||
|
||||
vehicle->Power110vIsAvailable = true;
|
||||
run_compressor(vehicle, 4.9);
|
||||
CHECK_FALSE(vehicle->CompressorFlag);
|
||||
run_compressor(vehicle, 0.2);
|
||||
CHECK(vehicle->CompressorFlag);
|
||||
}
|
||||
|
||||
TEST_CASE("compressor: the pressure switch stops the compressor above the cut-out pressure") {
|
||||
auto vehicle{compressor_vehicle(0.0)};
|
||||
vehicle->Mains = true;
|
||||
vehicle->CompressedVolume = 8.5; // over MaxCP, in the reservoir of 1 m3
|
||||
run_compressor(vehicle, step);
|
||||
CHECK(vehicle->CompressorGovernorLock);
|
||||
CHECK_FALSE(vehicle->CompressorFlag);
|
||||
}
|
||||
|
||||
TEST_CASE("compressor: without a pressure switch the air over the cut-out pressure is vented") {
|
||||
auto vehicle{compressor_vehicle(0.0, "Main", 8.0)}; // MinCP the same as MaxCP: no pressure switch
|
||||
vehicle->Mains = true;
|
||||
vehicle->CompressedVolume = 8.5;
|
||||
run_compressor(vehicle, step);
|
||||
REQUIRE(vehicle->CompressorFlag); // nothing stops it
|
||||
CHECK(vehicle->CompressedVolume < 8.5 * 0.81); // a fifth of the air let out
|
||||
}
|
||||
458
tests/unit/test_couplers.cpp
Normal file
458
tests/unit/test_couplers.cpp
Normal file
@@ -0,0 +1,458 @@
|
||||
#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) { 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{Global.crash_damage};
|
||||
};
|
||||
|
||||
} // 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};
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
pair.step();
|
||||
if (pair.gap() > 0.05) {
|
||||
break; // parted
|
||||
}
|
||||
coupled = coupled || 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; ++i) {
|
||||
pair.step();
|
||||
touched = touched || pair.gap() < 0.0;
|
||||
if (touched && pair.gap() > 0.3) {
|
||||
break; // bounced off
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("couplers: the coupler force of vehicles facing each other lands on the couplers holding them") {
|
||||
// the force a vehicle takes from its coupler, the other one takes back on its own coupler of the pair, whichever
|
||||
// way the vehicles face; it used to land on the far end of the other vehicle when they faced each other.
|
||||
// CForce runs along the axis of its own vehicle, so the two forces of the pair are opposite along the track: of
|
||||
// opposite signs for vehicles facing the same way, of the same sign for vehicles facing each other
|
||||
auto first{automatic_wagon(5.0, "first")};
|
||||
auto second{automatic_wagon(0.0, "second")};
|
||||
|
||||
SUBCASE("facing each other") {
|
||||
test::pair pair{first, end::front, second, end::front, 1.0};
|
||||
pair.run(5.0);
|
||||
REQUIRE(pair.coupled());
|
||||
|
||||
pair.step();
|
||||
REQUIRE(second->Couplers[end::front].CForce != 0.0);
|
||||
CHECK(first->Couplers[end::front].CForce == doctest::Approx(second->Couplers[end::front].CForce));
|
||||
CHECK(first->Couplers[end::rear].CForce == 0.0); // nothing there
|
||||
CHECK(second->Couplers[end::rear].CForce == 0.0);
|
||||
}
|
||||
SUBCASE("facing the same way") {
|
||||
test::pair pair{first, end::front, second, end::rear, 1.0};
|
||||
pair.run(5.0);
|
||||
REQUIRE(pair.coupled());
|
||||
|
||||
pair.step();
|
||||
REQUIRE(second->Couplers[end::rear].CForce != 0.0);
|
||||
CHECK(first->Couplers[end::front].CForce == doctest::Approx(-second->Couplers[end::rear].CForce));
|
||||
CHECK(first->Couplers[end::rear].CForce == 0.0);
|
||||
CHECK(second->Couplers[end::front].CForce == 0.0);
|
||||
}
|
||||
}
|
||||
58
tests/unit/test_gauge_positions.cpp
Normal file
58
tests/unit/test_gauge_positions.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#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) == "");
|
||||
}
|
||||
|
||||
TEST_CASE("gauge positions: positions below zero count the same way") {
|
||||
// a brake lever with the release position at -1
|
||||
std::map<int, std::string> positions{{-1, "release"}, {0, "running"}};
|
||||
CHECK(gauge_positions::last_reached(positions, 50, -120)->second == "release"); // down past 0 and -1
|
||||
CHECK(gauge_positions::last_reached(positions, -150, -90)->second == "release"); // up past -1
|
||||
}
|
||||
|
||||
TEST_CASE("gauge positions: a control without position sounds reaches none") {
|
||||
std::map<int, std::string> positions;
|
||||
CHECK(gauge_positions::last_reached(positions, 0, 500) == positions.end());
|
||||
}
|
||||
121
tests/unit/test_horn.cpp
Normal file
121
tests/unit/test_horn.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#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", 6.5).set("Brake", "MaxCP", 8.0)};
|
||||
}
|
||||
|
||||
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::air); // the default
|
||||
|
||||
vehicle->Compressor = 6.5;
|
||||
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 = 5.0;
|
||||
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::air);
|
||||
REQUIRE(vehicle->Horn3Supply == horn_supply::electric);
|
||||
|
||||
vehicle->Compressor = 0.0;
|
||||
vehicle->Power24vIsAvailable = true;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == 4);
|
||||
vehicle->Compressor = 6.5;
|
||||
vehicle->Power24vIsAvailable = false;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == (horn::low | horn::high));
|
||||
}
|
||||
|
||||
TEST_CASE("horn: a vehicle without any air, a road vehicle say, sounds its pneumatic horn as before") {
|
||||
// no main reservoir and no main pipe connection at either end
|
||||
test::vehicle vehicle{test::definition::load("wagon").set("BuffCoupl", "AllowedFlag", 3)};
|
||||
REQUIRE(vehicle->VeselVolume == 0.0);
|
||||
vehicle->ScndPipePress = 0.0;
|
||||
CHECK(vehicle->SoundingHorns(allhorns) == allhorns);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: a pneumatic horn sounds from the threshold pressure up") {
|
||||
auto vehicle{horn_vehicle(test::definition::load("wagon"))};
|
||||
vehicle->Compressor = TMoverParameters::HornMinPressure;
|
||||
CHECK(vehicle->SoundingHorns(horn::low) == horn::low);
|
||||
vehicle->Compressor = TMoverParameters::HornMinPressure - 0.01;
|
||||
CHECK(vehicle->SoundingHorns(horn::low) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: an electric horn sounds on 110 V too") {
|
||||
auto vehicle{horn_vehicle(test::definition::load("wagon").set("Cntrl", "HornType", "Electric"))};
|
||||
vehicle->Power110vIsAvailable = true;
|
||||
CHECK(vehicle->SoundingHorns(horn::high) == horn::high);
|
||||
}
|
||||
59
tests/unit/test_line_contactors.cpp
Normal file
59
tests/unit/test_line_contactors.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
// the lamp of the open line contactors (eu07/maszyna#470): lit, it tells the start can be made, so it should go out
|
||||
// where the start is cut off, by the pressure switch at 2 bar in the brake cylinders, not at 1 bar already
|
||||
|
||||
namespace {
|
||||
|
||||
test::vehicle locomotive(bool const Pressureswitch) {
|
||||
test::vehicle vehicle{test::definition::load("wagon").set("Engine", "PressureSwitch", Pressureswitch)};
|
||||
vehicle->StLinFlag = false; // the line contactors open
|
||||
vehicle->PipePress = 5.0;
|
||||
return vehicle;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("line contactors: with a pressure switch the lamp goes out with the start cut off") {
|
||||
auto vehicle{locomotive(true)};
|
||||
REQUIRE(vehicle->HasControlPressureSwitch);
|
||||
|
||||
vehicle->BrakePress = 1.5; // braked, the start can still be made
|
||||
vehicle->MotorConnectorsCheck(); // updates the pressure switch
|
||||
REQUIRE_FALSE(vehicle->ControlPressureSwitch);
|
||||
CHECK(vehicle->LineContactorsLampLit());
|
||||
|
||||
vehicle->BrakePress = 2.5; // the pressure switch cuts the start off
|
||||
vehicle->MotorConnectorsCheck();
|
||||
REQUIRE(vehicle->ControlPressureSwitch);
|
||||
CHECK_FALSE(vehicle->LineContactorsLampLit());
|
||||
}
|
||||
|
||||
TEST_CASE("line contactors: without a pressure switch the lamp goes out with the brakes applied, as before") {
|
||||
auto vehicle{locomotive(false)};
|
||||
vehicle->BrakePress = 0.5;
|
||||
vehicle->MotorConnectorsCheck();
|
||||
CHECK(vehicle->LineContactorsLampLit());
|
||||
vehicle->BrakePress = 1.5;
|
||||
vehicle->MotorConnectorsCheck();
|
||||
CHECK_FALSE(vehicle->LineContactorsLampLit());
|
||||
}
|
||||
|
||||
TEST_CASE("line contactors: closed contactors put the lamp out") {
|
||||
auto vehicle{locomotive(true)};
|
||||
vehicle->StLinFlag = true;
|
||||
CHECK_FALSE(vehicle->LineContactorsLampLit());
|
||||
}
|
||||
|
||||
TEST_CASE("line contactors: the ground relay and overload lamps follow the same rule") {
|
||||
// all the lamps of the main circuit go out together, with the pressure switch where it's fitted
|
||||
auto vehicle{locomotive(true)};
|
||||
vehicle->BrakePress = 1.5;
|
||||
vehicle->MotorConnectorsCheck();
|
||||
CHECK(vehicle->MainCircuitLampsEnabled());
|
||||
vehicle->BrakePress = 2.5;
|
||||
vehicle->MotorConnectorsCheck();
|
||||
CHECK_FALSE(vehicle->MainCircuitLampsEnabled());
|
||||
}
|
||||
32
tests/unit/test_mirrors.cpp
Normal file
32
tests/unit/test_mirrors.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
|
||||
#include "vehicle/mirrors.h"
|
||||
|
||||
// the sounds of the mirrors (MaSzyna-EU07/maszyna#84, eu07/maszyna#322): a mirror sounds as it sets off, unfolding
|
||||
// or folding, and not again while it keeps moving the same way
|
||||
|
||||
using mirror_moves::event;
|
||||
|
||||
TEST_CASE("mirrors: a mirror sounds once as it sets off") {
|
||||
mirror_moves::motion mirror;
|
||||
CHECK(mirror.update(0.0) == event::none); // standing folded
|
||||
CHECK(mirror.update(0.01) == event::unfolding);
|
||||
CHECK(mirror.update(0.01) == event::none); // still unfolding
|
||||
CHECK(mirror.update(0.0) == event::none); // unfolded
|
||||
CHECK(mirror.update(-0.01) == event::folding);
|
||||
CHECK(mirror.update(-0.01) == event::none);
|
||||
}
|
||||
|
||||
TEST_CASE("mirrors: a mirror turned back halfway sounds the other way") {
|
||||
mirror_moves::motion mirror;
|
||||
REQUIRE(mirror.update(0.01) == event::unfolding);
|
||||
CHECK(mirror.update(-0.01) == event::folding); // the vehicle got too fast while it was unfolding
|
||||
}
|
||||
|
||||
TEST_CASE("mirrors: a mirror setting off again the same way sounds again") {
|
||||
mirror_moves::motion mirror;
|
||||
REQUIRE(mirror.update(0.01) == event::unfolding);
|
||||
REQUIRE(mirror.update(0.0) == event::none); // stopped, the doors lost their permit say
|
||||
CHECK(mirror.update(0.01) == event::unfolding);
|
||||
}
|
||||
52
tests/unit/test_pumps.cpp
Normal file
52
tests/unit/test_pumps.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
// the joint oil and fuel pump switch (MaSzyna-EU07/maszyna#81) works the pumps which aren't started automatically
|
||||
|
||||
namespace {
|
||||
|
||||
test::vehicle pumps_vehicle(std::string const &Fuelstart, std::string const &Oilstart) {
|
||||
return test::vehicle{test::definition::load("wagon").set("Cntrl", "FuelStart", Fuelstart).set("Cntrl", "OilStart", Oilstart)};
|
||||
}
|
||||
|
||||
// what the two-state joint switch does when pressed, as TTrain::OnCommand_oilfuelpumptoggle()
|
||||
void press_joint_switch(test::vehicle &Vehicle) {
|
||||
auto const enable{false == Vehicle->OilFuelPumpsEnabled()};
|
||||
Vehicle->FuelPumpSwitch(enable, range_t::local);
|
||||
Vehicle->FuelPumpSwitchOff(false == enable, range_t::local);
|
||||
Vehicle->OilPumpSwitch(enable, range_t::local);
|
||||
Vehicle->OilPumpSwitchOff(false == enable, range_t::local);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("pumps: the joint switch turns both manual pumps on, then off") {
|
||||
auto vehicle{pumps_vehicle("Manual", "Manual")};
|
||||
REQUIRE_FALSE(vehicle->OilFuelPumpsEnabled());
|
||||
press_joint_switch(vehicle);
|
||||
CHECK(vehicle->FuelPump.is_enabled);
|
||||
CHECK(vehicle->OilPump.is_enabled);
|
||||
CHECK(vehicle->OilFuelPumpsEnabled());
|
||||
press_joint_switch(vehicle);
|
||||
CHECK_FALSE(vehicle->FuelPump.is_enabled);
|
||||
CHECK_FALSE(vehicle->OilPump.is_enabled);
|
||||
}
|
||||
|
||||
TEST_CASE("pumps: with one pump started automatically the joint switch still turns the other one off") {
|
||||
auto vehicle{pumps_vehicle("Manual", "Automatic")};
|
||||
press_joint_switch(vehicle);
|
||||
REQUIRE(vehicle->FuelPump.is_enabled);
|
||||
CHECK(vehicle->OilFuelPumpsEnabled()); // the switch shows on
|
||||
press_joint_switch(vehicle);
|
||||
CHECK_FALSE(vehicle->FuelPump.is_enabled);
|
||||
}
|
||||
|
||||
TEST_CASE("pumps: with one manual pump on and the other off the joint switch turns both on") {
|
||||
auto vehicle{pumps_vehicle("Manual", "Manual")};
|
||||
vehicle->FuelPumpSwitch(true, range_t::local);
|
||||
REQUIRE_FALSE(vehicle->OilFuelPumpsEnabled());
|
||||
press_joint_switch(vehicle);
|
||||
CHECK(vehicle->FuelPump.is_enabled);
|
||||
CHECK(vehicle->OilPump.is_enabled);
|
||||
}
|
||||
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());
|
||||
}
|
||||
16
tests/unit/test_switches.cpp
Normal file
16
tests/unit/test_switches.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
// the kinds of the cab switches, given in the Switches: section of the vehicle definition
|
||||
|
||||
TEST_CASE("switches: the train heating switch can spring back (MaSzyna-EU07/maszyna#85)") {
|
||||
SUBCASE("impulse") {
|
||||
test::vehicle vehicle{test::definition::load("cab_vehicle").set("Switches", "TrainHeating", "Impulse")};
|
||||
CHECK(vehicle->HeatingSwitchImpulse); // whichever the case it's written in
|
||||
}
|
||||
SUBCASE("a plain switch by default") {
|
||||
test::vehicle vehicle{test::definition::load("cab_vehicle")};
|
||||
CHECK_FALSE(vehicle->HeatingSwitchImpulse);
|
||||
}
|
||||
}
|
||||
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::vector<std::byte> make_standin(TMoverParameters &Mover) {
|
||||
std::vector<std::byte> standin(sizeof(TDynamicObject)); // zeroed
|
||||
reinterpret_cast<TDynamicObject *>(standin.data())->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 &other{m_vehicles[1 - i]}; // its stand-in is what this one points at
|
||||
auto &mover{self.owner->mover()};
|
||||
auto const &othermover{other.owner->mover()};
|
||||
auto &neighbour{mover.Neighbours[self.end]};
|
||||
neighbour.vehicle = as_neighbour(other.standin.data());
|
||||
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,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -64,8 +66,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}; // 1 m apart
|
||||
// pair.run(5.0); // for 5 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::vector<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