17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-12 01:48:14 +01:00

Merge remote-tracking branch 'origin/master' into fix/est3-valve

This commit is contained in:
pwarszawski
2026-11-07 21:17:04 +01:00
192 changed files with 13695 additions and 490 deletions

View File

@@ -30,6 +30,8 @@ http://mozilla.org/MPL/2.0/.
#include "world/station.h"
#include "input/keyboardinput.h"
#include <format>
#define LOGVELOCITY 0
#define LOGORDERS 1
#define LOGSTOPS 1
@@ -1093,10 +1095,9 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
if (Point.fDist < passengerstopmaxdistance * 0.5 ) {
// zaliczamy posterunek w pewnej odległości przed (choć W4 nie zasłania już semafora)
#if LOGSTOPS
WriteLog(
pVehicle->asName + " as " + TrainParams.TrainName
+ ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute)
+ " passed " + asNextStop); // informacja
WriteLog(std::format(
"{} as {}: at {}:{} passed {}",
pVehicle->asName, TrainParams.TrainName, simulation::Time.data().wHour, simulation::Time.data().wMinute, asNextStop)); // informacja
#endif
// przy jakim dystansie (stanie licznika) ma przesunąć na następny postój
fLastStopExpDist = mvOccupied->DistCounter + 0.250 + 0.001 * fLength;
@@ -1253,10 +1254,9 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
TrainParams.StationIndexInc(); // przejście do następnej
asNextStop = TrainParams.NextStop(); // pobranie kolejnego miejsca zatrzymania
#if LOGSTOPS
WriteLog(
pVehicle->asName + " as " + TrainParams.TrainName
+ ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute)
+ " next " + asNextStop); // informacja
WriteLog(std::format(
"{} as {}: at {}:{} next {}",
pVehicle->asName, TrainParams.TrainName, simulation::Time.data().wHour, simulation::Time.data().wMinute, asNextStop)); // informacja
#endif
// update consist weight, brake settings and ai braking tables
// NOTE: this calculation is expected to run after completing loading/unloading
@@ -1297,13 +1297,16 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
else {
// jeśli dojechaliśmy do końca rozkładu
#if LOGSTOPS
WriteLog(
pVehicle->asName + " as " + TrainParams.TrainName
+ ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute)
+ " end of route."); // informacja
WriteLog(std::format(
"{} as {}: at {}:{} end of route.",
pVehicle->asName, TrainParams.TrainName, simulation::Time.data().wHour, simulation::Time.data().wMinute)); // informacja
#endif
asNextStop = TrainParams.NextStop(); // informacja o końcu trasy
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ć
iDrivigFlags &= ~moveStopCloser;
if( false == TestFlag( iDrivigFlags, movePushPull ) ) {

View File

@@ -3381,15 +3381,12 @@ bool TDynamicObject::Update(double dt, double dt1)
&& MoverParameters->eimc[eimc_p_abed] < 0.001)
FzadED = 0; //pętla bezpieczeństwa - bez ED
auto const FzadPN = Fzad - FrED * MoverParameters->MED_FrED_factor;
//np = 0;
// BUG: likely memory leak, allocation per inner loop, deleted only once outside
// TODO: sort this shit out
bool* PrzekrF = new bool[np];
std::vector<bool> PrzekrF( np );
float nPrzekrF = 0;
bool test = true;
float* FzED = new float[np];
float* FzEP = new float[np];
float* FmaxEP = new float[np];
std::vector<float> FzED( np );
std::vector<float> FzEP( np );
std::vector<float> FmaxEP( np );
// 3. ustaw pojazdom sile hamowania ED
// - proporcjonalnie do mozliwosci
@@ -3548,11 +3545,6 @@ bool TDynamicObject::Update(double dt, double dt1)
}
delete[] PrzekrF;
delete[] FzED;
delete[] FzEP;
delete[] FmaxEP;
MED_oldFED = FzadED;
}
@@ -3627,6 +3619,7 @@ bool TDynamicObject::Update(double dt, double dt1)
if( dRailLength > 0.0 ) {
for( auto &axle : m_axlesounds ) {
axle.distance = axle.offset;
axle.atjoints = false;
}
}
dRailLength = MyTrack->fSoundDistance;
@@ -3660,36 +3653,32 @@ bool TDynamicObject::Update(double dt, double dt1)
1 :
ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) };
for( auto &axle : m_axlesounds ) {
axle.distance += dDOMoveLen * directioninconsist;
if( axle.distance < 0
|| axle.distance > dRailLength ) {
axle.distance = clamp_circular( axle.distance, dRailLength );
if( MoverParameters->Vel > 0.1 ) {
// NOTE: for combined clatter sound we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range
auto const frequency = true == axle.clatter.is_combined() ? MoverParameters->Vel * 0.01 : 1.0;
axle.clatter
.pitch( frequency )
.gain( volume )
.play();
// crude bump simulation, drop down on even axles, move back up on
// the odd ones
// MoverParameters->AccVert += (MoverParameters->Vel*0.1f) *
double const headroom = std::clamp(
MoverParameters->Vmax - ( MoverParameters->Vel + MoverParameters->Vmax * 0.32f ),
0.0, MoverParameters->Vmax );
double const jolt = headroom * .05f * ( MyTrack->iDamageFlag * 0.25f );
if (MyTrack->eType == tt_Normal)
{
MoverParameters->AccVert += std::clamp( jolt, 0.0, 4.0 );
}
else if (MyTrack->eType == tt_Switch)
{
double const accHorizontal = std::clamp( jolt, 0.0, 1.0 ) * ((axleindex % 2) != 0 ? 1 : -1);
MoverParameters->AccS += accHorizontal;
MoverParameters->AccN += accHorizontal;
MoverParameters->AccVert += std::clamp( jolt, 0.0, 2.0 );
}
}
if( axle_passed_joint( axle, dDOMoveLen, directioninconsist )
&& MoverParameters->Vel > 0.1 ) {
// NOTE: for combined clatter sound we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range
auto const frequency = true == axle.clatter.is_combined() ? MoverParameters->Vel * 0.01 : 1.0;
axle.clatter
.pitch( frequency )
.gain( volume )
.play();
// crude bump simulation, drop down on even axles, move back up on
// the odd ones
// MoverParameters->AccVert += (MoverParameters->Vel*0.1f) *
double const headroom = std::clamp(
MoverParameters->Vmax - ( MoverParameters->Vel + MoverParameters->Vmax * 0.32f ),
0.0, MoverParameters->Vmax );
double const jolt = headroom * .05f * ( MyTrack->iDamageFlag * 0.25f );
if (MyTrack->eType == tt_Normal)
{
MoverParameters->AccVert += std::clamp( jolt, 0.0, 4.0 );
}
else if (MyTrack->eType == tt_Switch)
{
double const accHorizontal = std::clamp( jolt, 0.0, 1.0 ) * ((axleindex % 2) != 0 ? 1 : -1);
MoverParameters->AccS += accHorizontal;
MoverParameters->AccN += accHorizontal;
MoverParameters->AccVert += std::clamp( jolt, 0.0, 2.0 );
}
}
++axleindex;
}
@@ -4150,6 +4139,8 @@ bool TDynamicObject::Update(double dt, double dt1)
// mirrors
auto const mirrorleft { dMirrorMoveL };
auto const mirrorright { dMirrorMoveR };
if( MoverParameters->Vel > MoverParameters->MirrorVelClose
|| MoverParameters->CabActive == 0 && activation::mirrors
|| MoverParameters->MirrorForbidden ) {
@@ -4180,6 +4171,8 @@ bool TDynamicObject::Update(double dt, double dt1)
dMirrorMoveR + 1.0 * dt1 );
}
}
play_mirror_sounds( side::left, m_mirrormotion[ side::left ].update( dMirrorMoveL - mirrorleft ) );
play_mirror_sounds( side::right, m_mirrormotion[ side::right ].update( dMirrorMoveR - mirrorright ) );
// compartment lights
// if the vehicle has a controller, we base the light state on state of the controller otherwise we check the vehicle itself
@@ -4825,12 +4818,13 @@ void TDynamicObject::RenderSounds() {
// horns
{
// for moving vehicle combine regular horn activation flag with emergency brake horn activation flag, if the brake is active
auto const warningsignal { (
MoverParameters->Vel > 0.5 && MoverParameters->AlarmChainFlag ?
// for moving vehicle combine regular horn activation flag with emergency brake horn activation flag, if the brake is active;
// the horns without the air or the voltage they need stay silent
auto const warningsignal { MoverParameters->SoundingHorns(
( MoverParameters->Vel > 0.5 && MoverParameters->AlarmChainFlag ?
MoverParameters->EmergencyBrakeWarningSignal :
0 )
| MoverParameters->WarningSignal };
| MoverParameters->WarningSignal ) };
if( TestFlag( warningsignal, 1 ) ) {
sHorn1.play( sound_flags::exclusive | sound_flags::looping );
@@ -4858,7 +4852,7 @@ void TDynamicObject::RenderSounds() {
#endif
&& ( // compound test whether the vehicle belongs to user-driven consist (as these don't emit outer noise in cab view)
FreeFlyModeFlag ? true : // in external view all vehicles emit outer noise
// Global.pWorld->train() == nullptr ? true : // (can skip this check, with no player train the external view is a given)
simulation::Train == nullptr ? true : // no player train, e.g. it went through a portal and the view isn't switched yet
ctOwner == nullptr ? true : // standalone vehicle, can't be part of user-driven train
ctOwner != simulation::Train->Dynamic()->ctOwner ? true : // confirmed isn't a part of the user-driven train
Global.CabWindowOpen ? true : // sticking head out we get to hear outer noise
@@ -6363,6 +6357,19 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co
}
}
else if( token == "mirroropen:" || token == "mirrorclose:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &mirror : m_mirrorsounds ) {
// apply configuration to all defined mirrors, but preserve their individual offsets
auto &sound { token == "mirroropen:" ? mirror.unfold : mirror.fold };
auto const mirroroffset { sound.offset() };
sound = soundtemplate;
sound.offset( mirroroffset );
}
}
else if( token == "doorstepclose:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
@@ -6576,6 +6583,27 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co
}
}
else if( token == "mirrors:" ) {
// a list of offsets along vehicle's z-axis of the mirror pairs, one at each cab; followed with "end"
while( (token = parser.getToken<std::string>()) != ""
&& token != "end" ) {
// vehicle faces +Z in 'its' space, as for the doors negative value means ahead of centre
auto const offset { std::atof( token.c_str() ) * -1.f };
mirror_sounds mirror;
mirror.end = offset >= 0.f ? end::front : end::rear;
for( auto const mirrorside : { side::left, side::right } ) {
auto const location { glm::vec3 {
MoverParameters->Dim.W * ( mirrorside == side::left ? 0.5f : -0.5f ),
MoverParameters->Dim.H * 0.5f,
offset } };
mirror.placement = mirrorside;
mirror.unfold.offset( location );
mirror.fold.offset( location );
m_mirrorsounds.emplace_back( mirror );
}
}
}
else if( token == "tractionmotors:" ) {
// a list of offsets along vehicle's z-axis; followed with "end"
while( (token = parser.getToken<std::string>()) != ""
@@ -7794,7 +7822,29 @@ int TDynamicObject::RouteWish(TTrack *tr)
return Mechanik ? Mechanik->CrossRoute(tr) : 0; // wg AI albo prosto
};
void TDynamicObject::DestinationSet(std::string to, std::string numer) {
void TDynamicObject::play_mirror_sounds( side const Side, mirror_moves::event const Event ) {
if( Event == mirror_moves::event::none ) { return; }
// as UpdateMirror(), only the mirrors of the occupied cab move
if( MoverParameters->CabOccupied == 0 ) { return; }
auto const cabend { MoverParameters->CabOccupied > 0 ? end::front : end::rear };
for( auto &mirror : m_mirrorsounds ) {
if( mirror.placement != Side || mirror.end != cabend ) { continue; }
auto &sound { Event == mirror_moves::event::unfolding ? mirror.unfold : mirror.fold };
auto &other { Event == mirror_moves::event::unfolding ? mirror.fold : mirror.unfold };
other.stop();
sound.play( sound_flags::exclusive );
}
}
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 const &to, std::string const &numer) {
// ustawienie stacji docelowej oraz wymiennej tekstury 4, jeśli istnieje plik
// w zasadzie, to każdy wagon mógłby mieć inną stację docelową
// zwłaszcza w towarowych, pod kątem zautomatyzowania maewrów albo pracy górki
@@ -7823,6 +7873,15 @@ void TDynamicObject::DestinationSet(std::string to, std::string numer) {
if( DestinationSign.script.empty() ) { return; } // no script so no way to make the texture
if( numer == "none" ) { return; } // blank or incomplete/malformed timetable, don't bother
auto const trainname { [this]() -> std::string {
if( ctOwner != nullptr ) { return ctOwner->TrainName(); }
if( Mechanik != nullptr ) { return Mechanik->TrainName(); }
return "none"; } }; // shouldn't get here but, eh
auto const instance { [this]() -> std::string {
if( DestinationSign.instancing == "name" ) { return MoverParameters->Name; }
if( DestinationSign.instancing == "type" ) { return MoverParameters->TypeName; }
return "none"; } };
std::string signrequest {
"make:"
+ DestinationSign.script
@@ -7834,17 +7893,10 @@ void TDynamicObject::DestinationSet(std::string to, std::string numer) {
ctOwner->Vehicle()->MoverParameters->Name ) // owned vehicle, safer to point to owner as carriages can have identical names
// basic instancing string
// NOTE: underscore doesn't have any magic meaning for the time being, it's just less likely to conflict with regular dictionary keys
+ "&_id1=" + (
ctOwner != nullptr ? ctOwner->TrainName() :
Mechanik != nullptr ? Mechanik->TrainName() :
"none" ) }; // shouldn't get here but, eh
+ "&_id1=" + trainname() };
// TBD, TODO: replace instancing with support for variables in extra parameters string?
if( false == DestinationSign.instancing.empty() ) {
signrequest +=
"&_id2=" + (
DestinationSign.instancing == "name" ? MoverParameters->Name :
DestinationSign.instancing == "type" ? MoverParameters->TypeName :
"none" );
signrequest += "&_id2=" + instance();
}
// optionl extra parameters
if( false == DestinationSign.parameters.empty() ) {
@@ -8029,17 +8081,25 @@ TDynamicObject::update_shake( double const Timedelta ) {
shakevector *= 0;
auto const iVel { std::min( GetVelocity(), 150.0 ) };
// an uneven track shakes the cab by itself, in place of the sway drawn from the curve and the jolts drawn at random, as far as it does
auto const replaced { track_uneven::replaced_shake( MyTrack != nullptr ? MyTrack->uneven_underway() : track_uneven::amounts {} ) };
if( iVel > 0.5 ) {
// acceleration-driven base shake
shakevector += glm::dvec3(
-MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway
replaced.sway ? 0.0 : -MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway
-MoverParameters->AccVert * Timedelta * Global.ShakingMultiplierUD,
-MoverParameters->AccSVBased * Timedelta * 1.5 * Global.ShakingMultiplierBF); // accent acceleration/deceleration
// unevenness of the track under the outer axles
auto const ride { 0.5 * ( Axle0.UnevenBend() + Axle1.UnevenBend() ) * ( MoverParameters->V * MoverParameters->V ) };
shakevector += glm::dvec3(
-glm::dot( ride, vLeft ) * Timedelta * 5.0 * Global.ShakingMultiplierRL,
-ride.y * Timedelta * Global.ShakingMultiplierUD,
0.0 );
}
auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) };
if( LocalRandom( iVel ) > 25.0 ) {
if( false == replaced.jolts && LocalRandom( iVel ) > 25.0 ) {
// extra shake at increased velocity
shake += ShakeSpring.ComputateForces(
glm::dvec3(
@@ -8065,6 +8125,28 @@ TDynamicObject::update_shake( double const Timedelta ) {
}
}
bool
TDynamicObject::axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ) const {
// from the follower on its side of the vehicle, as the two can be on different tracks. They stand fAxleDist apart, at the bogie pivots, and
// the offset of the axle is the place of its wheel set, so the two of a bogie come out half of ADist to either side of the pivot
auto const &follower { Axle.offset >= 0.0 ? Axle0 : Axle1 };
if( auto const railphase { follower.RailPhase( Axle.offset - std::copysign( 0.5 * fAxleDist, Axle.offset ) ) }; railphase >= 0.0 ) {
// the rails under the axle, which aren't always those under the track the vehicle is on
auto const passed { Axle.atjoints && std::abs( railphase - Axle.distance ) > 0.5 * follower.GetTrack()->fSoundDistance };
Axle.distance = railphase;
Axle.atjoints = true;
return passed;
}
Axle.atjoints = false;
Axle.distance += Move * Direction;
if( Axle.distance >= 0 && Axle.distance <= dRailLength ) {
return false;
}
Axle.distance = clamp_circular( Axle.distance, dRailLength );
return true;
}
std::pair<double, double>
TDynamicObject::shake_angles() const {

View File

@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
#include "interfaces/ITexture.h"
#include "audio/sound.h"
#include "world/Spring.h"
#include "vehicle/mirrors.h"
#include <vector>
@@ -393,6 +394,13 @@ private:
side placement {};
};
struct mirror_sounds {
sound_source unfold { sound_placement::general };
sound_source fold { sound_placement::general };
side placement {};
int end {}; // of the vehicle, the mirror moves with the cab at this end occupied
};
struct exchange_sounds {
sound_source loading { sound_placement::general };
sound_source unloading { sound_placement::general };
@@ -402,7 +410,10 @@ private:
double distance; // distance to rail joint
double offset; // axle offset from centre of the vehicle
sound_source clatter; // clatter emitter
bool atjoints { false }; // distance comes from TTrack::rail_phase
};
// whether the axle has just passed a rail joint. On a track with dipping joints it goes by them, elsewhere the axle counts a rail off itself
bool axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ) const;
struct powertrain_sounds {
sound_source inverter { sound_placement::engine };
@@ -567,6 +578,8 @@ private:
std::vector<pantograph_sounds> m_pantographsounds; // typically 2 but can be less (or more?)
std::vector<door_sounds> m_doorsounds; // can expect symmetrical arrangement, but don't count on it
bool m_doorlocks { false }; // sound helper, current state of door locks
std::vector<mirror_sounds> m_mirrorsounds;
std::array<mirror_moves::motion, 2> m_mirrormotion; // indexed by side: right, left
sound_source sHorn1 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE };
sound_source sHorn2 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE };
sound_source sHorn3 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE };
@@ -703,6 +716,8 @@ private:
void Move(double fDistance);
void FastMove(double fDistance);
void RenderSounds();
// plays the sounds of the mirrors on the Side, of the occupied cab, starting the Event
void play_mirror_sounds( side const Side, mirror_moves::event const Event );
inline glm::dvec3 GetPosition() const {
return vPosition; };
// converts location from vehicle coordinates frame to world frame
@@ -815,7 +830,9 @@ private:
void ParamSet(int what, int into);
// zapytanie do AI, po którym segmencie skrzyżowania jechać
int RouteWish(TTrack *tr);
void DestinationSet(std::string to, std::string numer);
void DestinationSet(std::string const &to, std::string const &numer);
// blanks electronic destination sign at the end of the service; physical signs stay until changed
void DestinationClear();
material_handle DestinationFind( std::string Destination );
void OverheadTrack(float o);
glm::dvec3 get_future_movement() const;

View File

@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "vehicle/Gauge.h"
#include "vehicle/gaugepositions.h"
#include "utilities/parser.h"
#include "model/Model3d.h"
#include "vehicle/DynObj.h"
@@ -302,42 +303,37 @@ bool
TGauge::UpdateValue( float fNewDesired ) {
auto const desiredtimes100 = static_cast<int>( std::round( 100.0 * fNewDesired ) );
if( desiredtimes100 == static_cast<int>( std::round( 100.0 * m_targetvalue ) ) ) {
auto const previoustimes100 = static_cast<int>( std::round( 100.0 * m_targetvalue ) );
if( desiredtimes100 == previoustimes100 ) {
return true;
}
m_targetvalue = fNewDesired;
// if there's any sound associated with new requested value, play it
// check value-specific table first...
if( auto const fullinteger { desiredtimes100 % 100 == 0 }; fullinteger ) {
// filter out values other than full integers
auto const lookup = m_soundfxvalues.find( desiredtimes100 / 100 );
if( lookup != m_soundfxvalues.end() ) {
lookup->second.play();
return true;
}
}
else {
if( desiredtimes100 % 100 != 0 ) {
// toggle the control to continous range/exclusive sound mode from now on
m_soundtype = sound_flags::exclusive;
}
// if there's any sound associated with a position reached on the way to the new requested value, play it
// check value-specific table first...
if( auto const reached = gauge_positions::last_reached( m_soundfxvalues, previoustimes100, desiredtimes100 );
reached != m_soundfxvalues.end() ) {
reached->second.play();
return true;
}
// ...and if there isn't any, fall back on the basic set...
// HACK: crude way to discern controls with continuous and quantized value range
if( auto const currentvalue = GetValue(); currentvalue < fNewDesired ) {
auto const currentvalue = GetValue();
if( currentvalue < fNewDesired && false == m_soundfxincrease.empty() ) {
// shift up
if( false == m_soundfxincrease.empty() ) {
m_soundfxincrease.play( m_soundtype );
return true;
}
m_soundfxincrease.play( m_soundtype );
return true;
}
else if( currentvalue > fNewDesired ) {
if( currentvalue > fNewDesired && false == m_soundfxdecrease.empty() ) {
// shift down
if( false == m_soundfxdecrease.empty() ) {
m_soundfxdecrease.play( m_soundtype );
return true;
}
m_soundfxdecrease.play( m_soundtype );
return true;
}
return false; // no suitable sound was found
};
}
void TGauge::PutValue(float fNewDesired)
{ // McZapkie-281102: natychmiastowe wpisanie wartosci

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "vehicle/Train.h"
#include "vehicle/horn.h"
#include "utilities/Globals.h"
#include "simulation/simulation.h"
@@ -340,6 +341,7 @@ TTrain::commandhandler_map const TTrain::m_commandhandlers = {
{user_command::fuelpumpenable, &TTrain::OnCommand_fuelpumpenable},
{user_command::fuelpumpdisable, &TTrain::OnCommand_fuelpumpdisable},
{user_command::oilpumptoggle, &TTrain::OnCommand_oilpumptoggle},
{user_command::oilfuelpumptoggle, &TTrain::OnCommand_oilfuelpumptoggle},
{user_command::oilpumpenable, &TTrain::OnCommand_oilpumpenable},
{user_command::oilpumpdisable, &TTrain::OnCommand_oilpumpdisable},
{user_command::waterheaterbreakertoggle, &TTrain::OnCommand_waterheaterbreakertoggle},
@@ -3995,6 +3997,37 @@ void TTrain::OnCommand_oilpumpdisable(TTrain *Train, command_data const &Command
}
}
void TTrain::OnCommand_oilfuelpumptoggle(TTrain *Train, command_data const &Command)
{
if (Command.action == GLFW_REPEAT)
{
return;
}
auto *vehicle{Train->mvControlled};
if (Train->ggOilFuelPumpButton.type() == TGaugeType::push)
{
// impulse switch, as the separate ones: held to start both pumps
auto const pressed{Command.action == GLFW_PRESS};
Train->ggOilFuelPumpButton.UpdateValue(pressed ? 1.0 : 0.0, Train->dsbSwitch);
vehicle->FuelPumpSwitch(pressed);
vehicle->OilPumpSwitch(pressed);
return;
}
// two-state switch, only cares about press events
if (Command.action != GLFW_PRESS)
{
return;
}
// both pumps go off if both were on, otherwise both go on; a pump started automatically doesn't take part
auto const enable{false == vehicle->OilFuelPumpsEnabled()};
Train->ggOilFuelPumpButton.UpdateValue(enable ? 1.0 : 0.0, Train->dsbSwitch);
vehicle->FuelPumpSwitch(enable);
vehicle->FuelPumpSwitchOff(false == enable);
vehicle->OilPumpSwitch(enable);
vehicle->OilPumpSwitchOff(false == enable);
}
void TTrain::OnCommand_waterheaterbreakertoggle(TTrain *Train, command_data const &Command)
{
@@ -6605,7 +6638,7 @@ void TTrain::OnCommand_heatingtoggle(TTrain *Train, command_data const &Command)
OnCommand_heatingdisable(Train, Command);
}
}
else if (Command.action == GLFW_RELEASE && Train->ggTrainHeatingButton.type() == TGaugeType::push)
else if (Command.action == GLFW_RELEASE && (Train->ggTrainHeatingButton.type() == TGaugeType::push || Train->mvOccupied->HeatingSwitchImpulse))
{
// impulse switch
// visual feedback
@@ -6623,6 +6656,11 @@ void TTrain::OnCommand_heatingenable(TTrain *Train, command_data const &Command)
// visual feedback
Train->ggTrainHeatingButton.UpdateValue(1.0, Train->dsbSwitch);
}
else if (Command.action == GLFW_RELEASE && Train->mvOccupied->HeatingSwitchImpulse)
{
// the switch springs back to the middle
Train->ggTrainHeatingButton.UpdateValue(0.0, Train->dsbSwitch);
}
}
void TTrain::OnCommand_heatingdisable(TTrain *Train, command_data const &Command)
@@ -6632,8 +6670,21 @@ void TTrain::OnCommand_heatingdisable(TTrain *Train, command_data const &Command
{
Train->mvOccupied->HeatingSwitch(false);
// visual feedback
Train->ggTrainHeatingButton.UpdateValue(Train->ggTrainHeatingButton.type() == TGaugeType::push ? 1.0 : 0.0, Train->dsbSwitch);
// visual feedback: an impulse switch is pushed the other way, down
auto position{0.0};
if (Train->mvOccupied->HeatingSwitchImpulse)
{
position = -1.0;
}
else if (Train->ggTrainHeatingButton.type() == TGaugeType::push)
{
position = 1.0;
}
Train->ggTrainHeatingButton.UpdateValue(position, Train->dsbSwitch);
}
else if (Command.action == GLFW_RELEASE && Train->mvOccupied->HeatingSwitchImpulse)
{
Train->ggTrainHeatingButton.UpdateValue(0.0, Train->dsbSwitch);
}
}
@@ -7841,16 +7892,10 @@ void TTrain::OnCommand_hornlowactivate(TTrain *Train, command_data const &Comman
if (Command.action == GLFW_PRESS)
{
// 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
Train->mvOccupied->WarningSignal |= 1;
/*
if( true == TestFlag( Train->mvOccupied->WarningSignal, 2 ) ) {
// low and high horn are treated as mutually exclusive
Train->mvControlled->WarningSignal &= ~2;
}
*/
Train->mvOccupied->WarningSignal = horn::pressed(Train->mvOccupied->WarningSignal, horn::low, Train->has_single_horn_lever());
// visual feedback
Train->ggHornButton.UpdateValue(-1.0);
Train->ggHornLowButton.UpdateValue(1.0);
@@ -7859,13 +7904,13 @@ void TTrain::OnCommand_hornlowactivate(TTrain *Train, command_data const &Comman
else if (Command.action == GLFW_RELEASE)
{
// turn off
/*
// 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;
Train->mvOccupied->WarningSignal = horn::released(Train->mvOccupied->WarningSignal, horn::low);
// 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);
}
}
@@ -7885,16 +7930,10 @@ void TTrain::OnCommand_hornhighactivate(TTrain *Train, command_data const &Comma
if (Command.action == GLFW_PRESS)
{
// 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
Train->mvOccupied->WarningSignal |= 2;
/*
if( true == TestFlag( Train->mvOccupied->WarningSignal, 1 ) ) {
// low and high horn are treated as mutually exclusive
Train->mvControlled->WarningSignal &= ~1;
}
*/
Train->mvOccupied->WarningSignal = horn::pressed(Train->mvOccupied->WarningSignal, horn::high, Train->has_single_horn_lever());
// visual feedback
Train->ggHornButton.UpdateValue(1.0);
Train->ggHornHighButton.UpdateValue(1.0);
@@ -7903,13 +7942,13 @@ void TTrain::OnCommand_hornhighactivate(TTrain *Train, command_data const &Comma
else if (Command.action == GLFW_RELEASE)
{
// turn off
/*
// 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 &= ~2;
Train->mvOccupied->WarningSignal = horn::released(Train->mvOccupied->WarningSignal, horn::high);
// 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);
}
}
@@ -7953,12 +7992,7 @@ void TTrain::OnCommand_radiotoggle(TTrain *Train, command_data const &Command)
return;
}
// NOTE: we ignore the lack of 3d model to allow system reset after receiving radio-stop signal
/*
if( false == Train->m_controlmapper.contains( "radio_sw:" ) ) {
return;
}
*/
// NOTE: works without the switch in the 3d model too, to allow system reset after receiving radio-stop signal
// only reacting to press, so the sound can loop uninterrupted
if (false == Train->mvOccupied->Radio)
{
@@ -8003,9 +8037,13 @@ void TTrain::OnCommand_radiochannelincrease(TTrain *Train, command_data const &C
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() + 1;
OnCommand_radiochannelset(Train, newCommand);
// the channel buttons of a radio switched off or without power do nothing
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() + 1;
OnCommand_radiochannelset(Train, newCommand);
}
Train->ggRadioChannelNext.UpdateValue(1.0);
}
@@ -8021,9 +8059,12 @@ void TTrain::OnCommand_radiochanneldecrease(TTrain *Train, command_data const &C
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() - 1;
OnCommand_radiochannelset(Train, newCommand);
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() - 1;
OnCommand_radiochannelset(Train, newCommand);
}
Train->ggRadioChannelPrevious.UpdateValue(1.0);
}
@@ -8049,7 +8090,7 @@ void TTrain::OnCommand_radiostopsend(TTrain *Train, command_data const &Command)
if (Command.action == GLFW_PRESS)
{
if (true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->mvOccupied->RadioPowered())
{
simulation::Region->RadioStop(Train->Dynamic()->GetPosition());
}
@@ -8067,7 +8108,7 @@ void TTrain::OnCommand_radiostopenable(TTrain *Train, command_data const &Comman
{
if (Command.action == GLFW_PRESS && Train->ggRadioStop.GetValue() == 0)
{
if (true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->mvOccupied->RadioPowered())
{
simulation::Region->RadioStop(Train->Dynamic()->GetPosition());
}
@@ -8090,7 +8131,7 @@ void TTrain::OnCommand_radiostoptest(TTrain *Train, command_data const &Command)
if (Command.action == GLFW_PRESS)
{
if (Train->RadioChannel() == 10 && true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->RadioChannel() == 10 && Train->mvOccupied->RadioPowered())
{
Train->Dynamic()->RadioStop();
}
@@ -8108,7 +8149,7 @@ void TTrain::OnCommand_radiocall1send(TTrain *Train, command_data const &Command
{
if (Command.action == GLFW_PRESS)
{
if (Train->RadioChannel() != 10 && true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->RadioChannel() != 10 && Train->mvOccupied->RadioPowered())
{
simulation::Events.queue_receivers(radio_message::call1, Train->Dynamic()->GetPosition());
}
@@ -8127,7 +8168,7 @@ void TTrain::OnCommand_radiocall3send(TTrain *Train, command_data const &Command
if (Command.action == GLFW_PRESS)
{
if (Train->RadioChannel() != 10 && true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->RadioChannel() != 10 && Train->mvOccupied->RadioPowered())
{
simulation::Events.queue_receivers(radio_message::call3, Train->Dynamic()->GetPosition());
}
@@ -8146,9 +8187,13 @@ void TTrain::OnCommand_radiovolumeincrease(TTrain *Train, command_data const &Co
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume + 0.125;
OnCommand_radiovolumeset(Train, newCommand);
// so do the volume buttons
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume + 0.125;
OnCommand_radiovolumeset(Train, newCommand);
}
Train->ggRadioVolumeNext.UpdateValue(1.0);
}
else if (Command.action == GLFW_RELEASE)
@@ -8163,9 +8208,12 @@ void TTrain::OnCommand_radiovolumedecrease(TTrain *Train, command_data const &Co
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume - 0.125;
OnCommand_radiovolumeset(Train, newCommand);
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume - 0.125;
OnCommand_radiovolumeset(Train, newCommand);
}
Train->ggRadioVolumePrevious.UpdateValue(1.0);
}
else if (Command.action == GLFW_RELEASE)
@@ -8931,9 +8979,9 @@ bool TTrain::Update(double const Deltatime)
btLampkaOpory.Turn(mvControlled->StLinFlag && mvControlled->ResistorsFlagCheck());
btLampkaBezoporowa.Turn(mvControlled->ResistorsFlagCheck() || mvControlled->MainCtrlActualPos == 0); // do EU04
btLampkaStyczn.Turn(!mvControlled->StLinFlag && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // mozna prowadzic rozruch
btLampkaPrzekRozn.Turn(!mvControlled->GroundRelay && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // relay is off and needs a reset
btLampkaNadmSil.Turn(mvControlled->FuseFlagCheck() && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // relay is off and needs a reset
btLampkaStyczn.Turn(mvControlled->LineContactorsLampLit()); // mozna prowadzic rozruch
btLampkaPrzekRozn.Turn(!mvControlled->GroundRelay && mvControlled->MainCircuitLampsEnabled()); // relay is off and needs a reset
btLampkaNadmSil.Turn(mvControlled->FuseFlagCheck() && mvControlled->MainCircuitLampsEnabled()); // relay is off and needs a reset
btLampkaUkrotnienie.Turn(TestFlag(mvControlled->Couplers[mvControlled->CabOccupied == 1 ? rear : front].CouplingFlag, control));
btLampkaHamPosp.Turn(TestFlag(mvOccupied->Hamulec->GetBrakeStatus(), 1)); // lampka drugiego stopnia hamowania
// TODO: youBy wyciągnąć flagę wysokiego stopnia
@@ -9180,14 +9228,7 @@ bool TTrain::Update(double const Deltatime)
btLampkaOporyB.Turn(mover->ResistorsFlagCheck());
btLampkaBezoporowaB.Turn(true == mover->ResistorsFlagCheck() || mover->MainCtrlActualPos == 0); // do EU04
if (mover->StLinFlag || mover->ControlPressureSwitch)
{
btLampkaStycznB.Turn(false);
}
else if (mover->BrakePress < 1.0)
{
btLampkaStycznB.Turn(true); // mozna prowadzic rozruch
}
btLampkaStycznB.Turn(mover->LineContactorsLampLit()); // mozna prowadzic rozruch
// hunter-271211: sygnalizacja poslizgu w pierwszym pojezdzie, gdy wystapi w drugim
if (mover->SlippingWheels)
{
@@ -9583,6 +9624,7 @@ bool TTrain::Update(double const Deltatime)
ggWaterCircuitsLinkButton.Update();
ggFuelPumpButton.Update();
ggOilPumpButton.Update();
ggOilFuelPumpButton.Update();
ggMotorBlowersFrontButton.Update();
ggMotorBlowersRearButton.Update();
ggMotorBlowersAllOffButton.Update();
@@ -10106,7 +10148,7 @@ void TTrain::update_sounds_radio()
std::erase_if(m_radiomessages, [](auto const &source) { return false == source.second->is_playing(); });
}
// adjust audibility of remaining messages based on current radio conditions
auto const radioenabled{true == mvOccupied->Radio && (mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable)};
auto const radioenabled{mvOccupied->RadioPowered()};
for (auto const &[messagechannel, messagesound] : m_radiomessages)
{
auto const volume{true == radioenabled && Dynamic()->Mechanik != nullptr && messagechannel == RadioChannel() ? m_radiovolume : 0.0};
@@ -10893,7 +10935,7 @@ void TTrain::radio_message(sound_source *Message, int const Channel)
m_radiomessages.emplace_back(Channel, std::make_shared<sound_source>(m_radiosound));
// assign sound to the template and play it
auto &message = *m_radiomessages.back().second.get();
auto const radioenabled{true == mvOccupied->Radio && (mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable)};
auto const radioenabled{mvOccupied->RadioPowered()};
auto const volume{true == radioenabled && Dynamic()->Mechanik != nullptr && Channel == RadioChannel() ? 1.0 : 0.0};
message.copy_sounds(*Message).gain(static_cast<float>(volume)).play();
}
@@ -11081,6 +11123,7 @@ void TTrain::clear_cab_controls()
ggWaterCircuitsLinkButton.Clear();
ggFuelPumpButton.Clear();
ggOilPumpButton.Clear();
ggOilFuelPumpButton.Clear();
ggMotorBlowersFrontButton.Clear();
ggMotorBlowersRearButton.Clear();
ggMotorBlowersAllOffButton.Clear();
@@ -11362,7 +11405,7 @@ void TTrain::set_cab_controls(int const Cab)
ggDoorStepButton.PutValue(mvOccupied->Doors.step_enabled ? 1.f : 0.f);
}
// heating
if (false == ggTrainHeatingButton.is_push())
if (false == ggTrainHeatingButton.is_push() && false == mvOccupied->HeatingSwitchImpulse)
{
ggTrainHeatingButton.PutValue(mvControlled->Heating ? 1.f : 0.f);
}
@@ -11415,6 +11458,11 @@ void TTrain::set_cab_controls(int const Cab)
{
ggOilPumpButton.PutValue(mvControlled->OilPump.is_enabled ? 1.f : 0.f);
}
// joint oil and fuel pump
if (ggOilFuelPumpButton.type() != TGaugeType::push)
{
ggOilFuelPumpButton.PutValue(mvControlled->OilFuelPumpsEnabled() ? 1.f : 0.f);
}
// traction motor fans
if (ggMotorBlowersFrontButton.type() != TGaugeType::push)
{
@@ -11742,6 +11790,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con
{"watercircuitslink_sw:", ggWaterCircuitsLinkButton},
{"fuelpump_sw:", ggFuelPumpButton},
{"oilpump_sw:", ggOilPumpButton},
{"oilfuelpump_sw:", ggOilFuelPumpButton},
{"oilpressb:", ggOilPressB},
{"motorblowersfront_sw:", ggMotorBlowersFrontButton},
{"motorblowersrear_sw:", ggMotorBlowersRearButton},

View File

@@ -195,6 +195,9 @@ class TTrain {
bool initialize_button(cParser &Parser, std::string const &Label, int const Cabindex);
// helper, returns true for EMU with oerlikon brake
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
TDynamicObject *find_nearest_consist_vehicle(bool freefly, glm::vec3 pos) const;
// mover master controller to specified position
@@ -339,6 +342,7 @@ class TTrain {
static void OnCommand_fuelpumpenable( TTrain *Train, command_data const &Command );
static void OnCommand_fuelpumpdisable( TTrain *Train, command_data const &Command );
static void OnCommand_oilpumptoggle( TTrain *Train, command_data const &Command );
static void OnCommand_oilfuelpumptoggle( TTrain *Train, command_data const &Command );
static void OnCommand_oilpumpenable( TTrain *Train, command_data const &Command );
static void OnCommand_oilpumpdisable( TTrain *Train, command_data const &Command );
static void OnCommand_waterheaterbreakertoggle( TTrain *Train, command_data const &Command );
@@ -692,7 +696,8 @@ public: // reszta może by?publiczna
TGauge ggWaterHeaterButton; // water heater switch
TGauge ggWaterCircuitsLinkButton;
TGauge ggFuelPumpButton; // fuel pump switch
TGauge ggOilPumpButton; // fuel pump switch
TGauge ggOilPumpButton; // oil pump switch
TGauge ggOilFuelPumpButton; // joint oil and fuel pump switch
TGauge ggMotorBlowersFrontButton; // front traction motor fan switch
TGauge ggMotorBlowersRearButton; // rear traction motor fan switch
TGauge ggMotorBlowersAllOffButton; // motor fans shutdown switch

35
vehicle/gaugepositions.h Normal file
View File

@@ -0,0 +1,35 @@
/*
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
#include <map>
// positions of a cab control which have sounds of their own
namespace gauge_positions {
// the entry of the last position of Positions the control reached on its way from the Previous to the Desired value,
// both in hundredths of a position, or Positions.end() if it reached none: a lever moved smoothly passes the
// positions rather than stops at them, and should sound each one it gets to
template <typename Value_>
typename std::map<int, Value_>::iterator last_reached(std::map<int, Value_> &Positions, int const Previous, int const Desired) {
auto reached{Positions.end()};
for (auto position{Positions.begin()}; position != Positions.end(); ++position) {
auto const at{position->first * 100};
if (Desired > Previous && at > Previous && at <= Desired) {
reached = position; // moving up, the last one in the ascending order is the one reached last
}
else if (Desired < Previous && at < Previous && at >= Desired) {
return position; // moving down, the first one is
}
}
return reached;
}
} // namespace gauge_positions

24
vehicle/horn.cpp Normal file
View 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
View 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

44
vehicle/mirrors.h Normal file
View File

@@ -0,0 +1,44 @@
/*
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 mirrors of a vehicle, unfolded on the side of the open doors and folded at speed
namespace mirror_moves {
enum class event {
none,
unfolding,
folding
};
// the move of a mirror from one update to the next. a mirror sounds as it sets off, unfolding or folding, not on
// every update it keeps moving the same way
struct motion {
int direction{0}; // of the last update: +1 unfolding, -1 folding, 0 standing
// the event the Change of the mirror position (0: folded, 1: unfolded) since the last update starts, if any
event update(double const Change) {
auto now{0};
if (Change > 0.0) {
now = 1;
}
else if (Change < 0.0) {
now = -1;
}
auto const setsoff{now != 0 && now != direction};
direction = now;
if (false == setsoff) {
return event::none;
}
return now > 0 ? event::unfolding : event::folding;
}
};
} // namespace mirror_moves

46
vehicle/smoke.cpp Normal file
View File

@@ -0,0 +1,46 @@
/*
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/smoke.h"
#include "McZapkie/MOVER.h"
namespace smoke {
namespace {
// how hard the engine works, scaling the spawn rate of the source
double engine_load(TMoverParameters const &Mover) {
if (Mover.dizel_spinup) {
// starting up, it puffs harder the faster it turns
return ((Mover.enrot / 4) * 0.01);
}
auto const rpmrise{(Mover.DElist[Mover.MainCtrlPosNo].RPM - Mover.enrot) / 60};
if (Mover.DirAbsolute == 0 || Mover.Im == 0) {
// idling: speeding up to the revolutions of the master controller
return ((rpmrise * 0.02) * (Mover.EnginePower * 0.005));
}
// under load: the more current the traction motors draw, the more smoke
return ((rpmrise * (std::sqrt(Mover.Im) * 0.01) * 0.02) * (Mover.EnginePower * 0.005));
}
} // namespace
float vehicle_spawn_count(TMoverParameters const &Mover, bool const Ontracks, spawn_rate const &Rate, double const Timedelta) {
if (false == Ontracks) {
return 0.f;
}
// the steady rate of the source, the way a source of the scenery spawns...
auto const steady{std::min<float>(static_cast<float>(Rate.pending + (Rate.rate * Timedelta * Rate.fidelity)), Rate.limit)};
// ...and on top of it the share of the engine
return std::min<float>(static_cast<float>(steady + (Rate.rate * Timedelta * Rate.fidelity * engine_load(Mover))), Rate.limit);
}
} // namespace smoke

30
vehicle/smoke.h Normal file
View File

@@ -0,0 +1,30 @@
/*
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
class TMoverParameters;
// the smoke of a vehicle's engine, let out by the smoke sources of its model (rendering/particles.h)
namespace smoke {
// what a smoke source spawns, before the engine of its vehicle is taken into account
struct spawn_rate {
float pending{0.f}; // particles left to spawn from the previous updates
float rate{0.f}; // particles per second
float fidelity{1.f}; // multiplier of the smoke settings
float limit{0.f}; // most particles the source can have
};
// number of particles the smoke source of a vehicle has to spawn after Timedelta seconds: the steady rate of the
// source, plus a share which grows with the work of its engine. a vehicle taken off the tracks (Ontracks false: gone through a portal, or waiting in the pool of the road
// traffic) puffs no more, the particles it already let out fade away as usual
float vehicle_spawn_count(TMoverParameters const &Mover, bool Ontracks, spawn_rate const &Rate, double Timedelta);
} // namespace smoke