mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 00:02:26 +01:00
fix(physics): automatic couplers don't catch above the safe collision speed (#86)
This commit is contained in:
@@ -461,6 +461,7 @@ set(CORE_SOURCES
|
|||||||
"utilities/parser.cpp"
|
"utilities/parser.cpp"
|
||||||
"utilities/utilities.cpp"
|
"utilities/utilities.cpp"
|
||||||
"scripting/ladderlogic.cpp"
|
"scripting/ladderlogic.cpp"
|
||||||
|
"vehicle/horn.cpp"
|
||||||
)
|
)
|
||||||
add_library(eu07_core STATIC ${CORE_SOURCES})
|
add_library(eu07_core STATIC ${CORE_SOURCES})
|
||||||
target_link_libraries(eu07_core PUBLIC eu07_settings)
|
target_link_libraries(eu07_core PUBLIC eu07_settings)
|
||||||
|
|||||||
@@ -5032,6 +5032,14 @@ void TMoverParameters::ComputeTotalForce(double dt)
|
|||||||
{
|
{
|
||||||
SlippingWheels = true;
|
SlippingWheels = true;
|
||||||
}
|
}
|
||||||
|
if (true == SlippingWheels && Vel < 0.1 // the same crude trap: (nearly) stopped vehicle...
|
||||||
|
&& std::abs(nrot * M_PI * WheelDiameter - V) < 0.1 / 3.6) // ...with wheels turning at its speed, i.e. locked wheels at the end of a skid
|
||||||
|
{
|
||||||
|
// locked wheels never pass the rotation speed test below once the vehicle stops, and a skidding vehicle
|
||||||
|
// keeps its braking force capped by the sliding adhesion, so the rest of the consist could push it around
|
||||||
|
SlippingWheels = false;
|
||||||
|
nrot = V / M_PI / WheelDiameter;
|
||||||
|
}
|
||||||
double temp_nrot = nrot;
|
double temp_nrot = nrot;
|
||||||
if (true == SlippingWheels)
|
if (true == SlippingWheels)
|
||||||
{
|
{
|
||||||
|
|||||||
39
tests/unit/test_horn.cpp
Normal file
39
tests/unit/test_horn.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "doctest.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));
|
||||||
|
}
|
||||||
63
tests/unit/test_wheelslip.cpp
Normal file
63
tests/unit/test_wheelslip.cpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#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 (double t = 0.0; t < 600.0; t += 0.01) {
|
||||||
|
result.skidded |= Coach->SlippingWheels;
|
||||||
|
auto const speed{std::abs(Coach.speed())};
|
||||||
|
if (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);
|
||||||
|
}
|
||||||
@@ -1303,6 +1303,10 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
|
|||||||
#endif
|
#endif
|
||||||
asNextStop = TrainParams.NextStop(); // informacja o końcu trasy
|
asNextStop = TrainParams.NextStop(); // informacja o końcu trasy
|
||||||
TrainParams.NewName("none"); // czyszczenie nieaktualnego rozkładu
|
TrainParams.NewName("none"); // czyszczenie nieaktualnego rozkładu
|
||||||
|
// koniec kursu, gasimy wyświetlacze relacji (tablice blaszane zostają)
|
||||||
|
for( auto *vehicle { pVehicles[ end::front ] }; vehicle != nullptr; vehicle = vehicle->Next() ) {
|
||||||
|
vehicle->DestinationClear();
|
||||||
|
}
|
||||||
// ma nie podjeżdżać pod W4 i ma je pomijać
|
// ma nie podjeżdżać pod W4 i ma je pomijać
|
||||||
iDrivigFlags &= ~moveStopCloser;
|
iDrivigFlags &= ~moveStopCloser;
|
||||||
if( false == TestFlag( iDrivigFlags, movePushPull ) ) {
|
if( false == TestFlag( iDrivigFlags, movePushPull ) ) {
|
||||||
|
|||||||
@@ -7794,6 +7794,13 @@ int TDynamicObject::RouteWish(TTrack *tr)
|
|||||||
return Mechanik ? Mechanik->CrossRoute(tr) : 0; // wg AI albo prosto
|
return Mechanik ? Mechanik->CrossRoute(tr) : 0; // wg AI albo prosto
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void TDynamicObject::DestinationClear() {
|
||||||
|
|
||||||
|
if( false == DestinationSign.has_light ) { return; } // physical destination signs remain up until manually changed
|
||||||
|
// update_destinations() shows the blank sign in place of the missing destination
|
||||||
|
DestinationSign.destination = null_handle;
|
||||||
|
}
|
||||||
|
|
||||||
void TDynamicObject::DestinationSet(std::string to, std::string numer) {
|
void TDynamicObject::DestinationSet(std::string to, std::string numer) {
|
||||||
// ustawienie stacji docelowej oraz wymiennej tekstury 4, jeśli istnieje plik
|
// ustawienie stacji docelowej oraz wymiennej tekstury 4, jeśli istnieje plik
|
||||||
// w zasadzie, to każdy wagon mógłby mieć inną stację docelową
|
// w zasadzie, to każdy wagon mógłby mieć inną stację docelową
|
||||||
|
|||||||
@@ -816,6 +816,8 @@ private:
|
|||||||
// zapytanie do AI, po którym segmencie skrzyżowania jechać
|
// zapytanie do AI, po którym segmencie skrzyżowania jechać
|
||||||
int RouteWish(TTrack *tr);
|
int RouteWish(TTrack *tr);
|
||||||
void DestinationSet(std::string to, std::string numer);
|
void DestinationSet(std::string to, std::string numer);
|
||||||
|
// blanks electronic destination sign at the end of the service; physical signs stay until changed
|
||||||
|
void DestinationClear();
|
||||||
material_handle DestinationFind( std::string Destination );
|
material_handle DestinationFind( std::string Destination );
|
||||||
void OverheadTrack(float o);
|
void OverheadTrack(float o);
|
||||||
glm::dvec3 get_future_movement() const;
|
glm::dvec3 get_future_movement() const;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
|
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "vehicle/Train.h"
|
#include "vehicle/Train.h"
|
||||||
|
#include "vehicle/horn.h"
|
||||||
|
|
||||||
#include "utilities/Globals.h"
|
#include "utilities/Globals.h"
|
||||||
#include "simulation/simulation.h"
|
#include "simulation/simulation.h"
|
||||||
@@ -7841,16 +7842,10 @@ void TTrain::OnCommand_hornlowactivate(TTrain *Train, command_data const &Comman
|
|||||||
if (Command.action == GLFW_PRESS)
|
if (Command.action == GLFW_PRESS)
|
||||||
{
|
{
|
||||||
// only need to react to press, sound will continue until stopped
|
// only need to react to press, sound will continue until stopped
|
||||||
if (false == TestFlag(Train->mvOccupied->WarningSignal, 1))
|
if (false == TestFlag(Train->mvOccupied->WarningSignal, horn::low))
|
||||||
{
|
{
|
||||||
// turn on
|
// turn on
|
||||||
Train->mvOccupied->WarningSignal |= 1;
|
Train->mvOccupied->WarningSignal = horn::pressed(Train->mvOccupied->WarningSignal, horn::low, Train->has_single_horn_lever());
|
||||||
/*
|
|
||||||
if( true == TestFlag( Train->mvOccupied->WarningSignal, 2 ) ) {
|
|
||||||
// low and high horn are treated as mutually exclusive
|
|
||||||
Train->mvControlled->WarningSignal &= ~2;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// visual feedback
|
// visual feedback
|
||||||
Train->ggHornButton.UpdateValue(-1.0);
|
Train->ggHornButton.UpdateValue(-1.0);
|
||||||
Train->ggHornLowButton.UpdateValue(1.0);
|
Train->ggHornLowButton.UpdateValue(1.0);
|
||||||
@@ -7863,9 +7858,13 @@ void TTrain::OnCommand_hornlowactivate(TTrain *Train, command_data const &Comman
|
|||||||
// NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
|
// NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
|
||||||
Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
|
Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
|
||||||
*/
|
*/
|
||||||
Train->mvOccupied->WarningSignal &= ~1;
|
Train->mvOccupied->WarningSignal = horn::released(Train->mvOccupied->WarningSignal, horn::low);
|
||||||
// visual feedback
|
// visual feedback
|
||||||
Train->ggHornButton.UpdateValue(0.0);
|
if (false == (Train->has_single_horn_lever() && TestFlag(Train->mvOccupied->WarningSignal, horn::high)))
|
||||||
|
{
|
||||||
|
// leave the single lever alone if it was moved to the other tone in the meantime
|
||||||
|
Train->ggHornButton.UpdateValue(0.0);
|
||||||
|
}
|
||||||
Train->ggHornLowButton.UpdateValue(0.0);
|
Train->ggHornLowButton.UpdateValue(0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7885,16 +7884,10 @@ void TTrain::OnCommand_hornhighactivate(TTrain *Train, command_data const &Comma
|
|||||||
if (Command.action == GLFW_PRESS)
|
if (Command.action == GLFW_PRESS)
|
||||||
{
|
{
|
||||||
// only need to react to press, sound will continue until stopped
|
// only need to react to press, sound will continue until stopped
|
||||||
if (false == TestFlag(Train->mvOccupied->WarningSignal, 2))
|
if (false == TestFlag(Train->mvOccupied->WarningSignal, horn::high))
|
||||||
{
|
{
|
||||||
// turn on
|
// turn on
|
||||||
Train->mvOccupied->WarningSignal |= 2;
|
Train->mvOccupied->WarningSignal = horn::pressed(Train->mvOccupied->WarningSignal, horn::high, Train->has_single_horn_lever());
|
||||||
/*
|
|
||||||
if( true == TestFlag( Train->mvOccupied->WarningSignal, 1 ) ) {
|
|
||||||
// low and high horn are treated as mutually exclusive
|
|
||||||
Train->mvControlled->WarningSignal &= ~1;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// visual feedback
|
// visual feedback
|
||||||
Train->ggHornButton.UpdateValue(1.0);
|
Train->ggHornButton.UpdateValue(1.0);
|
||||||
Train->ggHornHighButton.UpdateValue(1.0);
|
Train->ggHornHighButton.UpdateValue(1.0);
|
||||||
@@ -7907,9 +7900,13 @@ void TTrain::OnCommand_hornhighactivate(TTrain *Train, command_data const &Comma
|
|||||||
// NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
|
// NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
|
||||||
Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
|
Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
|
||||||
*/
|
*/
|
||||||
Train->mvOccupied->WarningSignal &= ~2;
|
Train->mvOccupied->WarningSignal = horn::released(Train->mvOccupied->WarningSignal, horn::high);
|
||||||
// visual feedback
|
// visual feedback
|
||||||
Train->ggHornButton.UpdateValue(0.0);
|
if (false == (Train->has_single_horn_lever() && TestFlag(Train->mvOccupied->WarningSignal, horn::low)))
|
||||||
|
{
|
||||||
|
// leave the single lever alone if it was moved to the other tone in the meantime
|
||||||
|
Train->ggHornButton.UpdateValue(0.0);
|
||||||
|
}
|
||||||
Train->ggHornHighButton.UpdateValue(0.0);
|
Train->ggHornHighButton.UpdateValue(0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,9 @@ class TTrain {
|
|||||||
bool initialize_button(cParser &Parser, std::string const &Label, int const Cabindex);
|
bool initialize_button(cParser &Parser, std::string const &Label, int const Cabindex);
|
||||||
// helper, returns true for EMU with oerlikon brake
|
// helper, returns true for EMU with oerlikon brake
|
||||||
bool is_eztoer() const;
|
bool is_eztoer() const;
|
||||||
|
// helper, returns true if both horn tones are operated with the same lever, without separate low and high tone buttons
|
||||||
|
bool has_single_horn_lever() const {
|
||||||
|
return ggHornButton.SubModel != nullptr && ggHornLowButton.SubModel == nullptr && ggHornHighButton.SubModel == nullptr; }
|
||||||
// locates nearest vehicle belonging to the consist
|
// locates nearest vehicle belonging to the consist
|
||||||
TDynamicObject *find_nearest_consist_vehicle(bool freefly, glm::vec3 pos) const;
|
TDynamicObject *find_nearest_consist_vehicle(bool freefly, glm::vec3 pos) const;
|
||||||
// mover master controller to specified position
|
// mover master controller to specified position
|
||||||
|
|||||||
24
vehicle/horn.cpp
Normal file
24
vehicle/horn.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "vehicle/horn.h"
|
||||||
|
|
||||||
|
namespace horn {
|
||||||
|
|
||||||
|
int pressed(int const Signal, int const Tone, bool const Singlelever) {
|
||||||
|
auto const other{Tone == low ? high : low};
|
||||||
|
return (Signal | Tone) & (Singlelever ? ~other : ~0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int released(int const Signal, int const Tone) {
|
||||||
|
return Signal & ~Tone;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace horn
|
||||||
24
vehicle/horn.h
Normal file
24
vehicle/horn.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// the horn controls of the cab, on the WarningSignal bits of the vehicle (1: low tone, 2: high tone)
|
||||||
|
namespace horn {
|
||||||
|
|
||||||
|
int const low{1};
|
||||||
|
int const high{2};
|
||||||
|
|
||||||
|
// the signal after the key or button of the Tone is pressed. a single lever for both tones, without
|
||||||
|
// separate buttons for them, can't sound both tones at once
|
||||||
|
int pressed(int Signal, int Tone, bool Singlelever);
|
||||||
|
// the signal after the key or button of the Tone is released
|
||||||
|
int released(int Signal, int Tone);
|
||||||
|
|
||||||
|
} // namespace horn
|
||||||
Reference in New Issue
Block a user