17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 14:29:20 +02:00
Files
maszyna/vehicle/DynObj.cpp
Hirek193 5a5bb15fa5 multiplayer: hold the authority's readings between updates, and hear the brakes
Two complaints, one cause underneath them.

The brake and squeal sounds came out in shreds as a train got going. Both are
shaped from UnitBrakeForce and Vel, and both of those are results of a physics
step. An update arrives ten times a second; in the twenty-odd frames between
them the local physics carries on computing its own speed and its own brake
force for a train it has no business simulating, they fall away, and the next
update snaps them back. rsBrake pitches on Vel and rsPisk gains on the brake
force ratio, so what the ear got was that sawtooth.

So the physics outputs of somebody else's train are now put back after every
local step, not only on the frame an update lands: speed, brake force, wheel
and engine revolutions, engine power, wheelslip, sand, and the running flags of
the converter and the compressors, which the local physics kept switching off
because the conditions it evaluates are not the ones the owner is evaluating.

The relays are the same problem wearing a different hat. MainCtrlActualPos - the
notch the resistors are actually on, as opposed to the one the handle is at -
was being stepped by the local automatic notching, off its own guesses, so the
clicks either fired at the wrong moment or never fired at all. It travels now,
together with ScndCtrlActualPos and StLinFlag, which stops the local notching
inventing anything, and every relay heard here is one that operated over there.

The brake unit keeps a sound bitfield of its own, and that is where the
accelerator and the cylinder venting live. Reading it clears it, so watching it
had to be done without consuming it: TBrake grows PeekSoundFlag(), and only the
flags that have just come up count as an event - the vehicle only clears them
when it has an accelerator sound to play, so a bit that stays lit is one event,
not one per frame. RaiseSoundFlag() hands them to the far end.

Snapshot version 5.
2026-09-07 09:24:47 +00:00

8803 lines
383 KiB
C++

/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak, Maciej Czapkiewicz and others
*/
#include "stdafx.h"
#include "vehicle/DynObj.h"
#include "simulation/simulation.h"
#include "rendering/lightarray.h"
#include "vehicle/Camera.h"
#include "vehicle/Train.h"
#include "vehicle/Driver.h"
#include "utilities/Globals.h"
#include "utilities/Timer.h"
#include "utilities/Logs.h"
#include "network/entities.h"
#include "network/snapshot.h"
#include "utilities/glmHelpers.h"
#include "Console.h"
#include "world/Traction.h"
#include "audio/sound.h"
#include "model/MdlMngr.h"
#include "model/Model3d.h"
#include "rendering/renderer.h"
#include "application/uitranscripts.h"
#include "input/messaging.h"
#include "vehicle/Driver.h"
// Ra: taki zapis funkcjonuje lepiej, ale może nie jest optymalny
#define vWorldFront glm::vec3(0, 0, 1)
#define vWorldUp glm::vec3(0, 1, 0)
#define vWorldLeft glm::cross(vWorldUp, vWorldFront)
#define M_2PI 6.283185307179586476925286766559;
const float maxrot = (float)(M_PI / 3.0); // 60°
std::string const TDynamicObject::MED_labels[] = {
"masa: ", "amax: ", "Fzad: ", "FmPN: ", "FmED: ", "FrED: ", "FzPN: ", "nPrF: "
};
bool TDynamicObject::bDynamicRemove { false };
// helper, locates submodel with specified name in specified 3d model; returns: pointer to the submodel, or null
TSubModel *
GetSubmodelFromName( TModel3d * const Model, std::string const Name ) {
return Model ? Model->GetFromName(Name) : nullptr;
}
// Ra 2015-01: sprawdzenie dostępności tekstury o podanej nazwie
std::string
TextureTest( std::string const &Name ) {
auto const lookup {
FileExists(
{ Global.asCurrentTexturePath + Name, Name, paths::textures + Name },
{ ".mat", ".dds", ".tga", ".ktx", ".png", ".bmp", ".jpg", ".tex" } ) };
return lookup.first + lookup.second;
}
//---------------------------------------------------------------------------
void TAnimPant::common_pantograph_settings()
{ // wspólne ustawienia, liczone po przypisaniu wymiarów ramion
vPos = glm::dvec3(0, 0, 0);
fAngleL0 = glm::radians(2.8547285515689267247882521833308);
fAngleL = fAngleL0; // początkowy kąt dolnego ramienia
fAngleU0 = acos((fLenL1 * cos(fAngleL) + fHoriz) / fLenU1); // górne ramię
fAngleU = fAngleU0; // początkowy kąt
PantWys = fLenL1 * sin(fAngleL) + fLenU1 * sin(fAngleU) + fHeight; // wysokość początkowa
PantTraction = PantWys;
hvPowerWire = nullptr;
fWidthExtra = 0.381f; //(2.032m-1.027)/2
// poza obszarem roboczym jest aproksymacja łamaną o 5 odcinkach
fHeightExtra[0] = 0.0f; //+0.0762
fHeightExtra[1] = -0.01f; //+0.1524
fHeightExtra[2] = -0.03f; //+0.2286
fHeightExtra[3] = -0.07f; //+0.3048
fHeightExtra[4] = -0.15f; //+0.3810
}
void TAnimPant::AKP_4E()
{ // ustawienie wymiarów dla pantografu AKP-4E
fLenL1 = 1.22; // 1.176289 w modelach
fLenU1 = 1.755; // 1.724482197 w modelach
fHoriz = 0.535; // 0.54555075 przesunięcie ślizgu w długości pojazdu względem
// osi obrotu dolnego
// ramienia
fHeight = 0.07; // wysokość ślizgu ponad oś obrotu
fWidth = 0.635; // połowa szerokości ślizgu, 0.635 dla AKP-1 i AKP-4E
common_pantograph_settings();
};
void TAnimPant::WBL85()
{ // ustawienie wymiarów dla pantografu WBL88
// mnozniki animacji ramion dla pantografu WBL88
rd1rf = 1.f;
rd2rf = 1.2;
rg1rf = 0.875;
rg2rf = 1.0;
slizgrf = 1.f;
fLenL1 = 1.98374;
fLenU1 = 2.14199;
fHoriz = 0.142; // 0.54555075 przesunięcie ślizgu w długości pojazdu względem
// osi obrotu dolnego ramienia
fHeight = 0.09353; // wysokość ślizgu ponad oś obrotu
fWidth = 0.4969; // połowa szerokości ślizgu
common_pantograph_settings();
};
void TAnimPant::EC160_200()
{ // ustawienie wymiarów dla pantografow EC160 lub EC200
// mnozniki animacji ramion dla pantografow EC160 lub EC200
rd1rf = 1.f;
rd2rf = 0.85;
rg1rf = 1.f;
rg2rf = 1.f; // 0.833
slizgrf = 1.f;
fLenL1 = 1.98374;
fLenU1 = 2.14199;
fHoriz = 0.142; // 0.54555075 przesunięcie ślizgu w długości pojazdu względem
// osi obrotu dolnego ramienia
fHeight = 0.09353; // wysokość ślizgu ponad oś obrotu
fWidth = 0.4969; // połowa szerokości ślizgu
common_pantograph_settings();
};
void TAnimPant::DSAx()
{ // ustawienie wymiarów dla pantografow z rodziny DSA
// mnozniki animacji ramion dla pantografow z rodziny DSA
rd1rf = 1.f;
rd2rf = 1.025;
rg1rf = 0.875;
rg2rf = 1.f;
slizgrf = 1.f;
fLenL1 = 1.98374;
fLenU1 = 2.14199;
fHoriz = 0.142; // 0.54555075 przesunięcie ślizgu w długości pojazdu względem
// osi obrotu dolnego ramienia
fHeight = 0.09353; // wysokość ślizgu ponad oś obrotu
fWidth = 0.4969; // połowa szerokości ślizgu
common_pantograph_settings();
};
//---------------------------------------------------------------------------
int TAnim::TypeSet(int i, TMoverParameters currentMover, int fl)
{ // ustawienie typu animacji i zależnej od niego ilości animowanych submodeli
fMaxDist = -1.0; // normalnie nie pokazywać
switch (i)
{ // maska 0x000F: ile używa wskaźników na submodele (0 gdy jeden,
// wtedy bez tablicy)
// maska 0x00F0:
// 0-osie,1-drzwi,2-obracane,3-zderzaki,4-wózki,5-pantografy,6-tłoki
// maska 0xFF00: ile używa liczb float dla współczynników i stanu
case 0:
iFlags = 0x000;
break; // 0-oś
case 1:
iFlags = 0x010;
break; // 1-drzwi
case 2:
iFlags = 0x020;
fParam = fl ? new float[fl] : nullptr;
iFlags += fl << 8;
break; // 2-wahacz, dźwignia itp.
case 3:
iFlags = 0x030;
break; // 3-zderzak
case 4:
iFlags = 0x040;
break; // 4-wózek
case 5: // 5-pantograf - 5 submodeli
iFlags = 0x055;
fParamPants = new TAnimPant();
switch (currentMover.EnginePowerSource.CollectorParameters.PantographType) {
case TPantType::AKP_4E:
fParamPants->AKP_4E();
break;
case TPantType::DSAx:
fParamPants->DSAx();
break;
case TPantType::EC160_200:
fParamPants->EC160_200();
break;
case TPantType::WBL85:
fParamPants->WBL85();
break;
}
break;
case 6:
iFlags = 0x068;
break; // 6-tłok i rozrząd - 8 submodeli
case 7:
iFlags = 0x070;
break; // doorstep
case 8:
iFlags = 0x080;
break; // mirror
case 9:
iFlags = 0x023; // 2: Rotating/Motion-based, 8: Uses 3 submodels
break;
default:
iFlags = 0;
}
yUpdate = nullptr;
return iFlags & 15; // ile wskaźników rezerwować dla danego typu animacji
};
TAnim::~TAnim()
{ // usuwanie animacji
switch (iFlags & 0xF0)
{ // usuwanie struktur, zależnie ile zostało stworzonych
case 0x20: // 2-wahacz, dźwignia itp.
delete fParam;
break;
case 0x50: // 5-pantograf
delete fParamPants;
break;
default:
break;
}
};
/*
void TAnim::Parovoz(){
// animowanie tłoka i rozrządu parowozu
};
*/
// assigns specified texture or a group of textures to replacable texture slots
void
material_data::assign( std::string const &Replacableskin ) {
// check for the pipe method first
if( contains( Replacableskin, '|' ) ) {
cParser nameparser( Replacableskin );
nameparser.getTokens( 4, false, "|" );
int skinindex = 0;
std::string texturename; nameparser >> texturename;
while( texturename != "" && skinindex < 4 ) {
replacable_skins[ skinindex + 1 ] = GfxRenderer->Fetch_Material( texturename );
++skinindex;
texturename = ""; nameparser >> texturename;
}
multi_textures = skinindex;
}
else {
// otherwise try the basic approach
int skinindex = 0;
do {
// test quietly for file existence so we don't generate tons of false errors in the log
// NOTE: this means actual missing files won't get reported which is hardly ideal, but still somewhat better
auto const material { TextureTest( ToLower( Replacableskin + "," + std::to_string( skinindex + 1 ) ) ) };
if( true == material.empty() ) { break; }
replacable_skins[ skinindex + 1 ] = GfxRenderer->Fetch_Material( material );
++skinindex;
} while( skinindex < 4 );
multi_textures = skinindex;
if( multi_textures == 0 ) {
// zestaw nie zadziałał, próbujemy normanie
replacable_skins[ 1 ] = GfxRenderer->Fetch_Material( Replacableskin );
}
}
if( replacable_skins[ 1 ] == null_handle ) {
// last ditch attempt, check for single replacable skin texture
replacable_skins[ 1 ] = GfxRenderer->Fetch_Material( Replacableskin );
}
// BUGS! it's not entierly designed whether opacity is property of material or submodel,
// and code does confusing things with this in various places
textures_alpha = GfxRenderer->Material(replacable_skins[1])->is_translucent() ? 0x31310031 : // tekstura -1 z kanałem alfa - nie renderować w cyklu nieprzezroczystych
0x30300030; // wszystkie tekstury nieprzezroczyste - nie renderować w cyklu przezroczystych
if( GfxRenderer->Material( replacable_skins[ 2 ] )->is_translucent() ) {
// tekstura -2 z kanałem alfa - nie renderować w cyklu nieprzezroczystych
textures_alpha |= 0x02020002;
}
if( GfxRenderer->Material( replacable_skins[ 3 ] )->is_translucent() ) {
// tekstura -3 z kanałem alfa - nie renderować w cyklu nieprzezroczystych
textures_alpha |= 0x04040004;
}
if( GfxRenderer->Material( replacable_skins[ 4 ] )->is_translucent() ) {
// tekstura -4 z kanałem alfa - nie renderować w cyklu nieprzezroczystych
textures_alpha |= 0x08080008;
}
}
void TDynamicObject::destination_data::deserialize( cParser &Input ) {
while( true == deserialize_mapping( Input ) ) {
; // all work done by while()
}
}
bool TDynamicObject::destination_data::deserialize_mapping( cParser &Input ) {
// token can be a key or block end
auto const key { Input.getToken<std::string>( true, "\n\r\t ,;[]" ) };
if( true == key.empty() || key == "}" ) { return false; }
if( key == "{" ) {
script = Input.getToken<std::string>();
}
else if( key == "update:" ) {
auto const value { Input.getToken<std::string>() };
// TODO: implement
}
else if( key == "instance:" ) {
instancing = Input.getToken<std::string>();
}
else if( key == "parameters:" ) {
parameters = Input.getToken<std::string>();
}
else if( key == "background:" ) {
background = Input.getToken<std::string>();
}
return true;
}
//---------------------------------------------------------------------------
TDynamicObject * TDynamicObject::FirstFind(int &coupler_nr, int cf)
{ // szukanie skrajnego połączonego pojazdu w pociagu
// od strony sprzegu (coupler_nr) obiektu (start)
TDynamicObject *temp = this;
for (int i = 0; i < 300; i++) // ograniczenie do 300 na wypadek zapętlenia składu
{
if (!temp)
return nullptr; // Ra: zabezpieczenie przed ewentaulnymi błędami sprzęgów
if ((temp->MoverParameters->Couplers[coupler_nr].CouplingFlag & cf) != cf)
return temp; // nic nie ma już dalej podłączone sprzęgiem cf
if (coupler_nr == end::front)
{ // jeżeli szukamy od sprzęgu 0
if (temp->PrevConnected()) // jeśli mamy coś z przodu
{
if (temp->PrevConnectedNo() == end::front) // jeśli pojazd od strony sprzęgu 0 jest odwrócony
coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu
temp = temp->PrevConnected(); // ten jest od strony 0
}
else
return temp; // jeśli jednak z przodu nic nie ma
}
else
{
if (temp->NextConnected())
{
if (temp->NextConnectedNo() == end::rear) // jeśli pojazd od strony sprzęgu 1 jest odwrócony
coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu
temp = temp->NextConnected(); // ten pojazd jest od strony 1
}
else
return temp; // jeśli jednak z tyłu nic nie ma
}
}
return nullptr; // to tylko po wyczerpaniu pętli
};
//---------------------------------------------------------------------------
float TDynamicObject::GetEPP()
{ // szukanie skrajnego połączonego pojazdu w pociagu
// od strony sprzegu (coupler_nr) obiektu (start)
TDynamicObject *temp = this;
int coupler_nr = 0;
double eq = 0.0;
double am = 0.0;
for (int i = 0; i < 300; ++i) // ograniczenie do 300 na wypadek zapętlenia składu
{
if (!temp)
break; // Ra: zabezpieczenie przed ewentaulnymi błędami sprzęgów
eq += temp->MoverParameters->PipePress * temp->MoverParameters->Dim.L;
am += temp->MoverParameters->Dim.L;
if ((temp->MoverParameters->Couplers[coupler_nr].CouplingFlag & coupling::brakehose) != coupling::brakehose)
break; // nic nie ma już dalej podłączone
if (coupler_nr == 0)
{ // jeżeli szukamy od sprzęgu 0
if (temp->PrevConnected()) // jeśli mamy coś z przodu
{
if (temp->PrevConnectedNo() == end::front) // jeśli pojazd od strony sprzęgu 0 jest odwrócony
coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu
temp = temp->PrevConnected(); // ten jest od strony 0
}
else
break; // jeśli jednak z przodu nic nie ma
}
else
{
if (temp->NextConnected())
{
if (temp->NextConnectedNo() == end::rear) // jeśli pojazd od strony sprzęgu 1 jest odwrócony
coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu
temp = temp->NextConnected(); // ten pojazd jest od strony 1
}
else
break; // jeśli jednak z tyłu nic nie ma
}
}
temp = this;
coupler_nr = 1;
for (int i = 0; i < 300; i++) // ograniczenie do 300 na wypadek zapętlenia składu
{
if (!temp)
break; // Ra: zabezpieczenie przed ewentaulnymi błędami sprzęgów
eq += temp->MoverParameters->PipePress * temp->MoverParameters->Dim.L;
am += temp->MoverParameters->Dim.L;
if ((temp->MoverParameters->Couplers[coupler_nr].CouplingFlag & coupling::brakehose) != coupling::brakehose)
break; // nic nie ma już dalej podłączone
if (coupler_nr == 0)
{ // jeżeli szukamy od sprzęgu 0
if (temp->PrevConnected()) // jeśli mamy coś z przodu
{
if (temp->PrevConnectedNo() == end::front) // jeśli pojazd od strony sprzęgu 0 jest odwrócony
coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu
temp = temp->PrevConnected(); // ten jest od strony 0
}
else
break; // jeśli jednak z przodu nic nie ma
}
else
{
if (temp->NextConnected())
{
if (temp->NextConnectedNo() == end::rear) // jeśli pojazd od strony sprzęgu 1 jest odwrócony
coupler_nr = 1 - coupler_nr; // to zmieniamy kierunek sprzęgu
temp = temp->NextConnected(); // ten pojazd jest od strony 1
}
else
break; // jeśli jednak z tyłu nic nie ma
}
}
eq -= MoverParameters->PipePress * MoverParameters->Dim.L;
am -= MoverParameters->Dim.L;
return eq / am;
};
//---------------------------------------------------------------------------
TDynamicObject * TDynamicObject::GetFirstDynamic(int cpl_type, int cf)
{ // Szukanie skrajnego połączonego pojazdu w pociagu
// od strony sprzegu (cpl_type) obiektu szukajacego
// Ra: wystarczy jedna funkcja do szukania w obu kierunkach
return FirstFind(cpl_type, cf); // używa referencji
};
void TDynamicObject::ABuSetModelShake(glm::dvec3 mShake)
{
modelShake = mShake;
};
int TDynamicObject::GetPneumatic(bool front, bool red)
{
int x, y, z; // 1=prosty, 2=skośny
if (red)
{
if (front)
{
x = btCPneumatic1.GetStatus();
y = btCPneumatic1r.GetStatus();
}
else
{
x = btCPneumatic2.GetStatus();
y = btCPneumatic2r.GetStatus();
}
}
else if (front)
{
x = btPneumatic1.GetStatus();
y = btPneumatic1r.GetStatus();
}
else
{
x = btPneumatic2.GetStatus();
y = btPneumatic2r.GetStatus();
}
z = 0; // brak węży?
if (x > 0 && y > 0)
z = 3; // dwa
if (x > 0 && y == 0)
z = 1; // lewy, brak prawego
if (x == 0 && y > 0)
z = 2; // brak lewego, prawy
return z;
}
void TDynamicObject::SetPneumatic(bool front, bool red)
{
int x = 0,
ten = 0,
tamten = 0;
ten = GetPneumatic(front, red); // 1=lewy skos,2=prawy skos,3=dwa proste
if (front)
if (PrevConnected()) // pojazd od strony sprzęgu 0
tamten = PrevConnected()->GetPneumatic(PrevConnectedNo() == end::front ? true : false, red);
if (!front)
if (NextConnected()) // pojazd od strony sprzęgu 1
tamten = NextConnected()->GetPneumatic(NextConnectedNo() == end::front ? true : false, red);
if (ten == tamten) // jeśli układ jest symetryczny
switch (ten)
{
case 1:
x = 2;
break; // mamy lewy skos, dać lewe skosy
case 2:
x = 3;
break; // mamy prawy skos, dać prawe skosy
case 3: // wszystkie cztery na prosto
if (MoverParameters->Couplers[front ? end::front : end::rear].Render)
x = 1;
else
x = 4;
break;
}
else
{
if (ten == 2)
x = 4;
if (ten == 1)
x = 1;
if (ten == 3)
if (tamten == 1)
x = 4;
else
x = 1;
}
if (front)
{
if (red)
cp1 = x;
else
sp1 = x;
} // który pokazywać z przodu
else
{
if (red)
cp2 = x;
else
sp2 = x;
} // który pokazywać z tyłu
}
void TDynamicObject::UpdateAxle(TAnim *pAnim)
{ // animacja osi
size_t wheel_id = pAnim->dWheelAngle;
pAnim->smAnimated->SetRotate(float3(1, 0, 0), dWheelAngle[wheel_id]);
pAnim->smAnimated->future_transform = glm::rotate((float)glm::radians(m_future_wheels_angle[wheel_id]), glm::vec3(1.0f, 0.0f, 0.0f));
};
// animacja drzwi - przesuw
void TDynamicObject::UpdateDoorTranslate(TAnim *pAnim) {
if( pAnim->smAnimated == nullptr ) { return; }
auto const &door { MoverParameters->Doors.instances[ (
( pAnim->iNumber & 1 ) == 0 ?
side::right :
side::left ) ] };
pAnim->smAnimated->SetTranslate(
glm::vec3{
0.0,
0.0,
door.position } );
};
// animacja drzwi - obrót
void TDynamicObject::UpdateDoorRotate(TAnim *pAnim) {
if( pAnim->smAnimated == nullptr ) { return; }
auto const &door { MoverParameters->Doors.instances[ (
( pAnim->iNumber & 1 ) == 0 ?
side::right :
side::left ) ] };
pAnim->smAnimated->SetRotate(
float3(1, 0, 0),
door.position );
};
// animacja drzwi - obrót
void TDynamicObject::UpdateDoorFold(TAnim *pAnim) {
if( pAnim->smAnimated == nullptr ) { return; }
auto const &door { MoverParameters->Doors.instances[ (
( pAnim->iNumber & 1 ) == 0 ?
side::right :
side::left ) ] };
// skrzydło mniejsze
pAnim->smAnimated->SetRotate(
float3(0, 0, 1),
door.position);
// skrzydło większe
auto *sm = pAnim->smAnimated->ChildGet();
if( sm == nullptr ) { return; }
sm->SetRotate(
float3(0, 0, 1),
-door.position - door.position);
// podnóżek?
sm = sm->ChildGet();
if( sm == nullptr ) { return; }
sm->SetRotate(
float3(0, 1, 0),
door.position);
};
// animacja drzwi - odskokprzesuw
void TDynamicObject::UpdateDoorPlug(TAnim *pAnim) {
if( pAnim->smAnimated == nullptr ) { return; }
auto const &door { MoverParameters->Doors.instances[ (
( pAnim->iNumber & 1 ) == 0 ?
side::right :
side::left ) ] };
pAnim->smAnimated->SetTranslate(
glm::vec3{
std::min(
door.position * 2.f,
MoverParameters->Doors.range_out ),
0.0,
std::max(
0.f,
door.position - MoverParameters->Doors.range_out * 0.5f ) } );
}
void TDynamicObject::UpdatePant(TAnim *pAnim)
{ // animacja pantografu - 4 obracane ramiona, ślizg piąty
float a, b, c;
a = glm::degrees(pAnim->fParamPants->fAngleL - pAnim->fParamPants->fAngleL0);
b = glm::degrees(pAnim->fParamPants->fAngleU - pAnim->fParamPants->fAngleU0);
c = a + b;
if (pAnim->smElement[0])
pAnim->smElement[0]->SetRotate(float3(-1, 0, 0), a); // dolne ramie 1
if (pAnim->smElement[1])
pAnim->smElement[1]->SetRotate(float3(1, 0, 0), a * pAnim->fParamPants->rd2rf); // dolne ramie 2
if (pAnim->smElement[2])
pAnim->smElement[2]->SetRotate(float3(1, 0, 0), c); // górne ramie 1
if (pAnim->smElement[3])
pAnim->smElement[3]->SetRotate(float3(-1, 0, 0), c * pAnim->fParamPants->rg2rf); // gorne ramie 2
if (pAnim->smElement[4])
pAnim->smElement[4]->SetRotate(float3(-1, 0, 0), b * pAnim->fParamPants->slizgrf); // ślizg
}
// doorstep animation, shift
void TDynamicObject::UpdatePlatformTranslate( TAnim *pAnim ) {
if( pAnim->smAnimated == nullptr ) { return; }
auto const &door { MoverParameters->Doors.instances[ (
( pAnim->iNumber & 1 ) == 0 ?
side::right :
side::left ) ] };
pAnim->smAnimated->SetTranslate(
glm::vec3{
std::lerp( 0.f, MoverParameters->Doors.step_range, door.step_position ),
0.0,
0.0 } );
}
// doorstep animation, rotate
void TDynamicObject::UpdatePlatformRotate( TAnim *pAnim ) {
if( pAnim->smAnimated == nullptr ) { return; }
auto const &door { MoverParameters->Doors.instances[ (
( pAnim->iNumber & 1 ) == 0 ?
side::right :
side::left ) ] };
pAnim->smAnimated->SetRotate(
float3( 0, 1, 0 ),
std::lerp( 0.f, MoverParameters->Doors.step_range, door.step_position ) );
}
// mirror animation, rotate
void TDynamicObject::UpdateMirror( TAnim *pAnim ) {
if( pAnim->smAnimated == nullptr ) { return; }
// only animate the mirror if it's located on the same end of the vehicle as the active cab
auto const isactive { (
MoverParameters->CabOccupied > 0 ? ( pAnim->iNumber >> 4 == end::front ? 1.0 : 0.0 ) :
MoverParameters->CabOccupied < 0 ? ( pAnim->iNumber >> 4 == end::rear ? 1.0 : 0.0 ) :
0.0 ) };
if( pAnim->iNumber & 1 )
pAnim->smAnimated->SetRotate(
float3( 0, 1, 0 ),
std::lerp( 0.0, MoverParameters->MirrorMaxShift, dMirrorMoveR * isactive ) );
else
pAnim->smAnimated->SetRotate(
float3( 0, 1, 0 ),
std::lerp( 0.0, MoverParameters->MirrorMaxShift, dMirrorMoveL * isactive ) );
}
// wipers
void TDynamicObject::UpdateWiper(TAnim* pAnim)
{
if (!pAnim || !pAnim->smElement)
return;
int i = pAnim->iNumber;
// odwaramy animacje dla parzystych indexow
const double rotateAngle = (i + 1) % 2 == 0 ? -MoverParameters->WiperAngle : MoverParameters->WiperAngle;
if (pAnim->smElement[0]) // ramie 1
pAnim->smElement[0]->SetRotate(float3(0, 1, 0), smoothInterpolate(0.0, rotateAngle, dWiperPos[i]));
if (pAnim->smElement[1]) // ramie 2
pAnim->smElement[1]->SetRotate(float3(0, 1, 0), smoothInterpolate(0.0, rotateAngle, dWiperPos[i]));
if (pAnim->smElement[2]) // pioro
pAnim->smElement[2]->SetRotate(float3(0, 1, 0), smoothInterpolate(0.0, -rotateAngle, dWiperPos[i]));
}
/*
void TDynamicObject::UpdateLeverDouble(TAnim *pAnim)
{ // animacja gałki zależna od double
pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fSpeed * *pAnim->fDoubleBase);
};
void TDynamicObject::UpdateLeverFloat(TAnim *pAnim)
{ // animacja gałki zależna od float
pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fSpeed * *pAnim->fFloatBase);
};
void TDynamicObject::UpdateLeverInt(TAnim *pAnim)
{ // animacja gałki zależna od int
pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fSpeed * *pAnim->iIntBase);
};
void TDynamicObject::UpdateLeverEnum(TAnim *pAnim)
{ // ustawienie kąta na
// wartość wskazaną przez
// int z tablicy fParam
// pAnim->fParam[0]; - dodać lepkość
pAnim->smAnimated->SetRotate(float3(1, 0, 0), pAnim->fParam[*pAnim->iIntBase]);
};
*/
// sets light levels for registered interior sections
void
TDynamicObject::toggle_lights() {
if( true == SectionLightsActive ) {
// switch all lights off...
for( auto &section : Sections ) {
auto const sectionname { section.compartment->pName };
if( sectionname.rfind( "cab", 0 ) != 0 ) {
section.light_level = 0.0;
}
}
SectionLightsActive = false;
}
else {
// set lights with probability depending on the compartment type. TODO: expose this in .mmd file
for( auto &section : Sections ) {
auto const sectionname { section.compartment->pName };
if( sectionname.find("corridor") == 0
|| sectionname.find("korytarz") == 0 ) {
// corridors are lit 100% of time
section.light_level = 0.75f;
}
else if(
sectionname.find("compartment") == 0
|| sectionname.find("przedzial") == 0 ) {
// compartments are lit with 75% probability
section.light_level = Random() < 0.75 ? 0.75f : 0.10f;
}
}
SectionLightsActive = true;
}
}
void
TDynamicObject::set_cab_lights( int const Cab, float const Level ) {
for( auto &section : Sections ) {
// cab compartments are placed at the beginning of the list, so we can bail out as soon as we find different compartment type
auto const sectionname { section.compartment->pName };
if( sectionname.size() < 4 ) { return; }
if( sectionname.find( "cab" ) != 0 ) { return; }
if( sectionname[ 3 ] != Cab + '0' ) { continue; } // match the cab with correct index
section.light_level = Level;
}
}
// ABu 29.01.05 przeklejone z render i renderalpha: *********************
void TDynamicObject::ABuLittleUpdate(double ObjSqrDist)
{ // ABu290105: pozbierane i uporzadkowane powtarzajace
// sie rzeczy z Render i RenderAlpha
// dodatkowy warunek, if (ObjSqrDist<...) zeby niepotrzebnie nie zmianiec w
// obiektach,
// ktorych i tak nie widac
// NBMX wrzesien, MC listopad: zuniwersalnione
btnOn = false; // czy przywrócić stan domyślny po renderowaniu
if (mdLoad) // tymczasowo ładunek na poziom podłogi
if (vFloor.z > 0.0)
mdLoad->GetSMRoot()->SetTranslate(modelShake + vFloor);
if (ObjSqrDist < 400 * 400 ) // gdy bliżej niż 400m
{
for( auto &animation : pAnimations ) {
// wykonanie kolejnych animacji
if( ObjSqrDist < animation.fMaxDist
&& animation.yUpdate ) {
// jeśli zdefiniowana funkcja aktualizacja animacji (położenia submodeli
animation.yUpdate( &animation );
}
}
if( mdModel != nullptr
&& ObjSqrDist < 50 * 50 ) {
// gdy bliżej niż 50m
// ABu290105: rzucanie pudlem
// te animacje wymagają bananów w modelach!
mdModel->GetSMRoot()->SetTranslate(modelShake);
if (mdKabina)
mdKabina->GetSMRoot()->SetTranslate(modelShake);
if (mdLoad)
mdLoad->GetSMRoot()->SetTranslate(modelShake + vFloor);
if (mdLowPolyInt)
mdLowPolyInt->GetSMRoot()->SetTranslate(modelShake);
// ABu: koniec rzucania
// ABu011104: liczenie obrotow wozkow
ABuBogies();
// Mczapkie-100402: rysowanie lub nie - sprzegow
// ABu-240105: Dodatkowy warunek: if (...).Render, zeby rysowal tylko jeden z polaczonych sprzegow
// display _on if connected with another vehicle and the coupling owner (render flag)
// display _xon if connected with another vehicle and not the coupling owner
// display _xon if not connected, but equipped with coupling adapter
// display _off if not connected, not equipped with coupling adapter or if _xon model is missing
if( TestFlag( MoverParameters->Couplers[ end::front ].CouplingFlag, coupling::coupler ) ) {
if( MoverParameters->Couplers[ end::front ].Render ) {
btCoupler1.TurnOn();
}
else {
btCoupler1.TurnxOnWithOffAsFallback();
}
btnOn = true;
}
else {
if( true == MoverParameters->Couplers[ end::front ].has_adapter() ) {
btCoupler1.TurnxOnWithOffAsFallback();
btnOn = true;
}
}
if( TestFlag( MoverParameters->Couplers[ end::rear ].CouplingFlag, coupling::coupler ) ) {
if( MoverParameters->Couplers[ end::rear ].Render ) {
btCoupler2.TurnOn();
}
else {
btCoupler2.TurnxOnWithOffAsFallback();
}
btnOn = true;
}
else {
if( true == MoverParameters->Couplers[ end::rear ].has_adapter() ) {
btCoupler2.TurnxOnWithOffAsFallback();
btnOn = true;
}
}
//********************************************************************************
// przewody powietrzne j.w., ABu: decyzja czy rysowac tylko na podstawie
// 'render' - juz
// nie
// przewody powietrzne, yB: decyzja na podstawie polaczen w t3d
if (Global.bnewAirCouplers)
{
SetPneumatic(false, false); // wczytywanie z t3d ulozenia wezykow
SetPneumatic(true, false); // i zapisywanie do zmiennej
SetPneumatic(true, true); // ktore z nich nalezy
SetPneumatic(false, true); // wyswietlic w tej klatce
if (TestFlag(MoverParameters->Couplers[end::front].CouplingFlag, ctrain_pneumatic))
{
switch (cp1)
{
case 1:
btCPneumatic1.TurnOn();
break;
case 2:
btCPneumatic1.TurnxOn();
break;
case 3:
btCPneumatic1r.TurnxOn();
break;
case 4:
btCPneumatic1r.TurnOn();
break;
}
btnOn = true;
}
if (TestFlag(MoverParameters->Couplers[end::rear].CouplingFlag, ctrain_pneumatic))
{
switch (cp2)
{
case 1:
btCPneumatic2.TurnOn();
break;
case 2:
btCPneumatic2.TurnxOn();
break;
case 3:
btCPneumatic2r.TurnxOn();
break;
case 4:
btCPneumatic2r.TurnOn();
break;
}
btnOn = true;
}
// przewody zasilajace, j.w. (yB)
if (TestFlag(MoverParameters->Couplers[end::front].CouplingFlag, ctrain_scndpneumatic))
{
switch (sp1)
{
case 1:
btPneumatic1.TurnOn();
break;
case 2:
btPneumatic1.TurnxOn();
break;
case 3:
btPneumatic1r.TurnxOn();
break;
case 4:
btPneumatic1r.TurnOn();
break;
}
btnOn = true;
}
if (TestFlag(MoverParameters->Couplers[end::rear].CouplingFlag, ctrain_scndpneumatic))
{
switch (sp2)
{
case 1:
btPneumatic2.TurnOn();
break;
case 2:
btPneumatic2.TurnxOn();
break;
case 3:
btPneumatic2r.TurnxOn();
break;
case 4:
btPneumatic2r.TurnOn();
break;
}
btnOn = true;
}
}
//*********************************************************************************/
else // po staremu ABu'oewmu
{
// przewody powietrzne j.w., ABu: decyzja czy rysowac tylko na podstawie
// 'render'
if (TestFlag(MoverParameters->Couplers[end::front].CouplingFlag, ctrain_pneumatic))
{
if (MoverParameters->Couplers[end::front].Render)
btCPneumatic1.TurnOn();
else
btCPneumatic1r.TurnOn();
btnOn = true;
}
if (TestFlag(MoverParameters->Couplers[end::rear].CouplingFlag, ctrain_pneumatic))
{
if (MoverParameters->Couplers[end::rear].Render)
btCPneumatic2.TurnOn();
else
btCPneumatic2r.TurnOn();
btnOn = true;
}
// przewody powietrzne j.w., ABu: decyzja czy rysowac tylko na podstawie
// 'render'
// //yB - zasilajace
if (TestFlag(MoverParameters->Couplers[0].CouplingFlag, ctrain_scndpneumatic))
{
if (MoverParameters->Couplers[0].Render)
btPneumatic1.TurnOn();
else
btPneumatic1r.TurnOn();
btnOn = true;
}
if (TestFlag(MoverParameters->Couplers[1].CouplingFlag, ctrain_scndpneumatic))
{
if (MoverParameters->Couplers[1].Render)
btPneumatic2.TurnOn();
else
btPneumatic2r.TurnOn();
btnOn = true;
}
}
//*************************************************************/// koniec
// wezykow
// uginanie zderzakow
for (int i = 0; i < 2; ++i) {
if( MoverParameters->Couplers[ i ].has_adapter() ) {
// HACK: if there's coupler adapter on this side, we presume there's additional distance put between vehicles
// which prevents buffers from clashing against each other (or the other vehicle doesn't have buffers to begin with)
continue;
}
auto const dist { std::clamp( MoverParameters->Couplers[ i ].Dist / 2.0, -MoverParameters->Couplers[ i ].DmaxB, 0.0 ) };
if( dist >= 0.0 ) { continue; }
if( smBuforLewy[ i ] ) {
smBuforLewy[ i ]->SetTranslate( glm::vec3( dist, 0, 0 ) );
}
if( smBuforPrawy[ i ] ) {
smBuforPrawy[ i ]->SetTranslate( glm::vec3( dist, 0, 0 ) );
}
}
} // vehicle within 50m
// Winger 160204 - podnoszenie pantografow
// przewody sterowania ukrotnionego
if (TestFlag(MoverParameters->Couplers[0].CouplingFlag, coupling::control))
{
btCCtrl1.Turn( true );
btnOn = true;
}
// else btCCtrl1.TurnOff();
if (TestFlag(MoverParameters->Couplers[1].CouplingFlag, coupling::control))
{
btCCtrl2.Turn( true );
btnOn = true;
}
// else btCCtrl2.TurnOff();
// McZapkie-181103: mostki przejsciowe
if (TestFlag(MoverParameters->Couplers[0].CouplingFlag, ctrain_passenger))
{
btCPass1.Turn( true );
btnOn = true;
}
// else btCPass1.TurnOff();
if (TestFlag(MoverParameters->Couplers[1].CouplingFlag, ctrain_passenger))
{
btCPass2.Turn( true );
btnOn = true;
}
// else btCPass2.TurnOff();
if (MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable)
{ // sygnaly konca pociagu
if (m_endsignals1.Active()) {
if (TestFlag(MoverParameters->iLights[end::front], light::redmarker_left | light::redmarker_right ) ) {
m_endsignals1.Turn( true );
btnOn = true;
}
}
else {
if (TestFlag(MoverParameters->iLights[end::front], light::redmarker_left) && !HeadlightsAoff)
{
m_endsignal13.Turn( true );
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::front], light::redmarker_right) && !HeadlightsAoff)
{
m_endsignal12.Turn( true );
btnOn = true;
}
}
if (m_endsignals2.Active()) {
if (TestFlag(MoverParameters->iLights[end::rear], light::redmarker_left | light::redmarker_right ) ) {
m_endsignals2.Turn( true );
btnOn = true;
}
}
else {
if (TestFlag(MoverParameters->iLights[end::rear], light::redmarker_left) && !HeadlightsBoff)
{
m_endsignal23.Turn( true );
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::rear], light::redmarker_right) && !HeadlightsBoff)
{
m_endsignal22.Turn( true );
btnOn = true;
}
}
}
// tablice blaszane:
if (TestFlag(MoverParameters->iLights[end::front], light::rearendsignals)) {
m_endtab1.Turn( true );
btnOn = true;
}
// else btEndSignalsTab1.TurnOff();
if (TestFlag(MoverParameters->iLights[end::rear], light::rearendsignals)) {
m_endtab2.Turn( true );
btnOn = true;
}
// destination signs
update_destinations();
// else btEndSignalsTab2.TurnOff();
// McZapkie-181002: krecenie wahaczem (korzysta z kata obrotu silnika)
if (iAnimType[ANIM_LEVERS])
for (int i = 0; i < 4; ++i)
if (smWahacze[i])
smWahacze[i]->SetRotate(float3(1, 0, 0),
fWahaczeAmp * cos(MoverParameters->eAngle));
// cooling shutters
// NOTE: shutters display _on state when they're closed, _off otherwise
if( true == MoverParameters->dizel_heat.water.config.shutters
&& false == MoverParameters->dizel_heat.zaluzje1 ) {
btShutters1.Turn( true );
btnOn = true;
}
if( true == MoverParameters->dizel_heat.water_aux.config.shutters
&& false == MoverParameters->dizel_heat.zaluzje2 ) {
btShutters2.Turn( true );
btnOn = true;
}
// only external models ( external_only_on / external_only_off )
btExteriorOnly.Turn(!bDisplayCab); // display only when cab is not rendered
if( false == bDisplayCab // edge case, lowpoly may act as a stand-in for the hi-fi cab, so make sure not to show the driver when inside
&& Mechanik != nullptr
&& Mechanik->action() != TAction::actSleep ) {
// rysowanie figurki mechanika
btMechanik1.Turn(MoverParameters->CabOccupied > 0);
btMechanik2.Turn( MoverParameters->CabOccupied < 0 );
if( MoverParameters->CabOccupied != 0 ) {
btnOn = true;
}
}
} // vehicle within 400m
if( MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable )
{ // sygnały czoła pociagu //Ra: wyświetlamy bez
// ograniczeń odległości, by były widoczne z
// daleka
if (TestFlag(MoverParameters->iLights[end::front], light::headlight_left)
&& !HeadlightsAoff && !HighBeamLights)
{
if( DimHeadlights ) {
m_headlamp13.TurnxOnWithOnAsFallback();
}
else {
m_headlamp13.TurnOn();
}
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::front], light::headlight_upper)
&& !HeadlightsAoff)
{
if( DimHeadlights ) {
m_headlamp11.TurnxOnWithOnAsFallback();
}
else {
m_headlamp11.TurnOn();
}
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::front], light::headlight_right)
&& !HeadlightsAoff && !HighBeamLights)
{
if( DimHeadlights ) {
m_headlamp12.TurnxOnWithOnAsFallback();
}
else {
m_headlamp12.TurnOn();
}
btnOn = true;
}
// else btHeadSignals13.TurnOff();
if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_left)
&& !HeadlightsBoff && !HighBeamLights)
{
if( DimHeadlights ) {
m_headlamp23.TurnxOnWithOnAsFallback();
}
else {
m_headlamp23.TurnOn();
}
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_upper)
&& !HeadlightsBoff)
{
if( DimHeadlights ) {
m_headlamp21.TurnxOnWithOnAsFallback();
}
else {
m_headlamp21.TurnOn();
}
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_right)
&& !HeadlightsBoff && !HighBeamLights)
{
if( DimHeadlights ) {
m_headlamp22.TurnxOnWithOnAsFallback();
}
else {
m_headlamp22.TurnOn();
}
btnOn = true;
}
// auxiliary lights
if (TestFlag(MoverParameters->iLights[end::front], light::auxiliary_left) && !HeadlightsAoff)
{
if( DimHeadlights ) {
m_headsignal13.TurnxOnWithOnAsFallback();
}
else {
m_headsignal13.TurnOn();
}
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::front], light::auxiliary_right) && !HeadlightsAoff)
{
if( DimHeadlights ) {
m_headsignal12.TurnxOnWithOnAsFallback();
}
else {
m_headsignal12.TurnOn();
}
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::rear], light::auxiliary_left) && !HeadlightsBoff)
{
if( DimHeadlights ) {
m_headsignal23.TurnxOnWithOnAsFallback();
}
else {
m_headsignal23.TurnOn();
}
btnOn = true;
}
if (TestFlag(MoverParameters->iLights[end::rear], light::auxiliary_right) && !HeadlightsBoff)
{
if( DimHeadlights ) {
m_headsignal22.TurnxOnWithOnAsFallback();
}
else {
m_headsignal22.TurnOn();
}
btnOn = true;
}
// logika dlugich
if (TestFlag(MoverParameters->iLights[end::front], light::headlight_left)
&& !HeadlightsAoff && HighBeamLights)
m_highbeam13.TurnxOnWithOnAsFallback();
else
m_highbeam13.TurnOff();
if (TestFlag(MoverParameters->iLights[end::front], light::headlight_right)
&& !HeadlightsAoff && HighBeamLights)
m_highbeam12.TurnxOnWithOnAsFallback();
else
m_highbeam12.TurnOff();
// i to samo od dupy strony
if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_left)
&& !HeadlightsBoff && HighBeamLights)
m_highbeam23.TurnxOnWithOnAsFallback();
else
m_highbeam23.TurnOff();
if (TestFlag(MoverParameters->iLights[end::rear], light::headlight_right)
&& !HeadlightsBoff && HighBeamLights)
m_highbeam22.TurnxOnWithOnAsFallback();
else
m_highbeam22.TurnOff();
}
// interior light levels
auto sectionlightcolor { glm::vec4( 1.f ) };
bool cabsection{ true };
for( auto const &section : Sections ) {
if( cabsection ) {
// check whether we're still processing cab sections
auto const &sectionname { section.compartment->pName };
cabsection &= sectionname.size() >= 4 && sectionname.starts_with("cab");
}
// TODO: add cablight devices
auto const sectionlightlevel { section.light_level * ( cabsection ? 1.0f : MoverParameters->CompartmentLights.intensity ) };
sectionlightcolor = glm::vec4(
sectionlightlevel == 0.f || Global.fLuminance > section.compartment->fLight ?
glm::vec3(240.f / 255.f) : // TBD: save and restore initial submodel diffuse instead of enforcing one?
InteriorLight, // TODO: per-compartment (type) light color
sectionlightlevel );
section.compartment->SetLightLevel( sectionlightcolor, true );
if( section.load != nullptr ) {
section.load->SetLightLevel( sectionlightcolor, true );
}
}
// load chunks visibility
for( auto const &section : Sections ) {
// section isn't guaranteed to have load model, so check that first
if( section.load == nullptr ) { continue; }
section.load->iVisible = section.load_chunks_visible > 0;
// if the section root isn't visible we can skip meddling with its children
if( false == section.load->iVisible ) { continue; }
// if the section root is visible set the state of section chunks
auto *sectionchunk { section.load->ChildGet() };
auto visiblechunkcount { section.load_chunks_visible };
while( sectionchunk != nullptr ) {
sectionchunk->iVisible = visiblechunkcount > 0;
--visiblechunkcount;
sectionchunk = sectionchunk->NextGet();
}
}
// driver cabs visibility
for( int cabidx = 0; cabidx < LowPolyIntCabs.size(); ++cabidx ) {
if( LowPolyIntCabs[ cabidx ] == nullptr ) { continue; }
LowPolyIntCabs[ cabidx ]->iVisible = mdKabina == nullptr ? true : // there's no hi-fi cab
bDisplayCab == false ? true : // we're in external view
simulation::Train == nullptr ? true : // not a player-driven vehicle, implies external view
simulation::Train->Dynamic() != this ? true : // not a player-driven vehicle, implies external view
JointCabs ? false : // internal view, all cabs share the model so hide them 'all'
simulation::Train->iCabn != cabidx; // internal view, hide occupied cab and show others
}
}
// ABu 29.01.05 koniec przeklejenia *************************************
TDynamicObject * TDynamicObject::ABuFindNearestObject(glm::vec3 pos, TTrack *Track, TDynamicObject *MyPointer, int &CouplNr)
{
// zwraca wskaznik do obiektu znajdujacego sie na torze (Track), którego sprzęg jest najblizszy punktowi
// służy np. do łączenia i rozpinania sprzęgów
// WE: Track - tor, na ktorym odbywa sie poszukiwanie
// MyPointer - wskaznik do obiektu szukajacego
// WY: CouplNr - który sprzęg znalezionego obiektu jest bliższy kamerze
// Uwaga! Jesli CouplNr==-2 to szukamy njblizszego obiektu, a nie sprzegu!!!
for( auto dynamic : Track->Dynamics ) {
if( CouplNr == -2 ) {
// wektor [kamera-obiekt] - poszukiwanie obiektu
// length2 is better than length for comparing because it does not require sqrt function
if (glm::length2(glm::dvec3(pos) - dynamic->vPosition) < sq(10.0))
{
// 10 metrów
return dynamic;
}
}
else {
// jeśli (CouplNr) inne niz -2, szukamy sprzęgu
// length2 is better than length for comparing because it does not require sqrt function
if (glm::length2(glm::dvec3(pos) - dynamic->vCoulpler[0]) < sq(5.0)) {
// 5 metrów
CouplNr = 0;
return dynamic;
}
// length2 is better than length for comparing because it does not require sqrt function
if (glm::length2(glm::dvec3(pos) - dynamic->vCoulpler[1]) < sq(5.0)) {
// 5 metrów
CouplNr = 1;
return dynamic;
}
}
}
// empty track or nothing found
return nullptr;
}
TDynamicObject * TDynamicObject::ABuScanNearestObject(glm::vec3 pos, TTrack *Track, double ScanDir, double ScanDist, int &CouplNr)
{ // skanowanie toru w poszukiwaniu obiektu najblizszego punktowi
if (ABuGetDirection() < 0)
ScanDir = -ScanDir;
TDynamicObject *FoundedObj;
FoundedObj =
ABuFindNearestObject(pos, Track, this, CouplNr); // zwraca numer sprzęgu znalezionego pojazdu
if (FoundedObj == nullptr)
{
double ActDist; // Przeskanowana odleglosc.
double CurrDist = 0; // Aktualna dlugosc toru.
if (ScanDir >= 0)
ActDist =
Track->Length() - RaTranslationGet(); //???-przesunięcie wózka względem Point1 toru
else
ActDist = RaTranslationGet(); // przesunięcie wózka względem Point1 toru
while (ActDist < ScanDist)
{
ActDist += CurrDist;
if (ScanDir > 0) // do przodu
{
if (Track->iNextDirection)
{
Track = Track->CurrentNext();
ScanDir = -ScanDir;
}
else
Track = Track->CurrentNext();
}
else // do tyłu
{
if (Track->iPrevDirection)
Track = Track->CurrentPrev();
else
{
Track = Track->CurrentPrev();
ScanDir = -ScanDir;
}
}
if (Track != nullptr)
{ // jesli jest kolejny odcinek toru
CurrDist = Track->Length();
FoundedObj = ABuFindNearestObject(pos, Track, this, CouplNr);
if (FoundedObj != nullptr)
ActDist = ScanDist;
}
else // Jesli nie ma, to wychodzimy.
ActDist = ScanDist;
}
} // Koniec szukania najblizszego toru z jakims obiektem.
return FoundedObj;
}
// ABu 01.11.04 poczatek wyliczania przechylow pudla **********************
void TDynamicObject::ABuModelRoll()
{ // ustawienie przechyłki pojazdu i jego
// zawartości
// Ra: przechyłkę załatwiamy na etapie przesuwania modelu
}
// ABu 06.05.04 poczatek wyliczania obrotow wozkow **********************
void TDynamicObject::ABuBogies()
{ // Obracanie wozkow na zakretach. Na razie
// uwzględnia tylko zakręty,
// bez zadnych gorek i innych przeszkod.
if (smBogie[0] != nullptr && smBogie[1] != nullptr)
{
// modelRot.z=ABuAcos(Axle0.pPosition-Axle1.pPosition); //kąt obrotu pojazdu
// [rad]
// bogieRot[0].z=ABuAcos(Axle0.pPosition-Axle3.pPosition);
bogieRot[0].z = Axle0.vAngles.z;
bogieRot[0] = glm::degrees(modelRot - bogieRot[0]); // mnożenie wektora przez stałą
smBogie[0]->SetRotateXYZ(bogieRot[0]);
// bogieRot[1].z=ABuAcos(Axle2.pPosition-Axle1.pPosition);
bogieRot[1].z = Axle1.vAngles.z;
bogieRot[1] = glm::degrees(modelRot - bogieRot[1]);
smBogie[1]->SetRotateXYZ(bogieRot[1]);
}
};
// ABu 06.05.04 koniec wyliczania obrotow wozkow ************************
// ABu 16.03.03 sledzenie toru przed obiektem: **************************
void TDynamicObject::ABuCheckMyTrack()
{ // Funkcja przypisujaca obiekt
// prawidlowej tablicy Dynamics,
// bo gdzies jest jakis blad i wszystkie obiekty z danego
// pociagu na poczatku stawiane sa na jednym torze i wpisywane
// do jednej tablicy. Wykonuje sie tylko raz - po to 'ABuChecked'
TTrack *OldTrack = MyTrack;
TTrack *NewTrack = Axle0.GetTrack();
if( NewTrack != OldTrack ) {
if( OldTrack ) {
OldTrack->RemoveDynamicObject( this );
}
NewTrack->AddDynamicObject(this);
}
iAxleFirst = 0; // pojazd powiązany z przednią osią - Axle0
}
// Ra: w poniższej funkcji jest problem ze sprzęgami
TDynamicObject *
TDynamicObject::ABuFindObject( int &Foundcoupler, double &Distance, TTrack const *Track, int const Direction, int const Mycoupler ) const
{ // Zwraca wskaźnik najbliższego obiektu znajdującego się
// na torze w określonym kierunku, ale tylko wtedy, kiedy
// obiekty mogą się zderzyć, tzn. nie mijają się.
// WE:
// Track - tor, na ktorym odbywa sie poszukiwanie,
// Direction - kierunek szukania na torze (+1:w stronę Point2, -1:w stronę Point1)
// Mycoupler - nr sprzegu obiektu szukajacego;
// WY:
// wskaznik do znalezionego obiektu.
// Foundcoupler - nr sprzegu znalezionego obiektu
// Distance - distance to found object
if( true == Track->Dynamics.empty() ) {
// sens szukania na tym torze jest tylko, gdy są na nim pojazdy
Distance += Track->Length(); // doliczenie długości odcinka do przeskanowanej odległości
return nullptr; // nie ma pojazdów na torze, to jest NULL
}
double distance = Track->Length(); // najmniejsza znaleziona odleglość (zaczynamy od długości toru)
double myposition; // pozycja szukającego na torze
TDynamicObject *foundobject = nullptr;
if( MyTrack == Track ) {
// gdy szukanie na tym samym torze
myposition = RaTranslationGet(); // położenie wózka względem Point1 toru
}
else {
// gdy szukanie na innym torze
if( Direction > 0 ) {
// szukanie w kierunku Point2 (od zera) - jesteśmy w Point1
myposition = 0;
}
else {
// szukanie w kierunku Point1 (do zera) - jesteśmy w Point2
myposition = distance;
}
}
double objectposition; // robocza odległość od kolejnych pojazdów na danym odcinku
for( auto dynamic : Track->Dynamics ) {
if( dynamic == this ) { continue; } // szukający się nie liczy
/*
if( ( ( dynamic == PrevConnected ) && ( MoverParameters->Couplers[ TMoverParameters::side::front ].CouplingFlag != coupling::faux ) )
|| ( ( dynamic == NextConnected ) && ( MoverParameters->Couplers[ TMoverParameters::side::rear ].CouplingFlag != coupling::faux ) ) ){
// stop-gap check to prevent 'detection' of attached vehicles
// which seems to be a side-effect of inaccurate location calculation in 'simple' mode?
// TODO: investigate actual cause
continue;
}
*/
if( Direction > 0 ) {
// jeśli szukanie w kierunku Point2
objectposition = dynamic->RaTranslationGet() - myposition; // odległogłość tamtego od szukającego
if( objectposition > 0
&& objectposition < distance ) {
// gdy jest po właściwej stronie i bliżej niż jakiś wcześniejszy
Foundcoupler = dynamic->RaDirectionGet() > 0 ? 1 : 0; // to, bo (ScanDir>=0)
}
else { continue; }
}
else {
objectposition = myposition - dynamic->RaTranslationGet(); //???-przesunięcie wózka względem Point1 toru
if( objectposition > 0
&& objectposition < distance ) {
Foundcoupler = dynamic->RaDirectionGet() > 0 ? 0 : 1; // odwrotnie, bo (ScanDir<0)
}
else { continue; }
}
if( Track->iCategoryFlag & 254 ) {
// trajektoria innego typu niż tor kolejowy
// dla torów nie ma sensu tego sprawdzać, rzadko co jedzie po jednej szynie i się mija
// Ra: mijanie samochodów wcale nie jest proste
// Przesuniecie wzgledne pojazdow. Wyznaczane, zeby sprawdzic,
// czy pojazdy faktycznie sie zderzaja (moga byc przesuniete
// w/m siebie tak, ze nie zachodza na siebie i wtedy sie mijaja).
double relativeoffset; // wzajemna odległość poprzeczna
if( Foundcoupler != Mycoupler ) {
// facing the same direction
relativeoffset = std::abs( MoverParameters->OffsetTrackH - dynamic->MoverParameters->OffsetTrackH );
}
else {
relativeoffset = std::abs( MoverParameters->OffsetTrackH + dynamic->MoverParameters->OffsetTrackH );
}
if( relativeoffset + relativeoffset > MoverParameters->Dim.W + dynamic->MoverParameters->Dim.W ) {
// odległość większa od połowy sumy szerokości - kolizji nie będzie
continue;
}
// jeśli zahaczenie jest niewielkie, a jest miejsce na poboczu, to zjechać na pobocze
}
foundobject = dynamic; // potencjalna kolizja
distance = objectposition; // odleglość pomiędzy aktywnymi osiami pojazdów
}
Distance += distance; // doliczenie odległości przeszkody albo długości odcinka do przeskanowanej odległości
return foundobject;
}
int TDynamicObject::DettachStatus(int dir)
{ // sprawdzenie odległości sprzęgów
// rzeczywistych od strony (dir):
// 0=przód,1=tył
// Ra: dziwne, że ta funkcja nie jest używana
if( MoverParameters->Couplers[ dir ].CouplingFlag == coupling::faux ) {
return 0; // jeśli nic nie podłączone, to jest OK
}
return MoverParameters->DettachStatus(dir); // czy jest w odpowiedniej odległości?
}
int TDynamicObject::Dettach(int dir)
{ // rozłączenie sprzęgów rzeczywistych od
// strony (dir): 0=przód,1=tył
// zwraca maskę bitową aktualnych sprzegów (0 jeśli rozłączony)
if (ctOwner)
{ // jeśli pojazd ma przypisany obiekt nadzorujący skład, to póki
// są wskaźniki
TDynamicObject *d = this;
while (d)
{
d->ctOwner = nullptr; // usuwanie właściciela
d = d->Prev();
}
d = Next();
while (d)
{
d->ctOwner = nullptr; // usuwanie właściciela
d = d->Next(); // i w drugą stronę
}
}
if( MoverParameters->Couplers[ dir ].CouplingFlag ) {
// odczepianie, o ile coś podłączone
MoverParameters->Dettach( dir );
/*
if( true == MoverParameters->Dettach( dir ) ) {
auto *othervehicle { dir ? NextConnected : PrevConnected };
auto const othercoupler { dir ? NextConnectedNo() : PrevConnectedNo() };
( othercoupler ? othervehicle->NextConnected : othervehicle->PrevConnected ) = nullptr;
( dir ? NextConnected : PrevConnected ) = nullptr;
};
*/
}
// sprzęg po rozłączaniu (czego się nie da odpiąć
return MoverParameters->Couplers[dir].CouplingFlag;
}
void
TDynamicObject::couple( int const Side ) {
auto const &neighbour { MoverParameters->Neighbours[ Side ] };
if( neighbour.vehicle == nullptr ) { return; }
auto const &coupler { MoverParameters->Couplers[ Side ] };
auto *othervehicle { neighbour.vehicle };
auto *othervehicleparams{ othervehicle->MoverParameters };
auto const &othercoupler { othervehicleparams->Couplers[ neighbour.vehicle_end ] };
if( coupler.CouplingFlag == coupling::faux ) {
// najpierw hak
if( ( coupler.AllowedFlag
& othercoupler.AllowedFlag
& coupling::coupler ) == coupling::coupler ) {
if( MoverParameters->Attach(
Side, neighbour.vehicle_end,
othervehicleparams,
coupling::coupler ) ) {
// one coupling type per key press
return;
}
else {
WriteLog( "Mechanical coupling failed." );
}
}
}
if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::brakehose ) ) {
// pneumatyka
if( ( coupler.AllowedFlag
& othercoupler.AllowedFlag
& coupling::brakehose ) == coupling::brakehose ) {
if( MoverParameters->Attach(
Side, neighbour.vehicle_end,
othervehicleparams,
coupler.CouplingFlag | coupling::brakehose ) ) {
SetPneumatic( Side != 0, true );
othervehicle->SetPneumatic( Side != 0, true );
// one coupling type per key press
return;
}
}
}
if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::mainhose ) ) {
// zasilajacy
if( ( coupler.AllowedFlag
& othercoupler.AllowedFlag
& coupling::mainhose ) == coupling::mainhose ) {
if( MoverParameters->Attach(
Side, neighbour.vehicle_end,
othervehicleparams,
coupler.CouplingFlag | coupling::mainhose ) ) {
SetPneumatic( Side != 0, false );
othervehicle->SetPneumatic( Side != 0, false );
// one coupling type per key press
return;
}
}
}
if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::control ) ) {
// ukrotnionko
if( (coupler.AllowedFlag & othercoupler.AllowedFlag & coupling::control) == coupling::control
&& coupler.control_type == othercoupler.control_type ) {
if( MoverParameters->Attach(
Side, neighbour.vehicle_end,
othervehicleparams,
coupler.CouplingFlag | coupling::control ) ) {
// one coupling type per key press
return;
}
}
}
if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::gangway ) ) {
// mostek
if( ( coupler.AllowedFlag
& othercoupler.AllowedFlag
& coupling::gangway ) == coupling::gangway ) {
if( MoverParameters->Attach(
Side, neighbour.vehicle_end,
othervehicleparams,
coupler.CouplingFlag | coupling::gangway ) ) {
// one coupling type per key press
return;
}
}
}
if( false == TestFlag( MoverParameters->Couplers[ Side ].CouplingFlag, coupling::heating ) ) {
// heating
if( ( coupler.AllowedFlag
& othercoupler.AllowedFlag
& coupling::heating ) == coupling::heating ) {
if( MoverParameters->Attach(
Side, neighbour.vehicle_end,
othervehicleparams,
coupler.CouplingFlag | coupling::heating ) ) {
// one coupling type per key press
return;
}
}
}
}
int
TDynamicObject::uncouple( int const Side ) {
if( DettachStatus(Side) >= 0
|| true == TestFlag(MoverParameters->Couplers[Side].CouplingFlag, coupling::permanent) ) {
// can't uncouple, return existing coupling state
return MoverParameters->Couplers[ Side ].CouplingFlag;
}
// jeżeli sprzęg niezablokowany, jest co odczepić i się da
auto const couplingflag { Dettach( Side ) };
return couplingflag;
}
bool
TDynamicObject::attach_coupler_adapter( int const Side, bool const Enforce ) {
auto &coupler { MoverParameters->Couplers[ Side ] };
// sanity check(s)
if( coupler.type() == TCouplerType::Automatic ) { return false; }
auto const *neighbour { MoverParameters->Neighbours[ Side ].vehicle };
if( neighbour == nullptr
|| MoverParameters->Neighbours[Side].distance > 25.0 ) {
// can only acquire the adapter from a nearby enough vehicle
return false;
}
// TBD: empty struct instead of fallback defaults, to allow vehicles without adapter?
auto adapterdata {
coupleradapter_data {
{ 0.085f, 0.95f },
"tabor/polsprzeg" } };
if( false == neighbour->m_coupleradapter.model.empty() ) {
// explicit coupler adapter definition overrides default parameters
adapterdata = neighbour->m_coupleradapter;
}
if( MoverParameters->Neighbours[Side].distance - adapterdata.position.x < 0.5
&& false == Enforce ) {
// arbitrary amount of free room required to install the adapter
// NOTE: this also covers cases with established physical connection
return false;
}
coupler.adapter_type = TCouplerType::Automatic;
coupler.adapter_length = adapterdata.position.x;
coupler.adapter_height = adapterdata.position.y;
// audio flag, visuals update
coupler.sounds |= sound::attachadapter;
m_coupleradapters[ Side ] = TModelsManager::GetModel( adapterdata.model );
return true;
}
bool
TDynamicObject::remove_coupler_adapter( int const Side ) {
auto &coupler{ MoverParameters->Couplers[ Side ] };
if( coupler.adapter_type == TCouplerType::NoCoupler ) { return false; }
// TODO: sanity check(s)
if( coupler.Connected != nullptr ) {
// TBD: disallow instead adapter removal if it's coupled with another vehicle?
uncouple( Side );
}
coupler.adapter_type = TCouplerType::NoCoupler;
coupler.adapter_length = 0.0;
coupler.adapter_height = 0.0;
// audio flag, visuals update
coupler.sounds |= sound::removeadapter;
m_coupleradapters[ Side ] = nullptr;
return true;
}
TDynamicObject::TDynamicObject() {
modelShake = glm::dvec3(0, 0, 0);
// fTrackBlock = 10000.0; // brak przeszkody na drodze
btnOn = false;
vUp = vWorldUp;
vFront = vWorldFront;
vLeft = vWorldLeft;
iNumAxles = 0;
MoverParameters = nullptr;
Mechanik = nullptr;
MechInside = false;
// McZapkie-270202
Controller = AIdriver;
bDisplayCab = false; // 030303
// NextConnected = PrevConnected = NULL;
// NextConnectedNo = PrevConnectedNo = 2; // ABu: Numery sprzegow. 2=nie podłączony
bEnabled = true;
MyTrack = nullptr;
// McZapkie-260202
dWheelAngle[0] = 0.0;
dWheelAngle[1] = 0.0;
dWheelAngle[2] = 0.0;
// Winger 160204 - pantografy
// PantVolume = 3.5;
NoVoltTime = 0;
mdModel = nullptr;
mdKabina = nullptr;
// smWiazary[0]=smWiazary[1]=NULL;
smWahacze[0] = smWahacze[1] = smWahacze[2] = smWahacze[3] = nullptr;
fWahaczeAmp = 0;
smBrakeMode = nullptr;
smLoadMode = nullptr;
mdLoad = nullptr;
mdLowPolyInt = nullptr;
//smMechanik0 = smMechanik1 = NULL;
smBuforLewy[0] = smBuforLewy[1] = nullptr;
smBuforPrawy[0] = smBuforPrawy[1] = nullptr;
smBogie[0] = smBogie[1] = nullptr;
bogieRot[0] = bogieRot[1] = glm::dvec3(0, 0, 0);
modelRot = glm::dvec3(0, 0, 0);
cp1 = cp2 = sp1 = sp2 = 0;
iDirection = 1; // stoi w kierunku tradycyjnym (0, gdy jest odwrócony)
iAxleFirst = 0; // numer pierwszej osi w kierunku ruchu (przełączenie
// następuje, gdy osie sa na
// tym samym torze)
RaLightsSet(0, 0); // początkowe zerowanie stanu świateł
// Ra: domyślne ilości animacji dla zgodności wstecz (gdy brak ilości podanych
// w MMD)
// ustawienie liczby modeli animowanych podczas konstruowania obiektu a nie na 0
// prowadzi prosto do wysypów jeśli źle zdefiniowane mmd
iAnimations = 0; // na razie nie ma żadnego
pAnimated = nullptr;
fShade = 0.0; // standardowe oświetlenie na starcie
iHornWarning = 1; // numer syreny do użycia po otrzymaniu sygnału do jazdy
asDestination = "none"; // stojący nigdzie nie jedzie
pValveGear = nullptr; // Ra: tymczasowo
iCabs = 0; // maski bitowe modeli kabin
smBrakeSet = nullptr; // nastawa hamulca (wajcha)
smLoadSet = nullptr; // nastawa ładunku (wajcha)
smWiper = nullptr; // wycieraczka (poniekąd też wajcha)
ctOwner = nullptr; // na początek niczyj
iOverheadMask = 0; // maska przydzielana przez AI pojazdom posiadającym
// pantograf, aby wymuszały
// jazdę bezprądową
tmpTraction.TractionVoltage = 0; // Ra 2F1H: prowizorka, trzeba przechować
// napięcie, żeby nie wywalało WS pod
// izolatorem
fAdjustment = 0.0; // korekcja odległości pomiędzy wózkami (np. na łukach)
}
TDynamicObject::~TDynamicObject() {
SafeDelete( Mechanik );
SafeDelete( MoverParameters );
SafeDeleteArray( pAnimated ); // lista animowanych submodeli
}
void TDynamicObject::place_on_track(TTrack *Track, double fDist, bool Reversed)
{
for( auto &axle : m_axlesounds ) {
// wyszukiwanie osi (0 jest na końcu, dlatego dodajemy długość?)
axle.distance = (
Reversed ?
-axle.offset :
axle.offset + MoverParameters->Dim.L ) + fDist;
}
double fAxleDistHalf = fAxleDist * 0.5;
// przesuwanie pojazdu tak, aby jego początek był we wskazanym miejcu
fDist -= 0.5 * MoverParameters->Dim.L; // dodajemy pół długości pojazdu, bo ustawiamy jego środek (zliczanie na minus)
switch (iNumAxles) {
// Ra: pojazdy wstawiane są na tor początkowy, a potem przesuwane
case 2: // ustawianie osi na torze
Axle0.Init(Track, this, iDirection ? 1 : -1);
Axle0.Reset();
Axle0.Move((iDirection ? fDist : -fDist) + fAxleDistHalf, false);
Axle1.Init(Track, this, iDirection ? 1 : -1);
Axle1.Reset();
Axle1.Move((iDirection ? fDist : -fDist) - fAxleDistHalf, false); // false, żeby nie generować eventów
break;
}
// potrzebne do wyliczenia aktualnej pozycji; nie może być zero, bo nie przeliczy pozycji
// teraz jeszcze trzeba przypisać pojazdy do nowego toru, bo przesuwanie początkowe osi nie
// zrobiło tego
Move( 0.0001 );
ABuCheckMyTrack(); // zmiana toru na ten, co oś Axle0 (oś z przodu)
}
double
TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424"
std::string BaseDir, // z którego katalogu wczytany, np. "PKP/EU07"
std::string asReplacableSkin, // nazwa wymiennej tekstury
std::string Type_Name, // nazwa CHK/MMD, np. "303E"
TTrack *Track, // tor początkowy wstwawienia (początek składu)
double fDist, // dystans względem punktu 1
std::string DriverType, // typ obsady
double fVel, // prędkość początkowa
std::string TrainName, // nazwa składu, np. "PE2307" albo Vmax, jeśli pliku nie ma a są cyfry
float Load, // ilość ładunku
std::string LoadType, // nazwa ładunku
bool Reversed, // true, jeśli ma stać odwrotnie w składzie
std::string MoreParams // dodatkowe parametry wczytywane w postaci tekstowej
)
{ // Ustawienie początkowe pojazdu
iDirection = Reversed ? 0 : 1; // Ra: 0, jeśli ma być wstawiony jako obrócony tyłem
asBaseDir = paths::dynamic + BaseDir + "/"; // McZapkie-310302
asName = Name;
std::string asAnimName; // zmienna robocza do wyszukiwania osi i wózków
// Ra: zmieniamy znaczenie obsady na jednoliterowe, żeby dosadzić kierownika
if (DriverType == "headdriver")
DriverType = "1"; // sterujący kabiną +1
else if (DriverType == "reardriver")
DriverType = "2"; // sterujący kabiną -1
else if (DriverType == "passenger")
DriverType = ""; // legacy type, no longer needed
else if (DriverType == "nobody")
DriverType = ""; // nikt nie siedzi
int Cab = 0; // numer kabiny z obsadą (nie można zająć obu)
if (DriverType == "1") // od przodu składu
Cab = 1; // iDirection?1:-1; //iDirection=1 gdy normalnie, =0 odwrotnie
else if (DriverType == "2") // od tyłu składu
Cab = -1; // iDirection?-1:1;
/*
// NOTE: leave passenger in the middle section, this is most likely to be 'passenger' section in MU trains
else if (DriverType == "p")
{
if (Random(6) < 3)
Cab = 1;
else
Cab = -1; // losowy przydział kabiny
}
*/
// utworzenie parametrów fizyki
MoverParameters = new TMoverParameters(iDirection ? fVel : -fVel, Type_Name, asName, Cab);
// McZapkie: TypeName musi byc nazwą CHK/MMD pojazdu
if (!MoverParameters->LoadFIZ(asBaseDir))
{ // jak wczytanie CHK się nie uda, to błąd
if (ConversionError == 666)
ErrorLog( "Bad vehicle: failed to locate definition file \"" + BaseDir + "/" + Type_Name + ".fiz" + "\"" );
else {
ErrorLog( "Bad vehicle: failed to load definition from file \"" + BaseDir + "/" + Type_Name + ".fiz\" (error " + std::to_string( ConversionError ) + ")" );
}
return 0.0; // zerowa długość to brak pojazdu
}
// load the cargo now that we know whether the vehicle will allow it
MoverParameters->AssignLoad( LoadType, Load );
MoverParameters->ComputeMass();
bool driveractive = fVel != 0.0; // jeśli prędkość niezerowa, to aktywujemy ruch
if (!MoverParameters->CheckLocomotiveParameters(
driveractive,
(fVel > 0 ? 1 : -1) * Cab *
(iDirection ? 1 : -1))) // jak jedzie lub obsadzony to gotowy do drogi
{
Error("Parameters mismatch: dynamic object " + asName + " from \"" + BaseDir + "/" + Type_Name + "\"" );
return 0.0; // zerowa długość to brak pojazdu
}
// controller position
MoverParameters->MainCtrlPos = MoverParameters->MainCtrlNoPowerPos();
// ustawienie pozycji hamulca
MoverParameters->LocalBrakePosA = 0.0;
if (driveractive)
{
if (Cab == 0)
MoverParameters->BrakeCtrlPos =
static_cast<int>( std::floor(MoverParameters->Handle->GetPos(bh_NP)) );
else
MoverParameters->BrakeCtrlPos = static_cast<int>( std::floor(MoverParameters->Handle->GetPos(bh_RP)) );
// engage independent brake if applicable, if the vehicle is to be on standby
// NOTE: with more than one driver in the consist this is likely to go awery, since all but one will be sent to sleep
// TBD, TODO: have the virtual helper release independent brakes during consist check?
if( DriverType != "" ) {
if( MoverParameters->LocalBrake != TLocalBrake::ManualBrake ) {
if( fVel < 1.0 ) {
MoverParameters->IncLocalBrakeLevel( LocalBrakePosNo );
if ( MoverParameters->EIMCtrlEmergency ) {
MoverParameters->DecLocalBrakeLevel(1);
}
}
}
}
}
else
MoverParameters->BrakeCtrlPos =
static_cast<int>( std::floor(MoverParameters->Handle->GetPos(bh_NP)) );
// poprawienie hamulca po ewentualnym przestawieniu przez Pascal
// TODO: check if needed, we're not in Pascal anymore, Toto
MoverParameters->BrakeLevelSet(MoverParameters->BrakeCtrlPos);
// dodatkowe parametry yB
MoreParams += "."; // wykonuje o jedną iterację za mało, więc trzeba mu dodać
// kropkę na koniec
size_t kropka = MoreParams.find("."); // znajdź kropke
std::string ActPar; // na parametry
while (kropka != std::string::npos) // jesli sa kropki jeszcze
{
size_t dlugosc = MoreParams.length();
ActPar = ToUpper(MoreParams.substr(0, kropka)); // pierwszy parametr;
MoreParams = MoreParams.substr(kropka + 1, dlugosc - kropka); // reszta do dalszej obrobki
kropka = MoreParams.find(".");
if (ActPar.substr(0, 1) == "B") // jesli hamulce
{ // sprawdzanie kolejno nastaw
WriteLog("Wpis hamulca: " + ActPar);
if ( contains( ActPar, 'G') )
{
MoverParameters->BrakeDelaySwitch(bdelay_G);
}
if( contains( ActPar, 'P' ) )
{
MoverParameters->BrakeDelaySwitch(bdelay_P);
}
if( contains( ActPar, 'R' ) )
{
MoverParameters->BrakeDelaySwitch(bdelay_R);
}
if( contains( ActPar, 'M' ) )
{
MoverParameters->BrakeDelaySwitch(bdelay_R);
MoverParameters->BrakeDelaySwitch(bdelay_R + bdelay_M);
}
// wylaczanie hamulca
if ( contains( ActPar, "<>") ) // wylaczanie na probe hamowania naglego
{
MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz
}
if ( contains( ActPar, '0' ) ) // wylaczanie na sztywno
{
MoverParameters->Hamulec->ForceEmptiness();
MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz
}
if ( contains( ActPar, 'E' ) ) // oprozniony
{
MoverParameters->Hamulec->ForceEmptiness();
MoverParameters->Pipe->CreatePress(0);
MoverParameters->Pipe2->CreatePress(0);
}
if ( contains( ActPar, 'Q' ) ) // oprozniony
{
MoverParameters->Hamulec->ForceEmptiness();
MoverParameters->Pipe->CreatePress(0.0);
MoverParameters->PipePress = 0.0;
MoverParameters->Pipe2->CreatePress(0.0);
MoverParameters->ScndPipePress = 0.0;
MoverParameters->PantVolume = 0.1;
MoverParameters->PantPress = 0.0;
MoverParameters->CompressedVolume = 0.0;
}
if ( contains( ActPar, '1' ) ) // wylaczanie 10%
{
if (Random(10) < 1) // losowanie 1/10
{
MoverParameters->Hamulec->ForceEmptiness();
MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz
}
}
if ( contains( ActPar, 'X') ) // agonalny wylaczanie 20%, usrednienie przekladni
{
if (Random(100) < 20) // losowanie 20/100
{
MoverParameters->Hamulec->ForceEmptiness();
MoverParameters->Hamulec->SetBrakeStatus( MoverParameters->Hamulec->GetBrakeStatus() | b_dmg ); // wylacz
}
if (MoverParameters->BrakeCylMult[2] * MoverParameters->BrakeCylMult[1] >
0.01) // jesli jest nastawiacz mechaniczny PL
{
float rnd = Random(100);
if (rnd < 20) // losowanie 20/100 usrednienie
{
MoverParameters->BrakeCylMult[2] = MoverParameters->BrakeCylMult[1] =
(MoverParameters->BrakeCylMult[2] + MoverParameters->BrakeCylMult[1]) /
2;
}
else if (rnd < 70) // losowanie 70/100-20/100 oslabienie
{
MoverParameters->BrakeCylMult[1] = MoverParameters->BrakeCylMult[1] * 0.50;
MoverParameters->BrakeCylMult[2] = MoverParameters->BrakeCylMult[2] * 0.75;
}
else if (rnd < 80) // losowanie 80/100-70/100 tylko prozny
{
MoverParameters->BrakeCylMult[2] = MoverParameters->BrakeCylMult[1];
}
else // tylko ladowny
{
MoverParameters->BrakeCylMult[1] = MoverParameters->BrakeCylMult[2];
}
}
}
// nastawianie ladunku
if ( contains( ActPar, 'T' ) ) // prozny
{
MoverParameters->DecBrakeMult();
MoverParameters->DecBrakeMult();
} // dwa razy w dol
if ( contains( ActPar, 'H' ) ) // ladowny I (dla P-Ł dalej prozny)
{
MoverParameters->IncBrakeMult();
MoverParameters->IncBrakeMult();
MoverParameters->DecBrakeMult();
} // dwa razy w gore i obniz
if ( contains( ActPar, 'F' ) ) // ladowny II
{
MoverParameters->IncBrakeMult();
MoverParameters->IncBrakeMult();
} // dwa razy w gore
if ( contains( ActPar, 'N' ) ) // parametr neutralny
{
}
} // koniec hamulce
else if( ActPar.size() >= 3
&& ActPar.front() == 'W' ) {
// wheel
ActPar.erase( 0, 1 );
auto fixedflatsize { 0 };
auto randomflatsize { 0 };
auto flatchance { 100 };
while( false == ActPar.empty() ) {
// TODO: convert this whole copy and paste mess to something more elegant one day
switch( ActPar.front() ) {
case 'F': {
// fixed flat size
auto const indexstart { 1 };
auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) };
fixedflatsize = std::atoi( ActPar.substr( indexstart, indexend ).c_str() );
ActPar.erase( 0, indexend );
break;
}
case 'R': {
// random flat size
auto const indexstart { 1 };
auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) };
randomflatsize = std::atoi( ActPar.substr( indexstart, indexend ).c_str() );
ActPar.erase( 0, indexend );
break;
}
case 'P': {
// random flat probability
auto const indexstart { 1 };
auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) };
flatchance = std::atoi( ActPar.substr( indexstart, indexend ).c_str() );
ActPar.erase( 0, indexend );
break;
}
case 'H': {
// truck hunting
auto const indexstart { 1 };
auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) };
auto const huntingchance { std::atoi( ActPar.substr( indexstart, indexend ).c_str() ) };
MoverParameters->TruckHunting = Random(0, 100) < huntingchance;
ActPar.erase( 0, indexend );
break;
}
default: {
// unrecognized key
ActPar.erase( 0, 1 );
break;
}
}
}
if( Random( 0, 100 ) <= flatchance ) {
MoverParameters->WheelFlat += fixedflatsize + Random( 0, randomflatsize );
}
} // wheel
else if( ActPar.size() >= 2
&& ActPar.front() == 'T' ) {
// temperature
ActPar.erase( 0, 1 );
auto setambient { false };
while( false == ActPar.empty() ) {
switch( ActPar.front() ) {
case 'A': {
// cold start, set all temperatures to ambient level
setambient = true;
ActPar.erase( 0, 1 );
break;
}
default: {
// unrecognized key
ActPar.erase( 0, 1 );
break;
}
}
}
if( true == setambient ) {
// TODO: pull ambient temperature from environment data
MoverParameters->dizel_HeatSet( Global.AirTemperature );
}
} // temperature
else if( ActPar.size() >= 2
&& ActPar.front() == 'L' ) {
// load
ActPar.erase( 0, 1 );
// immediately followed by max load override
// TBD: make it instead an optional sub-parameter?
{
auto const indexstart { 0 };
auto const indexend { ActPar.find_first_not_of( "1234567890", indexstart ) };
MoverParameters->MaxLoad = std::atoi( ActPar.substr( indexstart, indexend ).c_str() );
ActPar.erase( 0, indexend );
}
while( false == ActPar.empty() ) {
switch( ActPar.front() ) {
default: {
// unrecognized key
ActPar.erase( 0, 1 );
break;
}
}
}
} // load
/* else if (ActPar.substr(0, 1) == "") // tu mozna wpisac inny prefiks i inne rzeczy
{
// jakies inne prefiksy
}
*/
} // koniec while kropka
if (MoverParameters->CategoryFlag & 2) // jeśli samochód
{ // ustawianie samochodow na poboczu albo na środku drogi
if( Track->fTrackWidth < 3.5 ) // jeśli droga wąska
MoverParameters->OffsetTrackH = 0.0; // to stawiamy na środku, niezależnie od stanu
// ruchu
else if( driveractive ) {// od 3.5m do 8.0m jedzie po środku pasa, dla
// szerszych w odległości
// 1.5m
auto const lanefreespace = 0.5 * Track->fTrackWidth - MoverParameters->Dim.W;
MoverParameters->OffsetTrackH = lanefreespace > 1.5 ?
-0.5 * MoverParameters->Dim.W - 0.75 : // wide road, keep near centre with safe margin
lanefreespace > 0.1 ?
-0.25 * Track->fTrackWidth : // narrow fit, stay on the middle
-0.5 * MoverParameters->Dim.W - 0.075; // too narrow, just keep some minimal clearance
}
else // jak stoi, to kołem na poboczu i pobieramy szerokość razem z
// poboczem, ale nie z
// chodnikiem
MoverParameters->OffsetTrackH =
-0.5 * (Track->WidthTotal() - MoverParameters->Dim.W) + 0.05;
iHornWarning = 0; // nie będzie trąbienia po podaniu zezwolenia na jazdę
if (fDist < 0.0) //-0.5*MoverParameters->Dim.L) //jeśli jest przesunięcie do tyłu
if (!Track->CurrentPrev()) // a nie ma tam odcinka i trzeba by coś
// wygenerować
fDist = -fDist; // to traktujemy, jakby przesunięcie było w drugą stronę
}
create_controller( DriverType, !TrainName.empty() );
ShakeSpring.Init( 125.0 );
BaseShake = baseshake_config {};
ShakeState = shake_state {};
// McZapkie-250202
/*
iAxles = std::min( MoverParameters->NAxles, MaxAxles ); // ilość osi
*/
// wczytywanie z pliku nazwatypu.mmd, w tym model
erase_extension( asReplacableSkin );
LoadMMediaFile(Type_Name, asReplacableSkin);
// McZapkie-100402: wyszukiwanie submodeli sprzegów
btCoupler1.Init( "coupler1", mdModel ); // false - ma być wyłączony
btCoupler2.Init( "coupler2", mdModel );
// brake hoses
btCPneumatic1.Init("cpneumatic1", mdModel);
btCPneumatic2.Init("cpneumatic2", mdModel);
btCPneumatic1r.Init("cpneumatic1r", mdModel);
btCPneumatic2r.Init("cpneumatic2r", mdModel);
// main hoses
btPneumatic1.Init("pneumatic1", mdModel);
btPneumatic2.Init("pneumatic2", mdModel);
btPneumatic1r.Init("pneumatic1r", mdModel);
btPneumatic2r.Init("pneumatic2r", mdModel);
// control cables
btCCtrl1.Init( "cctrl1", mdModel, false);
btCCtrl2.Init( "cctrl2", mdModel, false);
// gangways
btCPass1.Init( "cpass1", mdModel, false);
btCPass2.Init( "cpass2", mdModel, false);
// sygnaly
m_endsignal12.Init( "endsignal12", mdModel, false );
m_endsignal13.Init( "endsignal13", mdModel, false );
m_endsignal22.Init( "endsignal22", mdModel, false );
m_endsignal23.Init( "endsignal23", mdModel, false );
m_endsignals1.Init( "endsignals1", mdModel, false );
m_endsignals2.Init( "endsignals2", mdModel, false );
m_endtab1.Init( "endtab1", mdModel, false );
m_endtab2.Init( "endtab2", mdModel, false );
m_headlamp11.Init( "headlamp11", mdModel ); // górne
m_headlamp12.Init( "headlamp12", mdModel ); // prawe
m_headlamp13.Init( "headlamp13", mdModel ); // lewe
m_highbeam12.Init("highbeam12", mdModel); // prawe dlugie
m_highbeam13.Init("highbeam13", mdModel); // lewe dlugie
m_headlamp21.Init( "headlamp21", mdModel );
m_headlamp22.Init( "headlamp22", mdModel );
m_headlamp23.Init( "headlamp23", mdModel );
m_highbeam22.Init("highbeam22", mdModel);
m_highbeam23.Init("highbeam23", mdModel);
m_headsignal12.Init( "headsignal12", mdModel );
m_headsignal13.Init( "headsignal13", mdModel );
m_headsignal22.Init( "headsignal22", mdModel );
m_headsignal23.Init( "headsignal23", mdModel );
// informacja, czy ma poszczególne światła
iInventory[ end::front ] |= m_endsignal12.Active() ? light::redmarker_right : 0;
iInventory[ end::front ] |= m_endsignal13.Active() ? light::redmarker_left : 0;
iInventory[ end::rear ] |= m_endsignal22.Active() ? light::redmarker_right : 0;
iInventory[ end::rear ] |= m_endsignal23.Active() ? light::redmarker_left : 0;
iInventory[ end::front ] |= m_endsignals1.Active() ? light::redmarker_left | light::redmarker_right : 0;
iInventory[ end::rear ] |= m_endsignals2.Active() ? light::redmarker_left | light::redmarker_right : 0;
iInventory[ end::front ] |= m_endtab1.Active() ? light::rearendsignals : 0; // tabliczki blaszane
iInventory[ end::rear ] |= m_endtab2.Active() ? light::rearendsignals : 0;
iInventory[ end::front ] |= m_headlamp11.Active() ? light::headlight_upper : 0;
iInventory[ end::front ] |= m_headlamp12.Active() ? light::headlight_right : 0;
iInventory[ end::front ] |= m_headlamp13.Active() ? light::headlight_left : 0;
iInventory[end::front] |= m_highbeam12.Active() ? light::highbeamlight_right : 0;
iInventory[end::front] |= m_highbeam13.Active() ? light::highbeamlight_left : 0;
iInventory[ end::rear ] |= m_headlamp21.Active() ? light::headlight_upper : 0;
iInventory[ end::rear ] |= m_headlamp22.Active() ? light::headlight_right : 0;
iInventory[ end::rear ] |= m_headlamp23.Active() ? light::headlight_left : 0;
iInventory[ end::front ] |= m_headsignal12.Active() ? light::auxiliary_right : 0;
iInventory[ end::front ] |= m_headsignal13.Active() ? light::auxiliary_left : 0;
iInventory[ end::rear ] |= m_headsignal22.Active() ? light::auxiliary_right : 0;
iInventory[ end::rear ] |= m_headsignal23.Active() ? light::auxiliary_left : 0;
iInventory[end::rear] |= m_highbeam22.Active() ? light::highbeamlight_right : 0;
iInventory[end::rear] |= m_highbeam23.Active() ? light::highbeamlight_left : 0;
btExteriorOnly.Init("external_only", mdModel, false);
btMechanik1.Init( "mechanik1", mdLowPolyInt, false);
btMechanik2.Init( "mechanik2", mdLowPolyInt, false);
if( MoverParameters->dizel_heat.water.config.shutters ) {
btShutters1.Init( "shutters1", mdModel, false );
}
if( MoverParameters->dizel_heat.water_aux.config.shutters ) {
btShutters2.Init( "shutters2", mdModel, false );
}
TurnOff(); // resetowanie zmiennych submodeli
if( mdLowPolyInt != nullptr ) {
// check the low poly interior for potential compartments of interest, ie ones which can be individually lit
// TODO: definition of relevant compartments in the .mmd file
TSubModel *submodel { nullptr };
if( ( submodel = mdLowPolyInt->GetFromName( "cab0" ) ) != nullptr ) {
Sections.push_back( { submodel, nullptr, 0, 0.0f } );
LowPolyIntCabs[ 0 ] = submodel;
}
if( ( submodel = mdLowPolyInt->GetFromName( "cab1" ) ) != nullptr ) {
Sections.push_back( { submodel, nullptr, 0, 0.0f } );
LowPolyIntCabs[ 1 ] = submodel;
}
if( ( submodel = mdLowPolyInt->GetFromName( "cab2" ) ) != nullptr ) {
Sections.push_back( { submodel, nullptr, 0, 0.0f } );
LowPolyIntCabs[ 2 ] = submodel;
}
// passenger car compartments
std::vector<std::string> nameprefixes = { "corridor", "korytarz", "compartment", "przedzial" };
for( auto const &nameprefix : nameprefixes ) {
init_sections( mdLowPolyInt, nameprefix, MoverParameters->CompartmentLights.start_type == start_t::manual );
}
}
// destination sign
if( mdModel ) {
init_destination( mdModel );
}
// 'external_load' is an optional special section in the main model, pointing to submodel of external load
if( mdModel ) {
init_sections( mdModel, "external_load", false );
}
update_load_sections();
update_load_visibility();
// wyszukiwanie zderzakow
if( mdModel ) {
// jeśli ma w czym szukać
for( int i = 0; i < 2; i++ ) {
asAnimName = std::string( "buffer_left0" ) + std::to_string( i + 1 );
smBuforLewy[ i ] = mdModel->GetFromName( asAnimName );
if( smBuforLewy[ i ] )
smBuforLewy[ i ]->WillBeAnimated(); // ustawienie flagi animacji
asAnimName = std::string( "buffer_right0" ) + std::to_string( i + 1 );
smBuforPrawy[ i ] = mdModel->GetFromName( asAnimName );
if( smBuforPrawy[ i ] )
smBuforPrawy[ i ]->WillBeAnimated();
}
}
if( Track->fSoundDistance > 0.f ) {
for( auto &axle : m_axlesounds ) {
// wyszukiwanie osi (0 jest na końcu, dlatego dodajemy długość?)
axle.distance =
clamp_circular<double>(
( Reversed ?
-axle.offset :
axle.offset )
- 0.5 * MoverParameters->Dim.L
+ fDist,
Track->fSoundDistance );
}
}
// McZapkie-250202 end.
Track->AddDynamicObject(this); // wstawiamy do toru na pozycję 0, a potem przesuniemy
// McZapkie: zmieniono na ilosc osi brane z chk
// iNumAxles=(MoverParameters->NAxles>3 ? 4 : 2 );
initial_track = MyTrack;
iNumAxles = 2;
// McZapkie-090402: odleglosc miedzy czopami skretu lub osiami
fAxleDist = std::clamp(
std::max( MoverParameters->BDist, MoverParameters->ADist ),
0.2, //żeby się dało wektory policzyć
MoverParameters->Dim.L - 0.2 ); // nie mogą być za daleko bo będzie "walenie w mur"
double fAxleDistHalf = fAxleDist * 0.5;
// przesuwanie pojazdu tak, aby jego początek był we wskazanym miejcu
fDist -= 0.5 * MoverParameters->Dim.L; // dodajemy pół długości pojazdu, bo ustawiamy jego środek (zliczanie na minus)
switch (iNumAxles) {
// Ra: pojazdy wstawiane są na tor początkowy, a potem przesuwane
case 2: // ustawianie osi na torze
Axle0.Init(Track, this, iDirection ? 1 : -1);
Axle0.Move((iDirection ? fDist : -fDist) + fAxleDistHalf, false);
Axle1.Init(Track, this, iDirection ? 1 : -1);
Axle1.Move((iDirection ? fDist : -fDist) - fAxleDistHalf, false); // false, żeby nie generować eventów
break;
case 4:
Axle0.Init(Track, this, iDirection ? 1 : -1);
Axle0.Move((iDirection ? fDist : -fDist) + (fAxleDistHalf + MoverParameters->ADist * 0.5), false);
Axle1.Init(Track, this, iDirection ? 1 : -1);
Axle1.Move((iDirection ? fDist : -fDist) - (fAxleDistHalf + MoverParameters->ADist * 0.5), false);
// Axle2.Init(Track,this,iDirection?1:-1);
// Axle2.Move((iDirection?fDist:-fDist)-(fAxleDistHalf-MoverParameters->ADist*0.5),false);
// Axle3.Init(Track,this,iDirection?1:-1);
// Axle3.Move((iDirection?fDist:-fDist)+(fAxleDistHalf-MoverParameters->ADist*0.5),false);
break;
}
// potrzebne do wyliczenia aktualnej pozycji; nie może być zero, bo nie przeliczy pozycji
// teraz jeszcze trzeba przypisać pojazdy do nowego toru, bo przesuwanie początkowe osi nie
// zrobiło tego
Move( 0.0001 );
ABuCheckMyTrack(); // zmiana toru na ten, co oś Axle0 (oś z przodu)
// Ra: ustawienie pozycji do obliczania sprzęgów
MoverParameters->Loc = {
-vPosition.x,
vPosition.z,
vPosition.y }; // normalnie przesuwa ComputeMovement() w Update()
// ABuWozki 060504
if (mdModel) // jeśli ma w czym szukać
{
smBogie[0] = mdModel->GetFromName("bogie1"); // Ra: bo nazwy są małymi
smBogie[1] = mdModel->GetFromName("bogie2");
if (!smBogie[0])
smBogie[0] = mdModel->GetFromName("boogie01"); // Ra: alternatywna nazwa
if (!smBogie[1])
smBogie[1] = mdModel->GetFromName("boogie02"); // Ra: alternatywna nazwa
if (smBogie[0])
smBogie[0]->WillBeAnimated();
if (smBogie[1])
smBogie[1]->WillBeAnimated();
}
// ABu: zainicjowanie zmiennej, zeby nic sie nie ruszylo w pierwszej klatce,
// potem juz liczona prawidlowa wartosc masy
MoverParameters->ComputeConstans();
// wektor podłogi dla wagonów, przesuwa ładunek
vFloor = glm::dvec3(0, 0, MoverParameters->Floor);
// długość większa od zera oznacza OK; 2mm docisku?
return MoverParameters->Dim.L;
}
int
TDynamicObject::init_sections( TModel3d const *Model, std::string const &Nameprefix, bool const Overrideselfillum ) {
auto sectioncount = 0;
auto sectionindex = 0;
TSubModel *sectionsubmodel { nullptr };
do {
// section names for index < 10 match either prefix0X or prefixX
// section names above 10 match prefixX
auto const sectionindexname { std::to_string( sectionindex ) };
sectionsubmodel = Model->GetFromName( Nameprefix + sectionindexname );
if( sectionsubmodel == nullptr
&& sectionindex < 10 ) {
sectionsubmodel = Model->GetFromName( Nameprefix + "0" + sectionindexname );
}
if( sectionsubmodel != nullptr ) {
// HACK: disable automatic self-illumination threshold, at least until 3d model update
if( Overrideselfillum ) {
sectionsubmodel->SetSelfIllum( 2.0f, true, false );
}
Sections.push_back( {
sectionsubmodel,
nullptr, // pointers to load sections are generated afterwards
0,
0.0f } );
++sectioncount;
}
++sectionindex;
} while( sectionsubmodel != nullptr
|| sectionindex < 2 ); // chain can start from prefix00 or prefix01
return sectioncount;
}
bool
TDynamicObject::init_destination( TModel3d *Model ) {
if( Model->GetSMRoot() == nullptr ) { return false; }
std::tie( DestinationSign.sign, DestinationSign.has_light ) = Model->GetSMRoot()->find_replacable4();
return DestinationSign.sign != nullptr;
}
void
TDynamicObject::create_controller( std::string const Type, bool const Trainset ) {
if( Type == "" ) { return; }
// McZapkie-151102: rozkład jazdy czytany z pliku *.txt z katalogu w którym jest sceneria
if( Type == "1"
|| Type == "2" ) {
// McZapkie-110303: mechanik i rozklad tylko gdy jest obsada
Mechanik = new TController( Controller, this, Aggressive );
if( false == Trainset ) {
// jeśli nie w składzie
// załączenie rozrządu (wirtualne kabiny) itd.
Mechanik->DirectionInitial();
// tryb pociągowy z ustaloną prędkością (względem sprzęgów)
Mechanik->PutCommand(
"Timetable:",
MoverParameters->V * 3.6 * ( iDirection ? -1.0 : 1.0 ),
0,
nullptr );
}
}
else if( Type == "p" ) {
// obserwator w charakterze pasażera
// Ra: to jest niebezpieczne, bo w razie co będzie pomagał hamulcem bezpieczeństwa
Mechanik = new TController( Controller, this, Easyman, false );
}
}
void TDynamicObject::FastMove(double fDistance)
{
MoverParameters->dMoveLen = MoverParameters->dMoveLen + fDistance;
}
void TDynamicObject::Move(double fDistance)
{ // przesuwanie pojazdu po
// trajektorii polega na
// przesuwaniu poszczególnych osi
// Ra: wartość prędkości 2km/h ma ograniczyć aktywację eventów w przypadku
// drgań
if (Axle0.GetTrack() == Axle1.GetTrack()) // przed przesunięciem
{ // powiązanie pojazdu z osią można zmienić tylko wtedy, gdy skrajne osie są
// na tym samym torze
if (MoverParameters->Vel >
2) //|[km/h]| nie ma sensu zmiana osi, jesli pojazd drga na postoju
iAxleFirst = MoverParameters->V >= 0.0 ?
1 :
0; //[m/s] ?1:0 - aktywna druga oś w kierunku jazdy
// aktualnie eventy aktywuje druga oś, żeby AI nie wyłączało sobie semafora
// za szybko
}
if (fDistance > 0.0)
{ // gdy ruch w stronę sprzęgu 0, doliczyć korektę do osi 1
bEnabled &= Axle0.Move(fDistance, iAxleFirst == 0); // oś z przodu pojazdu
bEnabled &= Axle1.Move(fDistance /*-fAdjustment*/, iAxleFirst != 0); // oś z tyłu pojazdu
}
else if (fDistance < 0.0)
{ // gdy ruch w stronę sprzęgu 1, doliczyć korektę do osi 0
bEnabled &= Axle1.Move(fDistance, iAxleFirst != 0); // oś z tyłu pojazdu prusza się pierwsza
bEnabled &= Axle0.Move(fDistance /*-fAdjustment*/, iAxleFirst == 0); // oś z przodu pojazdu
}
else // gf: bez wywolania Move na postoju nie ma event0
{
bEnabled &= Axle1.Move(fDistance, iAxleFirst != 0); // oś z tyłu pojazdu prusza się pierwsza
bEnabled &= Axle0.Move(fDistance, iAxleFirst == 0); // oś z przodu pojazdu
}
if (fDistance != 0.0) // nie liczyć ponownie, jeśli stoi
{ // liczenie pozycji pojazdu tutaj, bo jest używane w wielu miejscach
vPosition = 0.5 * (Axle1.pPosition + Axle0.pPosition); //środek między skrajnymi osiami
vFront = Axle0.pPosition - Axle1.pPosition; // wektor pomiędzy skrajnymi osiami
// Ra 2F1J: to nie jest stabilne (powoduje rzucanie taborem) i wymaga
// dopracowania
fAdjustment = glm::length(vFront) - fAxleDist; // na łuku będzie ujemny
// if (fabs(fAdjustment)>0.02) //jeśli jest zbyt dużo, to rozłożyć na kilka przeliczeń (wygasza drgania?)
//{//parę centymetrów trzeba by już skorygować; te błędy mogą się też
// generować na ostrych łukach
// fAdjustment*=0.5; //w jednym kroku korygowany jest ułamek błędu
//}
// else
// fAdjustment=0.0;
vFront = glm::normalize(vFront); // kierunek ustawienia pojazdu (wektor jednostkowy)
vLeft = glm::normalize(glm::cross(glm::dvec3(vWorldUp), vFront)); // wektor poziomy w lewo,
// normalizacja potrzebna z powodu pochylenia (vFront)
vUp = glm::cross(vFront, vLeft); // wektor w górę, będzie jednostkowy
modelRot.z = atan2(-vFront.x, vFront.z); // kąt obrotu pojazdu [rad]; z ABuBogies()
auto const roll { Roll() }; // suma przechyłek
if (roll != 0.0)
{ // wyznaczanie przechylenia tylko jeśli jest przechyłka
// można by pobrać wektory normalne z toru...
mMatrix = glm::rotate(glm::dmat4(1.0), roll * 0.5, vFront); // ta macierz jest potrzebna głównie do wyświetlania // obrót wzdłuż osi o przechyłkę
vUp = glm::dvec3(mMatrix * glm::dvec4(vUp, 0.0)); // wektor w górę pojazdu (przekręcenie na przechyłce)
// vLeft=mMatrix*DynamicObject->vLeft;
// vUp=CrossProduct(vFront,vLeft); //wektor w górę
// vLeft=Normalize(CrossProduct(vWorldUp,vFront)); //wektor w lewo
vLeft = glm::normalize(glm::cross(vUp, vFront)); // wektor w lewo
// vUp=CrossProduct(vFront,vLeft); //wektor w górę
}
mMatrix = BasisChange(vLeft, vUp, vFront); // to też można by od razu policzyć, ale potrzebne jest do wyświetlania // przesuwanie jest jednak rzadziej niż renderowanie
mMatrix = glm::inverse(mMatrix); // wyliczenie macierzy dla pojazdu (potrzebna tylko do wyświetlania?)
// if (MoverParameters->CategoryFlag&2)
{ // przesunięcia są używane po wyrzuceniu pociągu z toru
vPosition.x += MoverParameters->OffsetTrackH * vLeft.x; // dodanie przesunięcia w bok
vPosition.z += MoverParameters->OffsetTrackH * vLeft.z; // vLeft jest wektorem poprzecznym
// if () na przechyłce będzie dodatkowo zmiana wysokości samochodu
vPosition.y += MoverParameters->OffsetTrackV; // te offsety są liczone przez moverparam
}
// obliczanie pozycji sprzęgów do liczenia zderzeń
auto dir = 0.5 * MoverParameters->Dim.L * vFront; // wektor sprzęgu
vCoulpler[end::front] = vPosition + dir; // współrzędne sprzęgu na początku
vCoulpler[end::rear] = vPosition - dir; // współrzędne sprzęgu na końcu
// bCameraNear=
// if (bCameraNear) //jeśli istotne są szczegóły (blisko kamery)
{ // przeliczenie cienia
TTrack *t0 = Axle0.GetTrack(); // już po przesunięciu
TTrack *t1 = Axle1.GetTrack();
if (t0->eEnvironment == e_flat && t1->eEnvironment == e_flat) // może być e_bridge...
fShade = 0.0; // standardowe oświetlenie
else
{ // jeżeli te tory mają niestandardowy stopień zacienienia
// (e_canyon, e_tunnel)
if (t0->eEnvironment == t1->eEnvironment)
{
switch (t0->eEnvironment)
{ // typ zmiany oświetlenia
case e_canyon:
fShade = 0.65f;
break; // zacienienie w kanionie
case e_tunnel:
fShade = 0.20f;
break; // zacienienie w tunelu
}
}
else // dwa różne
{ // liczymy proporcję
double d = Axle0.GetTranslation(); // aktualne położenie na torze
if (Axle0.GetDirection() < 0)
d = t0->Length() - d; // od drugiej strony liczona długość
d /= fAxleDist; // rozsataw osi procentowe znajdowanie się na torze
float shadefrom = 1.0f, shadeto = 1.0f;
// NOTE, TODO: calculating brightness level is used enough times to warrant encapsulation into a function
switch( t0->eEnvironment ) {
case e_canyon: { shadeto = 0.65f; break; }
case e_tunnel: { shadeto = 0.2f; break; }
default: {break; }
}
switch( t1->eEnvironment ) {
case e_canyon: { shadefrom = 0.65f; break; }
case e_tunnel: { shadefrom = 0.2f; break; }
default: {break; }
}
fShade = std::lerp( shadefrom, shadeto, static_cast<float>( d ) );
/*
switch (t0->eEnvironment)
{ // typ zmiany oświetlenia - zakładam, że
// drugi tor ma e_flat
case e_canyon:
fShade = (d * 0.65) + (1.0 - d);
break; // zacienienie w kanionie
case e_tunnel:
fShade = (d * 0.20) + (1.0 - d);
break; // zacienienie w tunelu
}
switch (t1->eEnvironment)
{ // typ zmiany oświetlenia - zakładam, że
// pierwszy tor ma e_flat
case e_canyon:
fShade = d + (1.0 - d) * 0.65;
break; // zacienienie w kanionie
case e_tunnel:
fShade = d + (1.0 - d) * 0.20;
break; // zacienienie w tunelu
}
*/
}
}
}
}
};
void TDynamicObject::AttachNext(TDynamicObject *Object, int iType)
{ // Ra: doczepia Object na końcu składu (nazwa funkcji może być myląca)
// Ra: używane tylko przy wczytywaniu scenerii
auto const vehicleend { iDirection };
auto const othervehicleend { Object->iDirection ^ 1 };
MoverParameters->Attach( vehicleend, othervehicleend, Object->MoverParameters, iType, true, false );
// update neighbour data for both affected vehicles
update_neighbours();
Object->update_neighbours();
// potentially attach automatic coupler adapter to allow the connection
// HACK: we're doing it after establishin actual connection, as the method needs valid neighbour data
auto &coupler { MoverParameters->Couplers[ vehicleend ] };
auto &othercoupler { Object->MoverParameters->Couplers[ ( othervehicleend != 2 ? othervehicleend : coupler.ConnectedNr ) ] };
if( coupler.type() != othercoupler.type() ) {
if( othercoupler.type() == TCouplerType::Automatic ) {
// try to attach adapter to the vehicle
attach_coupler_adapter(
vehicleend,
true );
}
else if( coupler.type() == TCouplerType::Automatic ) {
// try to attach adapter to the other vehicle
Object->attach_coupler_adapter(
othervehicleend != 2 ? othervehicleend : coupler.ConnectedNr,
true );
}
// update distance to neighbours on account of potentially attached adapter
update_neighbours();
Object->update_neighbours();
}
// potentially adjust vehicle position to avoid collision at the simulation start
if( MoverParameters->Neighbours[ vehicleend ].distance > -0.001 ) { return; }
Object->Move( MoverParameters->Neighbours[ vehicleend ].distance * Object->DirectionGet() );
// HACK: manually update vehicle position as it's used by neighbour distance update we do next
Object->MoverParameters->Loc = {
-Object->vPosition.x,
Object->vPosition.z,
Object->vPosition.y };
// update neighbour distance data after moving our vehicle
update_neighbours();
Object->update_neighbours();
}
bool TDynamicObject::UpdateForce(double dt)
{
if (!bEnabled)
return false;
if( dt > 0 ) {
// wywalenie WS zależy od ustawienia kierunku
MoverParameters->ComputeTotalForce( dt );
}
return true;
}
// initiates load change by specified amounts, with a platform on specified side
void TDynamicObject::LoadExchange( int const Disembark, int const Embark, int const Platforms ) {
/*
if( ( MoverParameters->Doors.open_control == control_t::passenger )
|| ( MoverParameters->Doors.open_control == control_t::mixed ) ) {
// jeśli jedzie do tyłu, to drzwi otwiera odwrotnie
auto const lewe = ( DirectionGet() > 0 ) ? 1 : 2;
auto const prawe = 3 - lewe;
if( Platform & lewe ) {
MoverParameters->OperateDoors( side::left, true, range_t::local );
}
if( Platform & prawe ) {
MoverParameters->OperateDoors( side::right, true, range_t::local );
}
}
*/
if( Platforms == 0 ) { return; } // edge case, if there's no accessible platforms discard the request
m_exchange.unload_count += Disembark;
m_exchange.load_count += Embark;
m_exchange.platforms = Platforms;
m_exchange.time = 0.0;
}
// calculates time needed to complete current load change
float TDynamicObject::LoadExchangeTime( int const Platforms ) {
m_exchange.platforms = Platforms;
return LoadExchangeTime();
}
float TDynamicObject::LoadExchangeTime() const {
if( m_exchange.unload_count < 0.01 && m_exchange.load_count < 0.01 ) { return 0.f; }
auto const baseexchangetime { m_exchange.unload_count / MoverParameters->UnLoadSpeed + m_exchange.load_count / MoverParameters->LoadSpeed };
auto const nominalexchangespeedfactor { ( m_exchange.platforms == 3 ? 2.f : 1.f ) };
auto const actualexchangespeedfactor { LoadExchangeSpeed() };
return baseexchangetime / ( actualexchangespeedfactor > 0.f ? actualexchangespeedfactor : nominalexchangespeedfactor );
}
// calculates current load exchange rate
float TDynamicObject::LoadExchangeSpeed() const {
// platforms (1:left, 2:right, 3:both)
// with exchange performed on both sides waiting times are halved
auto exchangespeedfactor { 0.f };
auto const lewe { DirectionGet() > 0 ? 1 : 2 };
auto const prawe { 3 - lewe };
if( m_exchange.platforms & lewe ) {
exchangespeedfactor += MoverParameters->Doors.instances[side::left].is_open ? 1.f : 0.f;
}
if( m_exchange.platforms & prawe ) {
exchangespeedfactor += MoverParameters->Doors.instances[side::right].is_open ? 1.f : 0.f;
}
return exchangespeedfactor;
}
// update state of load exchange operation
void TDynamicObject::update_exchange( double const Deltatime ) {
// TODO: move offset calculation after initial check, when the loading system is all unified
update_load_offset();
if( m_exchange.unload_count < 0.01 && m_exchange.load_count < 0.01 ) { return; }
if( MoverParameters->Vel < 2.0 ) {
auto const exchangespeed { LoadExchangeSpeed() };
if( exchangespeed < ( m_exchange.platforms == 3 ? 2.f : 1.f ) ) {
// the exchange isn't performed at optimal rate, or at all. try to open viable vehicle doors to speed it up
auto const lewe { DirectionGet() > 0 ? 1 : 2 };
auto const prawe { 3 - lewe };
if( m_exchange.platforms & lewe
&& false == (MoverParameters->Doors.instances[side::left].is_open || MoverParameters->Doors.instances[side::left].is_opening) ) {
// try to open left door
MoverParameters->OperateDoors( side::left, true, range_t::local );
}
if( m_exchange.platforms & prawe
&& false == (MoverParameters->Doors.instances[side::right].is_open || MoverParameters->Doors.instances[side::right].is_opening) ) {
// try to open right door
MoverParameters->OperateDoors( side::right, true, range_t::local );
}
}
if( exchangespeed > 0.f ) {
m_exchange.time += Deltatime;
while( m_exchange.unload_count > 0.01
&& m_exchange.time >= 1.0 ) {
m_exchange.time -= 1.0;
auto const exchangesize = std::min( m_exchange.unload_count, MoverParameters->UnLoadSpeed * exchangespeed );
m_exchange.unload_count -= exchangesize;
MoverParameters->LoadStatus = 1;
MoverParameters->LoadAmount = std::max( 0.f, MoverParameters->LoadAmount - exchangesize );
MoverParameters->ComputeMass();
update_load_visibility();
}
if( m_exchange.unload_count < 0.01 ) {
// finish any potential unloading operation before adding new load
// don't load more than can fit
m_exchange.load_count = std::min( m_exchange.load_count, MoverParameters->MaxLoad - MoverParameters->LoadAmount );
while( m_exchange.load_count > 0.01
&& m_exchange.time >= 1.0 ) {
m_exchange.time -= 1.0;
auto const exchangesize = std::min( m_exchange.load_count, MoverParameters->LoadSpeed * exchangespeed );
m_exchange.load_count -= exchangesize;
MoverParameters->LoadStatus = 2;
MoverParameters->LoadAmount = std::min( MoverParameters->MaxLoad, MoverParameters->LoadAmount + exchangesize ); // std::max not strictly needed but, eh
MoverParameters->ComputeMass();
update_load_visibility();
}
}
}
}
if( MoverParameters->Vel > 2.0 ) {
// if the vehicle moves too fast cancel the exchange
m_exchange.unload_count = 0;
m_exchange.load_count = 0;
}
if( m_exchange.unload_count < 0.01
&& m_exchange.load_count < 0.01 ) {
MoverParameters->LoadStatus = 4;
// if the exchange is completed (or canceled) close the door, if applicable
if( MoverParameters->Doors.close_control == control_t::passenger
|| MoverParameters->Doors.close_control == control_t::mixed ) {
if( MoverParameters->Vel > 2.0
|| Random() < (
// remotely controlled door are more likely to be left open
MoverParameters->Doors.close_control == control_t::passenger ? 0.75 : 0.50) ) {
MoverParameters->OperateDoors( side::left, false, range_t::local );
MoverParameters->OperateDoors( side::right, false, range_t::local );
}
}
// if the vehicle was emptied potentially switch load visualization model
if( MoverParameters->LoadAmount == 0 ) {
MoverParameters->AssignLoad( "" );
}
}
}
void TDynamicObject::LoadUpdate() {
MoverParameters->LoadStatus &= 1 | 2; // po zakończeniu będzie równe zero
// przeładowanie modelu ładunku
if( MoverParameters->LoadTypeChange ) {
// whether we succeed or not don't try more than once
MoverParameters->LoadTypeChange = false;
// bieżąca ścieżka do tekstur to dynamic/...
Global.asCurrentTexturePath = asBaseDir;
mdLoad = LoadMMediaFile_mdload( MoverParameters->LoadType.name );
// TODO: discern from vehicle component which merely uses vehicle directory and has no animations, so it can be initialized outright
// and actual vehicles which get their initialization after their animations are set up
if( mdLoad != nullptr ) {
mdLoad->Init();
}
// update bindings between lowpoly sections and potential load chunks placed inside them
update_load_sections();
// z powrotem defaultowa sciezka do tekstur
Global.asCurrentTexturePath = std::string( paths::textures );
}
}
void
TDynamicObject::update_load_sections() {
SectionLoadOrder.clear();
for( auto &section : Sections ) {
section.load = GetSubmodelFromName( mdLoad, section.compartment->pName );
if( section.load != nullptr ) {
// create entry for each load model chunk assigned to the section
// TBD, TODO: store also pointer to chunk submodel and control its visibility more directly, instead of per-section visibility flag?
auto loadchunkcount { section.load->count_children() };
while( loadchunkcount-- ) {
SectionLoadOrder.push_back( &section );
}
// HACK: disable automatic self-illumination threshold, at least until 3d model update
if( MoverParameters->CompartmentLights.start_type == start_t::manual ) {
section.load->SetSelfIllum( 2.0f, true, false );
}
}
}
shuffle_load_order();
}
void
TDynamicObject::update_load_visibility() {
// start with clean load chunk visibility slate
for( auto &section : Sections ) {
section.load_chunks_visible = 0;
}
// each entry in load order sequence matches a single chunk of the section it points to
// the length of load order sequence matches total number of load chunks
auto const loadpercentage { (
MoverParameters->MaxLoad == 0.f ?
0.0 :
MoverParameters->LoadAmount / MoverParameters->MaxLoad ) };
auto visiblechunkcount { (
SectionLoadOrder.empty() ?
0 :
static_cast<int>( std::ceil( loadpercentage * SectionLoadOrder.size() - 0.001f ) ) ) };
for( auto *section : SectionLoadOrder ) {
if( visiblechunkcount == 0 ) { break; }
section->load_chunks_visible++;
--visiblechunkcount;
}
}
void
TDynamicObject::update_load_offset() {
if( MoverParameters->LoadType.offset_min == 0.f ) { return; }
auto const loadpercentage { (
MoverParameters->MaxLoad == 0.f ?
0.0 :
100.0 * MoverParameters->LoadAmount / MoverParameters->MaxLoad ) };
LoadOffset = std::lerp( MoverParameters->LoadType.offset_min, 0.f, safe_clamp( 0.0, loadpercentage * 0.01, 1.0 ) );
}
void
TDynamicObject::shuffle_load_order() {
std::shuffle( std::begin( SectionLoadOrder ), std::end( SectionLoadOrder ), Global.random_engine );
// shift chunks assigned to corridors to the end of the list, so they show up last
std::stable_partition(
std::begin( SectionLoadOrder ), std::end( SectionLoadOrder ),
[]( vehicle_section const *section ) {
return section->compartment->pName.find("compartment") == 0 || section->compartment->pName.find("przedzial") == 0; } );
// NOTE: potentially we're left with a mix of corridor and external section loads
// but that's not necessarily a wrong outcome, so we leave it this way for the time being
}
/*
Ra:
Powinny być dwie funkcje wykonujące aktualizację fizyki. Jedna wykonująca
krok obliczeń, powtarzana odpowiednią liczbę razy, a druga wykonująca zbiorczą
aktualzację mniej istotnych elementów.
Ponadto należało by ustalić odległość składów od kamery i jeśli przekracza
ona np. 10km, to traktować składy jako uproszczone, np. bez wnikania w siły
na sprzęgach, opóźnienie działania hamulca itp. Oczywiście musi mieć to pewną
histerezę czasową, aby te tryby pracy nie przełączały się zbyt szybko.
*/
void TDynamicObject::update_destinations() {
if( DestinationSign.sign == nullptr ) { return; }
auto const lowvoltagepower { ( MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable ) };
DestinationSign.sign->fLight = DestinationSign.has_light && lowvoltagepower ? 2.0 : -1.0;
// jak są 4 tekstury wymienne, to nie zmieniać rozkładem
if( std::abs( m_materialdata.multi_textures ) >= 4 ) { return; }
// TODO: dedicated setting to discern electronic signs, instead of fallback on light presence
m_materialdata.replacable_skins[ 4 ] = DestinationSign.destination != null_handle && (false == DestinationSign.has_light // physical destination signs remain up until manually changed
|| (lowvoltagepower // lcd signs are off without power
&& ctOwner != nullptr)) ? // lcd signs are off for carriages without engine, potentially left on a siding
DestinationSign.destination :
DestinationSign.destination_off;
}
bool TDynamicObject::Update(double dt, double dt1)
{
if (dt1 == 0.0)
return true; // Ra: pauza
if (!MoverParameters->PhysicActivation &&
!MechInside) // to drugie, bo będąc w maszynowym blokuje się fizyka
return true; // McZapkie: wylaczanie fizyki gdy nie potrzeba
if (!MyTrack)
return false; // pojazdy postawione na torach portalowych mają MyTrack==NULL
if (!bEnabled)
return false; // a normalnie powinny mieć bEnabled==false
double dDOMoveLen;
// McZapkie: parametry powinny byc pobierane z toru
if (Axle0.vAngles.z != Axle1.vAngles.z)
{ // wyliczenie promienia z obrotów osi - modyfikację zgłosił youBy
ts.R = Axle0.vAngles.z - Axle1.vAngles.z; // różnica może dawać stałą ±M_2PI
if( ts.R > 15000.0 || ts.R < -15000.0 ) {
// szkoda czasu na zbyt duże promienie, 4km to promień nie wymagający przechyłki
ts.R = 0.0;
}
else {
// normalizacja
if( ts.R > M_PI ) { ts.R -= M_2PI; }
else if( ts.R < -M_PI ) { ts.R += M_2PI; }
if( ts.R != 0.0 ) {
// sin(0) results in division by zero
ts.R = -0.5 * MoverParameters->BDist / sin( ts.R * 0.5 );
}
}
}
else
ts.R = 0.0;
// ts.R=ComputeRadius(Axle1.pPosition,Axle2.pPosition,Axle3.pPosition,Axle0.pPosition);
// Ra: składową pochylenia wzdłużnego mamy policzoną w jednostkowym wektorze
// vFront
ts.Len = 1.0; // std::max(MoverParameters->BDist,MoverParameters->ADist);
ts.dHtrack = -vFront.y; // Axle1.pPosition.y-Axle0.pPosition.y; //wektor
// między skrajnymi osiami
// (!!!odwrotny)
ts.dHrail = (Axle1.GetRoll() + Axle0.GetRoll()) * 0.5; //średnia przechyłka pudła
// TTrackParam tp;
tp.Width = MyTrack->fTrackWidth;
// McZapkie-250202
tp.friction = MyTrack->Friction() * Global.fFriction * Global.FrictionWeatherFactor;
tp.CategoryFlag = MyTrack->iCategoryFlag & 15;
tp.DamageFlag = MyTrack->iDamageFlag;
tp.QualityFlag = MyTrack->iQualityFlag;
// couplers
if( MoverParameters->Couplers[0].CouplingFlag != coupling::faux
&& MoverParameters->Couplers[1].CouplingFlag != coupling::faux ) {
MoverParameters->InsideConsist = true;
}
else {
MoverParameters->InsideConsist = false;
}
if( TestFlag( MoverParameters->AIFlag, sound::attachcoupler ) ) {
auto *driver{ ctOwner ? ctOwner : Mechanik };
if( driver != nullptr ) {
driver->CheckVehicles( Connect );
}
ClearFlag( MoverParameters->AIFlag, sound::attachcoupler );
}
// napiecie sieci trakcyjnej
if (MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) { // dla EZT tylko silnikowy
tmpTraction.TractionVoltage = std::max( std::abs( MoverParameters->PantRearVolt ), std::abs( MoverParameters->PantFrontVolt ) );
// jeśli brak zasilania dłużej niż 0.2 sekundy (25km/h pod izolatorem daje 0.15s)
// Ra 2F1H: prowizorka, trzeba przechować napięcie, żeby nie wywalało WS pod izolatorem
if( tmpTraction.TractionVoltage > 0.0) {
NoVoltTime = 0;
}
else {
NoVoltTime += dt1;
if( NoVoltTime <= 0.2 ) {
tmpTraction.TractionVoltage = MoverParameters->PantographVoltage;
}
else {
// jeśli jedzie
if( MoverParameters->Vel > 0.5 ) {
// Ra 2014-07: doraźna blokada logowania zimnych lokomotyw - zrobić to trzeba inaczej
if( MoverParameters->Pantographs[end::front].is_active
|| MoverParameters->Pantographs[end::rear].is_active ) {
if( MoverParameters->Mains // Ra 15-01: logować tylko, jeśli WS załączony
&& MoverParameters->GetTrainsetHighVoltage() < 0.1f ) { // yB 16-03: i nie jest to asynchron zasilany z daleka
// Ra 15-01: bezwzględne współrzędne pantografu nie są dostępne więc lepiej się tego nie zaloguje
ErrorLog(
"Bad traction: " + MoverParameters->Name
+ " lost power for " + to_string( NoVoltTime, 2 ) + " sec. at "
+ to_string(vPosition) );
}
}
}
}
}
}
else {
tmpTraction.TractionVoltage = 0.95 * MoverParameters->EnginePowerSource.MaxVoltage;
}
tmpTraction.TractionFreq = 0;
tmpTraction.TractionMaxCurrent = 7500; // Ra: chyba za dużo? powinno wywalać przy 1500
tmpTraction.TractionResistivity = 0.3;
MoverParameters->PantographVoltage = tmpTraction.TractionVoltage;
// McZapkie: predkosc w torze przekazac do TrackParam
// McZapkie: Vel ma wymiar [km/h] (absolutny), V ma wymiar [m/s], taka przyjalem notacje
tp.Velmax = MyTrack->VelocityGet();
if (Mechanik)
{ // Ra 2F3F: do Driver.cpp to przenieść?
if( Mechanik->primary() ) {
MoverParameters->EqvtPipePress = GetEPP(); // srednie cisnienie w PG
}
if (Mechanik->primary() &&
(MoverParameters->EngineType == TEngineType::DieselEngine ||
MoverParameters->EngineType == TEngineType::DieselElectric)
&& MoverParameters->EIMCtrlType > 0) {
MoverParameters->CheckEIMIC(dt1);
if (MoverParameters->SpeedCtrl)
MoverParameters->CheckSpeedCtrl(dt1);
MoverParameters->eimic_real = std::min(MoverParameters->eimic,MoverParameters->eimicSpeedCtrl);
MoverParameters->SendCtrlToNext("EIMIC", MoverParameters->eimic_real, MoverParameters->CabActive);
}
if( Mechanik->primary()
&& MoverParameters->EngineType == TEngineType::ElectricInductionMotor ) {
// jesli glowny i z asynchronami, to niech steruje hamulcem i napedem lacznie dla calego pociagu/ezt
auto const kier = DirectionGet() * MoverParameters->CabOccupied > 0;
auto FED { 0.0 };
auto np { 0 };
auto masa { 0.0 };
auto FrED { 0.0 };
auto masamax { 0.0 };
auto FmaxPN { 0.0 };
auto FfulED { 0.0 };
auto FmaxED { 0.0 };
auto Frj { 0.0 };
auto osie { 0 };
// 0a. ustal aktualna nastawe zadania sily napedowej i hamowania
if( MoverParameters->Power < 1
&& ctOwner != nullptr ) {
MoverParameters->MainCtrlPos = ctOwner->Controlling()->MainCtrlPos*MoverParameters->MainCtrlPosNo / std::max(1, ctOwner->Controlling()->MainCtrlPosNo);
MoverParameters->SpeedCtrlValue = ctOwner->Controlling()->SpeedCtrlValue;
MoverParameters->SpeedCtrlUnit.IsActive = ctOwner->Controlling()->SpeedCtrlUnit.IsActive;
}
MoverParameters->CheckEIMIC(dt1);
MoverParameters->CheckSpeedCtrl(dt1);
auto eimic = std::min(MoverParameters->eimic, MoverParameters->eimicSpeedCtrl);
MoverParameters->eimic_real = eimic;
if (MoverParameters->EIMCtrlType == 2 && MoverParameters->MainCtrlPos == 0)
eimic = -1.0;
MoverParameters->SendCtrlToNext("EIMIC", std::max(0., eimic), MoverParameters->CabActive);
auto LBR = std::max(-eimic, 0.);
auto eim_lb = Mechanik->AIControllFlag || !MoverParameters->LocHandleTimeTraxx ? 0 : MoverParameters->eim_localbrake;
// 1. ustal wymagana sile hamowania calego pociagu
// - opoznienie moze byc ustalane na podstawie charakterystyki
// - opoznienie moze byc ustalane na podstawie mas i cisnien granicznych
//
// 2. ustal mozliwa do realizacji sile hamowania ED
// - w szczegolnosci powinien brac pod uwage rozne sily hamowania
for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p;
kier ? p = p->Next(4) : p = p->Prev(4))
{
++np;
masamax +=
p->MoverParameters->MBPM
+ ( p->MoverParameters->MBPM > 1.0 ?
0.0 :
p->MoverParameters->Mass )
+ p->MoverParameters->Mred;
auto const Nmax = ((p->MoverParameters->P2FTrans * p->MoverParameters->MaxBrakePress[0] -
p->MoverParameters->BrakeCylSpring) *
p->MoverParameters->BrakeCylMult[0] -
p->MoverParameters->BrakeSlckAdj) *
p->MoverParameters->BrakeCylNo * p->MoverParameters->BrakeRigEff;
FmaxPN += Nmax * p->MoverParameters->Hamulec->GetFC(
Nmax / (p->MoverParameters->NAxles * p->MoverParameters->NBpA),
p->MoverParameters->MED_Vref) *
1000; // sila hamowania pn
FmaxED += p->MoverParameters->Mains && p->MoverParameters->DirActive != 0 && p->MoverParameters->InvertersRatio == 1.0 &&
p->MoverParameters->eimc[eimc_p_Fh] * p->MoverParameters->NPoweredAxles > 0 ?
p->MoverParameters->eimc[eimc_p_Fh] * 1000 :
0; // chwilowy max ED -> do rozdzialu sil
FED -= std::min(p->MoverParameters->eimv[eimv_Fmax], 0.0) *
1000; // chwilowy max ED -> do rozdzialu sil
FfulED = std::min(p->MoverParameters->eimv[eimv_Fful], 0.0) *
1000; // chwilowy max ED -> do rozdzialu sil
FrED -= std::min(p->MoverParameters->eimv[eimv_Fmax], 0.0) *
1000; // chwilowo realizowane ED -> do pneumatyki
Frj += std::max(p->MoverParameters->eimv[eimv_Fmax], 0.0) *
1000;// chwilowo realizowany napęd -> do utrzymującego
masa += p->MoverParameters->TotalMass;
osie += p->MoverParameters->NAxles;
}
double RapidMult = 1.0;
if ((MoverParameters->BrakeDelays & bdelay_P + bdelay_R) == bdelay_P + bdelay_R
&& MoverParameters->BrakeDelayFlag & bdelay_P)
RapidMult = MoverParameters->RapidMult;
auto const amax = RapidMult * std::min(FmaxPN / masamax, MoverParameters->MED_amax);
Mechanik->fMedAmax = amax;
auto doorisopen {
false == MoverParameters->Doors.instances[side::left].is_closed
|| false == MoverParameters->Doors.instances[side::right].is_closed
|| ( MoverParameters->Doors.permit_needed
&& ( MoverParameters->Doors.instances[ side::left ].open_permit
|| MoverParameters->Doors.instances[ side::right ].open_permit ) ) };
doorisopen &= !(MoverParameters->ReleaseParkingBySpringBrakeWhenDoorIsOpen && MoverParameters->SpringBrake.IsActive);
if (MoverParameters->Vel < 0.5 && (eimic < 0 || doorisopen || MoverParameters->Hamulec->GetEDBCP()))
{
MoverParameters->ShuntMode = true;
}
if (MoverParameters->ShuntMode)
{
MoverParameters->ShuntModeAllow = false == doorisopen &&
LBR < 0.01;
}
if( MoverParameters->Vel > 1 && false == doorisopen )
{
MoverParameters->ShuntMode = false;
MoverParameters->ShuntModeAllow = MoverParameters->BrakePress > 0.2 &&
LBR < 0.01;
}
auto Fzad = amax * LBR * masa;
if ((MoverParameters->BrakeCtrlPos == MoverParameters->Handle->GetPos(bh_EB)
&& MoverParameters->eimc[eimc_p_abed] < 0.001) ||
MoverParameters->EmergencyValveFlow > 0)
Fzad = amax * masa; //pętla bezpieczeństwa - pełne służbowe
if (MoverParameters->ScndS &&
MoverParameters->Vel > MoverParameters->eimc[eimc_p_Vh1] && FmaxED > 0)
{
Fzad = std::min(LBR * FmaxED, FfulED);
}
if (MoverParameters->ShuntMode && eimic <= 0 || doorisopen /*||
(MoverParameters->V * MoverParameters->DirAbsolute < -0.2)*/)
{
auto const sbd { ( MoverParameters->SpringBrake.IsActive && MoverParameters->ReleaseParkingBySpringBrake ? 0.0 : MoverParameters->StopBrakeDecc ) };
Fzad = std::max( Fzad, sbd * masa );
}
if (Fzad > 1 && !MEDLogFile.is_open() && MoverParameters->Vel > 1)
{
MEDLogFile.open(
"MEDLOGS/" + MoverParameters->Name + "_" + std::to_string( ++MEDLogCount ) + ".csv",
std::ios::in | std::ios::out | std::ios::trunc );
MEDLogFile << "t\tVel\tMasa\tOsie\tFmaxPN\tFmaxED\tFfulED\tFrED\tFzad\tFzadED\tFzadPN";
for(int k=1;k<=np;k++)
{
MEDLogFile << "\tBP" << k;
}
MEDLogFile << "\n";
MEDLogFile.flush();
MEDLogInactiveTime = 0;
MEDLogTime = 0;
}
auto FzadED { 0.0 };
if( LBR > MoverParameters->MED_MinBrakeReqED
&& ( MoverParameters->BrakeHandle == TBrakeHandle::MHZ_EN57 ?
(MoverParameters->BrakeOpModeFlag & bom_MED) != 0 :
MoverParameters->EpFuse ) ) {
FzadED = std::min( Fzad, FmaxED );
}
/*/ELF - wdrazanie ED po powrocie na utrzymanie hamowania - do usuniecia
if (MoverParameters->EIMCtrlType == 2 && MoverParameters->MainCtrlPos < 2 && MoverParameters->eimic > -0.999)
{
FzadED = std::min(FzadED, MED_oldFED);
} //*/
//opoznienie wdrazania ED
if (FzadED > MED_oldFED)
{
if (MoverParameters->MED_ED_DelayTimer <= 0) {
MoverParameters->MED_ED_DelayTimer += dt1;
if (MoverParameters->MED_ED_DelayTimer > 0) {
}
else {
FzadED = std::min(FzadED, MED_oldFED);
}
}
else
{
FzadED = std::min(FzadED, MED_oldFED);
MoverParameters->MED_ED_DelayTimer = FrED > 0 ? -MoverParameters->MED_ED_Delay2 : -MoverParameters->MED_ED_Delay1;
}
}
if (MoverParameters->BrakeCtrlPos == MoverParameters->Handle->GetPos(bh_EB)
&& 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];
float nPrzekrF = 0;
bool test = true;
float* FzED = new float[np];
float* FzEP = new float[np];
float* FmaxEP = new float[np];
// 3. ustaw pojazdom sile hamowania ED
// - proporcjonalnie do mozliwosci
// 4. ustal potrzebne dohamowanie pneumatyczne
// - od sily zadanej trzeba odjac realizowana przez ED
// 5. w razie potrzeby wlacz hamulec utrzymujacy
// - gdy zahamowany ma ponizej 2 km/h
// 6. ustaw pojazdom sile hamowania ep
// - proporcjonalnie do masy, do liczby osi, rowne cisnienia - jak
// bedzie, tak bedzie dobrze
float Fpoj = 0; // MoverParameters->CabOccupied < 0
////ALGORYTM 2 - KAZDEMU PO ROWNO, ale nie wiecej niz eped * masa
// 1. najpierw daj kazdemu tyle samo
int i = 0;
for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p;
p = kier == true ? p->Next(4) : p->Prev(4) )
{
auto const Nmax = ((p->MoverParameters->P2FTrans * p->MoverParameters->MaxBrakePress[0] -
p->MoverParameters->BrakeCylSpring) *
p->MoverParameters->BrakeCylMult[0] -
p->MoverParameters->BrakeSlckAdj) *
p->MoverParameters->BrakeCylNo * p->MoverParameters->BrakeRigEff;
FmaxEP[i] = Nmax *
p->MoverParameters->Hamulec->GetFC(
Nmax / (p->MoverParameters->NAxles * p->MoverParameters->NBpA),
p->MoverParameters->MED_Vref) *
1000; // sila hamowania pn
PrzekrF[i] = false;
FzED[i] = FmaxED > 0 ? FzadED / FmaxED : 0;
p->MoverParameters->AnPos =
MoverParameters->ScndS ? LBR : FzED[i];
FzEP[ i ] = static_cast<float>( FzadPN * p->MoverParameters->NAxles ) / static_cast<float>( osie );
++i;
p->MoverParameters->ShuntMode = MoverParameters->ShuntMode;
p->MoverParameters->ShuntModeAllow = MoverParameters->ShuntModeAllow;
}
while (test)
{
test = false;
i = 0;
float przek = 0;
for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p;
p = kier == true ? p->Next(4) : p->Prev(4) )
{
if (FzEP[i] > 0.01 &&
FzEP[i] > p->MoverParameters->TotalMass * p->MoverParameters->eimc[eimc_p_eped] + std::min(p->MoverParameters->eimv[eimv_Fmax], 0.) * 1000 &&
!PrzekrF[i])
{
float przek1 = -std::min(p->MoverParameters->eimv[eimv_Fmax], 0.) * 1000 +
FzEP[i] -
p->MoverParameters->TotalMass *
p->MoverParameters->eimc[eimc_p_eped] * 0.999;
PrzekrF[i] = true;
test = true;
nPrzekrF++;
przek1 = std::min(przek1, FzEP[i]);
FzEP[i] -= przek1;
if (FzEP[i] < 0)
FzEP[i] = 0;
przek += przek1;
}
++i;
}
i = 0;
przek = przek / (np - nPrzekrF);
for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p;
true == kier ? p = p->Next(4) : p = p->Prev(4))
{
if (!PrzekrF[i])
{
FzEP[i] += przek;
}
++i;
}
}
i = 0;
for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p;
true == kier ? p = p->Next(4) : p = p->Prev(4))
{
float Nmax = ((p->MoverParameters->P2FTrans * p->MoverParameters->MaxBrakePress[0] -
p->MoverParameters->BrakeCylSpring) *
p->MoverParameters->BrakeCylMult[0] -
p->MoverParameters->BrakeSlckAdj) *
p->MoverParameters->BrakeCylNo * p->MoverParameters->BrakeRigEff;
if (FrED > 0.1)
p->MoverParameters->MED_EPVC_CurrentTime = 0;
else
p->MoverParameters->MED_EPVC_CurrentTime += dt1;
bool EPVC = p->MoverParameters->MED_EPVC && (p->MoverParameters->MED_EPVC_Time < 0 || p->MoverParameters->MED_EPVC_CurrentTime < p->MoverParameters->MED_EPVC_Time);
float VelC = EPVC ? safe_clamp(p->MoverParameters->Vel, p->MoverParameters->MED_Vmin, p->MoverParameters->MED_Vmax) : p->MoverParameters->MED_Vref;//korekcja EP po prędkości
float FmaxPoj = Nmax *
p->MoverParameters->Hamulec->GetFC(
Nmax / (p->MoverParameters->NAxles * p->MoverParameters->NBpA), VelC) *
1000; // sila hamowania pn
p->MoverParameters->LocalBrakePosAEIM = FzEP[i] / FmaxPoj;
if (p->MoverParameters->LocalBrakePosAEIM>0.009)
if (p->MoverParameters->P2FTrans * p->MoverParameters->BrakeCylMult[0] *
p->MoverParameters->MaxBrakePress[0] != 0)
{
float x = (p->MoverParameters->BrakeSlckAdj / p->MoverParameters->BrakeCylMult[0] +
p->MoverParameters->BrakeCylSpring) / (p->MoverParameters->P2FTrans *
p->MoverParameters->MaxBrakePress[0]);
p->MoverParameters->LocalBrakePosAEIM = x + (1 - x) * p->MoverParameters->LocalBrakePosAEIM;
}
else
p->MoverParameters->LocalBrakePosAEIM = p->MoverParameters->LocalBrakePosAEIM;
else
p->MoverParameters->LocalBrakePosAEIM = 0;
if (p->MoverParameters->LocHandleTimeTraxx)
{
p->MoverParameters->eim_localbrake = eim_lb;
p->MoverParameters->LocalBrakePosAEIM = std::max(p->MoverParameters->LocalBrakePosAEIM, eim_lb);
}
++i;
}
MED[0][0] = masa*0.001;
MED[0][1] = amax;
MED[0][2] = Fzad*0.001;
MED[0][3] = FmaxPN*0.001;
MED[0][4] = FmaxED*0.001;
MED[0][5] = FrED*0.001;
MED[0][6] = FzadPN*0.001;
MED[0][7] = nPrzekrF;
if (MEDLogFile.is_open())
{
MEDLogFile << MEDLogTime << "\t" << MoverParameters->Vel << "\t" << masa*0.001 << "\t" << osie << "\t" << FmaxPN*0.001 << "\t" << FmaxED*0.001 << "\t"
<< FfulED*0.001 << "\t" << FrED*0.001 << "\t" << Fzad*0.001 << "\t" << FzadED*0.001 << "\t" << FzadPN*0.001;
for (TDynamicObject *p = GetFirstDynamic(MoverParameters->CabOccupied < 0 ? 1 : 0, 4); p;
true == kier ? p = p->Next(4) : p = p->Prev(4))
{
MEDLogFile << "\t" << p->MoverParameters->BrakePress;
}
MEDLogFile << "\n";
if (floor(MEDLogTime + dt1) > floor(MEDLogTime))
{
MEDLogFile.flush();
}
MEDLogTime += dt1;
if (MoverParameters->Vel < 0.1 || MoverParameters->MainCtrlPowerPos() > 0)
{
MEDLogInactiveTime += dt1;
}
else
{
MEDLogInactiveTime = 0;
}
if (MEDLogInactiveTime > 5)
{
MEDLogFile.flush();
MEDLogFile.close();
}
}
delete[] PrzekrF;
delete[] FzED;
delete[] FzEP;
delete[] FmaxEP;
MED_oldFED = FzadED;
}
Mechanik->Update(dt1); // przebłyski świadomości AI
}
// fragment "z EXE Kursa"
if( MoverParameters->Mains ) { // nie wchodzić w funkcję bez potrzeby
if( false == (MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable)
/*
// NOTE: disabled on account of multi-unit setups, where the unmanned unit wouldn't be affected
&& ( Controller == Humandriver )
*/
&& MoverParameters->EngineType != TEngineType::DieselEngine
&& MoverParameters->EngineType != TEngineType::WheelsDriven ) { // jeśli bateria wyłączona, a nie diesel ani drezyna reczna
if( MoverParameters->MainSwitch( false, MoverParameters->TrainType == dt_EZT ? range_t::unit : range_t::local ) ) {
// wyłączyć zasilanie
// NOTE: we turn off entire EMU, but only the affected unit for other multi-unit consists
MoverParameters->EventFlag = true;
// drop pantographs
// NOTE: this isn't universal behaviour
// TODO: have this dependant on .fiz-driven flag
// NOTE: moved to pantspeed calculation part a little later in the function. all remarks and todo still apply
/*
MoverParameters->PantFront( false, ( MoverParameters->TrainType == dt_EZT ? range::unit : range::local ) );
MoverParameters->PantRear( false, ( MoverParameters->TrainType == dt_EZT ? range::unit : range::local ) );
*/
}
}
}
// przekazanie pozycji do fizyki
// NOTE: coordinate system swap
// TODO: replace with regular glm vectors
TLocation const l {
-vPosition.x,
vPosition.z,
vPosition.y };
TRotation const r {
0.0,
0.0,
modelRot.z };
// McZapkie-260202 - dMoveLen przyda sie przy stukocie kol
dDOMoveLen = GetdMoveLen() + MoverParameters->ComputeMovement(dt, dt1, ts, tp, tmpTraction, l, r);
if( Mechanik ) {
// dodanie aktualnego przemieszczenia
Mechanik->MoveDistanceAdd( dDOMoveLen );
}
if( simulation::Train != nullptr
&& simulation::Train->Dynamic() == this ) {
// update distance meter in user-controlled cab
// TBD: place the meter on mover logic level?
simulation::Train->add_distance( dDOMoveLen );
}
glm::dvec3 old_pos = vPosition;
Move(dDOMoveLen);
m_future_movement = (vPosition - old_pos) / dt1 * Timer::GetDeltaRenderTime();
if (!bEnabled) // usuwane pojazdy nie mają toru
{ // pojazd do usunięcia
bDynamicRemove = true; // sprawdzić
return false;
}
ResetdMoveLen();
// McZapkie-260202
// tupot mew, tfu, stukot kol:
if( MyTrack->fSoundDistance != -1 ) {
if( MyTrack->fSoundDistance != dRailLength ) {
if( dRailLength > 0.0 ) {
for( auto &axle : m_axlesounds ) {
axle.distance = axle.offset;
}
}
dRailLength = MyTrack->fSoundDistance;
}
if( dRailLength > 0.0 ) {
if( MoverParameters->Vel > 0 ) {
// TODO: track quality and/or environment factors as separate subroutine
auto volume =
std::lerp(
0.8, 1.2,
std::clamp(
MyTrack->iQualityFlag / 10.0,
0.0, 1.5 ) );
switch( MyTrack->eEnvironment ) {
case e_tunnel: {
volume *= 1.3;
break;
}
case e_bridge: {
volume *= 1.5;
break;
}
default: {
break;
}
}
if( dRailLength > 0.0 ) {
auto axleindex { 0 };
auto const directioninconsist { (
ctOwner == nullptr ?
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 );
}
}
}
++axleindex;
}
}
}
}
}
// McZapkie-260202 end
// fragment z EXE Kursa
/* if (MoverParameters->TrainType==dt_ET42)
{
if ((MoverParameters->DynamicBrakeType=dbrake_switch) &&
((MoverParameters->BrakePress >
0.2) || ( MoverParameters->PipePress < 0.36 )))
{
MoverParameters->StLinFlag=true;
}
else
if ((MoverParameters->DynamicBrakeType=dbrake_switch) &&
(MoverParameters->BrakePress <
0.1))
{
MoverParameters->StLinFlag=false;
}
} */
if (MoverParameters->Vel != 0)
{ // McZapkie-050402: krecenie kolami:
glm::dvec3 old_wheels = glm::dvec3(dWheelAngle[0], dWheelAngle[1], dWheelAngle[2]);
dWheelAngle[0] += 114.59155902616464175359630962821 * MoverParameters->V * dt1 /
MoverParameters->WheelDiameterL; // przednie toczne
dWheelAngle[1] += MoverParameters->nrot * dt1 * 360.0; // napędne
dWheelAngle[2] += 114.59155902616464175359630962821 * MoverParameters->V * dt1 /
MoverParameters->WheelDiameterT; // tylne toczne
m_future_wheels_angle = (glm::dvec3(dWheelAngle[0], dWheelAngle[1], dWheelAngle[2]) - old_wheels) / dt1 * Timer::GetDeltaRenderTime();
dWheelAngle[0] = clamp_circular( dWheelAngle[0] );
dWheelAngle[1] = clamp_circular( dWheelAngle[1] );
dWheelAngle[2] = clamp_circular( dWheelAngle[2] );
}
if (pants) // pantograf może być w wagonie kuchennym albo pojeździe rewizyjnym (np. SR61)
{ // przeliczanie kątów dla pantografów
double k; // tymczasowy kąt
double PantDiff;
TAnimPant *p; // wskaźnik do obiektu danych pantografu
double fCurrent = (
MoverParameters->DynamicBrakeFlag && MoverParameters->ResistorsFlag ?
0 :
std::abs( MoverParameters->Itot ) * MoverParameters->IsVehicleEIMBrakingFactor() )
+ MoverParameters->TotalCurrent; // prąd pobierany przez pojazd - bez sensu z tym (TotalCurrent)
// TotalCurrent to bedzie prad nietrakcyjny (niezwiazany z napedem)
// fCurrent+=fabs(MoverParameters->Voltage)*1e-6; //prąd płynący przez woltomierz, rozładowuje kondensator orgromowy 4µF
/*
double fPantCurrent = fCurrent; // normalnie cały prąd przez jeden pantograf
if (iAnimType[ANIM_PANTS] > 1) { // a jeśli są dwa pantografy //Ra 1014-11: proteza, trzeba zrobić sensowniej
if (pants[0].fParamPants->hvPowerWire && pants[1].fParamPants->hvPowerWire) { // i oba podłączone do drutów
fPantCurrent = fCurrent * 0.5; // to dzielimy prąd równo na oba (trochę bez sensu, ale lepiej tak niż podwoić prąd)
}
}
*/
// test whether more than one pantograph touches the wire
// NOTE: we're duplicating lot of code from below
// TODO: clean this up
auto activepantographs { 0 };
for( int idx = 0; idx < iAnimType[ ANIM_PANTS ]; ++idx ) {
auto const *pantograph { pants[ idx ].fParamPants };
if( Global.bLiveTraction == false ) {
if( pantograph->PantWys >= 1.2 ) {
++activepantographs;
}
}
else {
if( pantograph->hvPowerWire != nullptr
&& true == MoverParameters->Pantographs[end::front].is_active
&& pantograph->PantTraction - pantograph->PantWys < 0.01 ) { // tolerancja niedolegania
++activepantographs;
}
}
}
auto const fPantCurrent { fCurrent / std::max( 1, activepantographs ) };
for (int i = 0; i < iAnimType[ANIM_PANTS]; ++i)
{ // pętla po wszystkich pantografach
p = pants[i].fParamPants;
if (p->PantWys < 0)
{ // patograf został połamany, liczony nie będzie
if (p->fAngleL > p->fAngleL0)
p->fAngleL -= 0.2 * dt1; // nieco szybciej niż jak dla opuszczania
if (p->fAngleL < p->fAngleL0)
p->fAngleL = p->fAngleL0; // kąt graniczny
if (p->fAngleU < M_PI)
p->fAngleU += 0.5 * dt1; // górne się musi ruszać szybciej.
if (p->fAngleU > M_PI)
p->fAngleU = M_PI;
if (i & 1) // zgłoszono, że po połamaniu potrafi zostać zasilanie
MoverParameters->PantRearVolt = 0.0;
else
MoverParameters->PantFrontVolt = 0.0;
continue; // reszta wtedy nie jest wykonywana
}
PantDiff = p->PantTraction - p->PantWys; // docelowy-aktualny
switch (i) // numer pantografu
{ // trzeba usunąć to rozróżnienie
case 0:
if (Global.bLiveTraction == false
&& p->hvPowerWire == nullptr ) {
// jeśli nie ma drutu, może pooszukiwać
MoverParameters->PantFrontVolt =
p->PantWys >= 1.2 ?
0.95 * MoverParameters->EnginePowerSource.MaxVoltage :
0.0;
}
else if( true == MoverParameters->Pantographs[end::front].is_active
&& PantDiff < 0.01 ) // tolerancja niedolegania
{
if (p->hvPowerWire) {
auto const lastvoltage { MoverParameters->PantFrontVolt };
// TODO: wyliczyć trzeba prąd przypadający na pantograf i wstawić do GetVoltage()
if( lastvoltage == 0.0 ) {
// HACK: retrieve the wire voltage for calculations down the road without blowing up the supply
MoverParameters->PantFrontVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, 0.0 );
}
else {
MoverParameters->PantFrontVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, fPantCurrent );
if( MoverParameters->PantFrontVolt > 0.0 ) {
fCurrent -= fPantCurrent; // taki prąd płynie przez powyższy pantograf (unless it doesn't)
}
}
// TODO: refactor reaction to voltage change to mover as sound event for specific pantograph
if( lastvoltage == 0.0
&& MoverParameters->PantFrontVolt > 0.0 ) {
for( auto &pantograph : m_pantographsounds ) {
if( pantograph.sPantUp.offset().z > 0 ) {
// limit to pantographs located in the front half of the vehicle
pantograph.sPantUp.play( sound_flags::exclusive );
}
}
}
}
else
MoverParameters->PantFrontVolt = 0.0;
}
else
MoverParameters->PantFrontVolt = 0.0;
( fPantCurrent > 0.0 ? MoverParameters->EnergyMeter.first : MoverParameters->EnergyMeter.second ) += MoverParameters->PantRearVolt * fPantCurrent * dt1 / 3600000.0;
break;
case 1:
if( false == Global.bLiveTraction
&& nullptr == p->hvPowerWire ) {
// jeśli nie ma drutu, może pooszukiwać
MoverParameters->PantRearVolt =
p->PantWys >= 1.2 ?
0.95 * MoverParameters->EnginePowerSource.MaxVoltage :
0.0;
}
else if ( true == MoverParameters->Pantographs[end::rear].is_active
&& PantDiff < 0.01 )
{
if (p->hvPowerWire) {
auto const lastvoltage { MoverParameters->PantRearVolt };
// TODO: wyliczyć trzeba prąd przypadający na pantograf i wstawić do GetVoltage()
if( lastvoltage == 0.0 ) {
// HACK: retrieve the wire voltage for calculations down the road without blowing up the supply
MoverParameters->PantRearVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, 0.0 );
}
else {
MoverParameters->PantRearVolt = p->hvPowerWire->VoltageGet( MoverParameters->PantographVoltage, fPantCurrent );
if( MoverParameters->PantRearVolt > 0.0 ) {
fCurrent -= fPantCurrent; // taki prąd płynie przez powyższy pantograf (unless it doesn't)
}
}
// TODO: refactor reaction to voltage change to mover as sound event for specific pantograph
if( lastvoltage == 0.0
&& MoverParameters->PantRearVolt > 0.0 ) {
for( auto &pantograph : m_pantographsounds ) {
if( pantograph.sPantUp.offset().z < 0 ) {
// limit to pantographs located in the rear half of the vehicle
pantograph.sPantUp.play( sound_flags::exclusive );
}
}
}
}
else
MoverParameters->PantRearVolt = 0.0;
}
else {
// Global.iPause ^= 2;
MoverParameters->PantRearVolt = 0.0;
}
( fPantCurrent > 0.0 ? MoverParameters->EnergyMeter.first : MoverParameters->EnergyMeter.second ) += MoverParameters->PantFrontVolt * fPantCurrent * dt1 / 3600000.0;
break;
} // pozostałe na razie nie obsługiwane
if( MoverParameters->PantPress > (
MoverParameters->TrainType == dt_EZT ?
2.45 : // Ra 2013-12: Niebugocław mówi, że w EZT podnoszą się przy 2.5
3.45 ) ) {
// z EXE Kursa
// Ra: wysokość zależy od ciśnienia !!!
pantspeedfactor = 0.015 * MoverParameters->PantPress * dt1;
}
else {
pantspeedfactor = 0.0;
}
if( false == ( MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable ) ) {
pantspeedfactor = 0.0;
}
pantspeedfactor = std::max( 0.0, pantspeedfactor );
k = p->fAngleL;
if( pantspeedfactor > 0.0
&& MoverParameters->Pantographs[i].is_active )// jeśli ma być podniesiony
{
if (PantDiff > 0.001) // jeśli nie dolega do drutu
{ // jeśli poprzednia wysokość jest mniejsza niż pożądana, zwiększyć kąt dolnego
// ramienia zgodnie z ciśnieniem
if (pantspeedfactor >
0.55 * PantDiff) // 0.55 to około pochodna kąta po wysokości
k += 0.55 * PantDiff; // ograniczenie "skoku" w danej klatce
else
k += pantspeedfactor; // dolne ramię
// jeśli przekroczono kąt graniczny, zablokować pantograf
// (wymaga interwencji pociągu sieciowego)
}
else if (PantDiff < -0.001) {
// drut się obniżył albo pantograf podniesiony za wysoko
// jeśli wysokość jest zbyt duża, wyznaczyć zmniejszenie kąta
// jeśli zmniejszenie kąta jest zbyt duże, przejść do trybu łamania pantografu
// if (PantFrontDiff<-0.05) //skok w dół o 5cm daje złąmanie pantografu
k += 0.4 * PantDiff; // mniej niż pochodna kąta po wysokości
} // jeśli wysokość jest dobra, nic więcej nie liczyć
}
else
{ // jeśli ma być na dole
if (k > p->fAngleL0) // jeśli wyżej niż położenie wyjściowe
k -= 0.15 * dt1; // ruch w dół
if (k < p->fAngleL0)
k = p->fAngleL0; // położenie minimalne
}
if (k != p->fAngleL)
{ //żeby nie liczyć w kilku miejscach ani gdy nie potrzeba
if (k + p->fAngleU < M_PI)
{ // o ile nie został osiągnięty kąt maksymalny
p->fAngleL = k; // zmieniony kąt
// wyliczyć kąt górnego ramienia z wzoru (a)cosinusowego
//=acos((b*cos()+c)/a)
// p->dPantAngleT=acos((1.22*cos(k)+0.535)/1.755); //górne ramię
p->fAngleU = acos((p->fLenL1 * cos(k) + p->fHoriz) / p->fLenU1); // górne ramię
// wyliczyć aktualną wysokość z wzoru sinusowego
// h=a*sin()+b*sin()
// wysokość całości
p->PantWys = p->fLenL1 * sin(k) + p->fLenU1 * sin(p->fAngleU) + p->fHeight;
}
}
} // koniec pętli po pantografach
// TBD, TODO: generate sound event during mover update instead?
if( MoverParameters->Pantographs[end::front].sound_event != MoverParameters->Pantographs[ end::front ].is_active ) {
if( MoverParameters->Pantographs[ end::front ].is_active ) {
// pantograph moving up
// TBD: add a sound?
}
else {
// pantograph dropping
for( auto &pantograph : m_pantographsounds ) {
if( pantograph.sPantDown.offset().z > 0 ) {
// limit to pantographs located in the front half of the vehicle
pantograph.sPantDown.play( sound_flags::exclusive );
}
}
}
MoverParameters->Pantographs[ end::front ].sound_event = MoverParameters->Pantographs[ end::front ].is_active;
}
if( MoverParameters->Pantographs[ end::rear ].sound_event != MoverParameters->Pantographs[ end::rear ].is_active ) {
if( MoverParameters->Pantographs[ end::rear ].is_active ) {
// pantograph moving up
// TBD: add a sound?
}
else {
// pantograph dropping
for( auto &pantograph : m_pantographsounds ) {
if( pantograph.sPantDown.offset().z < 0 ) {
// limit to pantographs located in the front half of the vehicle
pantograph.sPantDown.play( sound_flags::exclusive );
}
}
}
MoverParameters->Pantographs[ end::rear ].sound_event = MoverParameters->Pantographs[ end::rear ].is_active;
}
}
else if (MoverParameters->EnginePowerSource.SourceType == TPowerSource::InternalSource)
if (MoverParameters->EnginePowerSource.PowerType == TPowerType::SteamPower)
// if (smPatykird1[0])
{ // Ra: animacja rozrządu parowozu, na razie nieoptymalizowane
/* //Ra: tymczasowo wyłączone ze względu na porządkowanie animacji
pantografów
double fi,dx,c2,ka,kc;
double sin_fi,cos_fi;
double L1=1.6688888888888889;
double L2=5.6666666666666667; //2550/450
double Lc=0.4;
double L=5.686422222; //2558.89/450
double G1,G2,G3,ksi,sin_ksi,gam;
double G1_2,G2_2,G3_2; //kwadraty
//ruch tłoków oraz korbowodów
for (int i=0;i<=1;++i)
{//obie strony w ten sam sposób
fi=DegToRad(dWheelAngle[1]+(i?pant2x:pant1x)); //kąt obrotu koła dla
tłoka 1
sin_fi=sin(fi);
cos_fi=cos(fi);
dx=panty*cos_fi+sqrt(panth*panth-panty*panty*sin_fi*sin_fi)-panth;
//nieoptymalne
if (smPatykird1[i]) //na razie zabezpieczenie
smPatykird1[i]->SetTranslate(float3(dx,0,0));
ka=-asin(panty/panth)*sin_fi;
if (smPatykirg1[i]) //na razie zabezpieczenie
smPatykirg1[i]->SetRotateXYZ(vector3(RadToDeg(ka),0,0));
//smPatykirg1[0]->SetRotate(float3(0,1,0),RadToDeg(fi)); //obracamy
//ruch drążka mimośrodkowego oraz jarzma
//korzystałem z pliku PDF "mm.pdf" (opis czworoboku
korbowo-wahaczowego):
//"MECHANIKA MASZYN. Szkic wykładu i laboratorium komputerowego."
//Prof. dr hab. inż. Jerzy Zajączkowski, 2007, Politechnika Łódzka
//L1 - wysokość (w pionie) osi jarzma ponad osią koła
//L2 - odległość w poziomie osi jarzma od osi koła
//Lc - długość korby mimośrodu na kole
//Lr - promień jarzma =1.0 (pozostałe przeliczone proporcjonalnie)
//L - długość drążka mimośrodowego
//fi - kąt obrotu koła
//ksi - kąt obrotu jarzma (od pionu)
//gam - odchylenie drążka mimośrodowego od poziomu
//G1=(Lr*Lr+L1*L1+L2*L2+Kc*Lc-L*L-2.0*Lc*L2*cos(fi)+2.0*Lc*L1*sin(fi))/(Lr*Lr);
//G2=2.0*(L2-Lc*cos(fi))/Lr;
//G3=2.0*(L1-Lc*sin(fi))/Lr;
fi=DegToRad(dWheelAngle[1]+(i?pant2x:pant1x)-96.77416667); //kąt
obrotu koła dla
tłoka 1
//1) dla dWheelAngle[1]=0° korba jest w dół, a mimośród w stronę
jarzma, czyli
fi=-7°
//2) dla dWheelAngle[1]=90° korba jest do tyłu, a mimośród w dół,
czyli fi=83°
sin_fi=sin(fi);
cos_fi=cos(fi);
G1=(1.0+L1*L1+L2*L2+Lc*Lc-L*L-2.0*Lc*L2*cos_fi+2.0*Lc*L1*sin_fi);
G1_2=G1*G1;
G2=2.0*(L2-Lc*cos_fi);
G2_2=G2*G2;
G3=2.0*(L1-Lc*sin_fi);
G3_2=G3*G3;
sin_ksi=(G1*G2-G3*_fm_sqrt(G2_2+G3_2-G1_2))/(G2_2+G3_2); //x1 (minus
delta)
ksi=asin(sin_ksi); //kąt jarzma
if (smPatykirg2[i])
smPatykirg2[i]->SetRotateXYZ(vector3(RadToDeg(ksi),0,0)); //obrócenie
jarzma
//1) ksi=-23°, gam=
//2) ksi=10°, gam=
//gam=acos((L2-sin_ksi-Lc*cos_fi)/L); //kąt od poziomu, liczony
względem poziomu
//gam=asin((L1-cos_ksi-Lc*sin_fi)/L); //kąt od poziomu, liczony
względem pionu
gam=atan2((L1-cos(ksi)+Lc*sin_fi),(L2-sin_ksi+Lc*cos_fi)); //kąt od
poziomu
if (smPatykird2[i]) //na razie zabezpieczenie
smPatykird2[i]->SetRotateXYZ(vector3(RadToDeg(-gam-ksi),0,0));
//obrócenie drążka
mimośrodowego
}
*/
}
if (MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector
&& MoverParameters->EnginePowerSource.CollectorParameters.FakePower) {
MoverParameters->PantRearVolt = 0.95 * MoverParameters->EnginePowerSource.MaxVoltage;
MoverParameters->PantFrontVolt = 0.95 * MoverParameters->EnginePowerSource.MaxVoltage;
}
// wipers
if (dWiperPos.size() > 0) // tylko dla wozow ze zdefiniowanymi wycierakami
{
for (int i = 0; i < dWiperPos.size(); i++) // iteracja po kazdej wycieraczce
{
bool wipersActive;
auto const bytesum = MoverParameters->WiperList[MoverParameters->wiperSwitchPos].byteSum;
// rozroznienie kabinowe
if (MoverParameters->CabActive == 1)
{
wipersActive = (bytesum & 1 << i) != 0 && MoverParameters->Battery;
}
else if (MoverParameters->CabActive == -1)
{
// odwroconie indexow wycieraczek
wipersActive = (bytesum & 1 << dWiperPos.size() - 1 - i) != 0 && MoverParameters->Battery;
}
else {
wipersActive = false;
}
auto const currentWiperParams = MoverParameters->WiperList[workingSwitchPos[i]];
wiperOutTimer[i] += dt1; // aktualizujemy zegarek
wiperParkTimer[i] += dt1; // aktualizujemy zegarek
if (wipersActive || dWiperPos[i] > 0.0) // zeby wrocily do trybu park
{
if (dWiperPos[i] > 0.0 && !wipersActive) // bezwzgledny powrot do zera
{
dWiperPos[i] = std::max(0.0, dWiperPos[i] - 1.f / currentWiperParams.WiperSpeed * dt1);
}
else {
if (dWiperPos[i] < 1.0 && !wiperDirection[i] && wiperParkTimer[i] > currentWiperParams.interval)
// go out
{
if (!sWiperFromParkPlayed)
{
sWiperFromPark.play(sound_flags::exclusive);
sWiperFromParkPlayed = true;
}
dWiperPos[i] = std::min(1.0, dWiperPos[i] + 1.f / currentWiperParams.WiperSpeed * dt1);
}
if (dWiperPos[i] > 0.0 && wiperDirection[i] && wiperOutTimer[i] > currentWiperParams.outBackDelay)
// return back
{
if (!sWiperToParkPlayed)
{
sWiperToPark.play(sound_flags::exclusive);
sWiperToParkPlayed = true;
}
dWiperPos[i] = std::max(0.0, dWiperPos[i] - 1.f / currentWiperParams.WiperSpeed * dt1);
}
if (dWiperPos[i] >= 1.0) // we reached end
{
sWiperFromPark.stop();
sWiperFromParkPlayed = false;
wiperParkTimer[i] = 0.0;
wiperDirection[i] = true; // switch direction
}
if (dWiperPos[i] <= 0.0)
{
// when in park position
sWiperToPark.stop();
sWiperToParkPlayed = false;
wiperOutTimer[i] = 0.0;
wiperDirection[i] = false;
workingSwitchPos[i] = MoverParameters->wiperSwitchPos; // update configuration
}
}
}
}
}
// mirrors
if( MoverParameters->Vel > MoverParameters->MirrorVelClose
|| MoverParameters->CabActive == 0 && activation::mirrors
|| MoverParameters->MirrorForbidden ) {
// automatically fold mirrors when above velocity threshold
if( dMirrorMoveL > 0.0 ) {
dMirrorMoveL = std::max(
0.0,
dMirrorMoveL - 1.0 * dt1 );
}
if( dMirrorMoveR > 0.0 ) {
dMirrorMoveR = std::max(
0.0,
dMirrorMoveR - 1.0 * dt1 );
}
}
else {
// unfold mirror on the side with open doors, if not moving too fast
if( dMirrorMoveL < 1.0
&& true == MoverParameters->Doors.instances[side::left].open_permit ) {
dMirrorMoveL = std::min(
1.0,
dMirrorMoveL + 1.0 * dt1 );
}
if( dMirrorMoveR < 1.0
&& true == MoverParameters->Doors.instances[side::right].open_permit ) {
dMirrorMoveR = std::min(
1.0,
dMirrorMoveR + 1.0 * dt1 );
}
}
// compartment lights
// if the vehicle has a controller, we base the light state on state of the controller otherwise we check the vehicle itself
if( ctOwner != nullptr ? SectionLightsActive != MoverParameters->CompartmentLights.is_active :
Mechanik != nullptr ? SectionLightsActive != MoverParameters->CompartmentLights.is_active :
SectionLightsActive ) { // without controller switch the lights off
toggle_lights();
}
if (MoverParameters->InactiveCabPantsCheck)
{
pants_up();
MoverParameters->InactiveCabPantsCheck = false;
}
if( MoverParameters->LoadStatus ) {
LoadUpdate(); // zmiana modelu ładunku
}
update_exchange( dt );
return true; // Ra: chyba tak?
}
void TDynamicObject::pants_up()
{
TDynamicObject *d = this;
bool isAnyPantUp = false;
while (d) {
for (auto &item : d->MoverParameters->Pantographs)
{
isAnyPantUp |= item.is_active;
}
d = d->Next(4); // pozostałe też
}
d = Prev(4);
while (d) {
for (auto &item : d->MoverParameters->Pantographs)
{
isAnyPantUp |= item.is_active;
}
d = d->Prev(4); // w drugą stronę też
}
if (isAnyPantUp)
{
d = this;
while (d) {
d->MoverParameters->OperatePantographValve(end::front, operation_t::enable, range_t::local);
d->MoverParameters->OperatePantographValve(end::rear, operation_t::enable, range_t::local);
d = d->Next(4); // pozostałe też
}
d = Prev(4);
while (d) {
d->MoverParameters->OperatePantographValve(end::front, operation_t::enable, range_t::local);
d->MoverParameters->OperatePantographValve(end::rear, operation_t::enable, range_t::local);
d = d->Prev(4); // w drugą stronę też
}
}
}
glm::dvec3 TDynamicObject::get_future_movement() const
{
return m_future_movement;
}
void TDynamicObject::move_set(double distance)
{
TDynamicObject *d = this;
while( d ) {
d->Move( distance * d->DirectionGet() );
d = d->Next(); // pozostałe też
}
d = Prev();
while( d ) {
d->Move( distance * d->DirectionGet() );
d = d->Prev(); // w drugą stronę też
}
}
bool TDynamicObject::FastUpdate(double dt)
{
if (dt == 0.0)
return true; // Ra: pauza
double dDOMoveLen;
if (!MoverParameters->PhysicActivation)
return true; // McZapkie: wylaczanie fizyki gdy nie potrzeba
if (!bEnabled)
return false;
// NOTE: coordinate system swap
// TODO: replace with regular glm vectors
TLocation const l {
-vPosition.x,
vPosition.z,
vPosition.y };
TRotation const r {
0.0,
0.0,
modelRot.z };
// McZapkie: parametry powinny byc pobierane z toru
// ts.R=MyTrack->fRadius;
// ts.Len= std::max(MoverParameters->BDist,MoverParameters->ADist);
// ts.dHtrack=Axle1.pPosition.y-Axle0.pPosition.y;
// ts.dHrail=((Axle1.GetRoll())+(Axle0.GetRoll()))*0.5f;
// tp.Width=MyTrack->fTrackWidth;
// McZapkie-250202
// tp.friction= MyTrack->fFriction;
// tp.CategoryFlag= MyTrack->iCategoryFlag&15;
// tp.DamageFlag=MyTrack->iDamageFlag;
// tp.QualityFlag=MyTrack->iQualityFlag;
dDOMoveLen = MoverParameters->FastComputeMovement(dt, ts, tp, l, r); // ,ts,tp,tmpTraction);
// Move(dDOMoveLen);
// ResetdMoveLen();
FastMove(dDOMoveLen);
if( MoverParameters->LoadStatus ) {
LoadUpdate(); // zmiana modelu ładunku
}
update_exchange( dt );
return true; // Ra: chyba tak?
}
// McZapkie-040402: liczenie pozycji uwzgledniajac wysokosc szyn itp.
// vector3 TDynamicObject::GetPosition()
//{//Ra: pozycja pojazdu jest liczona zaraz po przesunięciu
// return vPosition;
//};
void TDynamicObject::TurnOff()
{ // wyłączenie rysowania submodeli zmiennych dla
// egemplarza pojazdu
btnOn = false;
btCoupler1.TurnOff();
btCoupler2.TurnOff();
btCPneumatic1.TurnOff();
btCPneumatic1r.TurnOff();
btCPneumatic2.TurnOff();
btCPneumatic2r.TurnOff();
btPneumatic1.TurnOff();
btPneumatic1r.TurnOff();
btPneumatic2.TurnOff();
btPneumatic2r.TurnOff();
btCCtrl1.Turn( false );
btCCtrl2.Turn( false );
btCPass1.Turn( false );
btCPass2.Turn( false );
m_endsignal12.Turn( false );
m_endsignal13.Turn( false );
m_endsignal22.Turn( false );
m_endsignal23.Turn( false );
m_endsignals1.Turn( false );
m_endsignals2.Turn( false );
m_endtab1.Turn( false );
m_endtab2.Turn( false );
m_headlamp11.TurnOff();
m_headlamp12.TurnOff();
m_headlamp13.TurnOff();
m_headlamp21.TurnOff();
m_headlamp22.TurnOff();
m_headlamp23.TurnOff();
m_highbeam12.TurnOff();
m_highbeam13.TurnOff();
m_highbeam22.TurnOff();
m_highbeam23.TurnOff();
m_headsignal12.TurnOff();
m_headsignal13.TurnOff();
m_headsignal22.TurnOff();
m_headsignal23.TurnOff();
btMechanik1.Turn( false );
btMechanik2.Turn( false );
btShutters1.Turn( false );
btShutters2.Turn( false );
};
// przeliczanie dźwięków, bo będzie słychać bez wyświetlania sektora z pojazdem
void TDynamicObject::RenderSounds() {
if( false == simulation::is_ready ) { return; }
if( Global.iPause != 0 ) { return; }
if( m_startjoltplayed
&& ( std::abs(MoverParameters->AccSVBased) < 0.01
|| GetVelocity() < 0.01 ) ) {
// if the vehicle comes to a stop set the movement jolt to play when it starts moving again
m_startjoltplayed = false;
}
auto const dt{ Timer::GetDeltaRenderTime() };
m_powertrainsounds.render( *MoverParameters, dt );
auto volume{ 0.0 };
auto frequency{ 1.0 };
// NBMX dzwiek przetwornicy
if( MoverParameters->ConverterFlag ) {
if( MoverParameters->EngineType == TEngineType::ElectricSeriesMotor ) {
auto const voltage { std::max( MoverParameters->GetTrainsetHighVoltage(), MoverParameters->PantographVoltage ) };
if( voltage > 0.0 ) {
// NOTE: we do sound modulation here to avoid sudden jump on voltage loss
frequency = voltage / (MoverParameters->NominalVoltage * MoverParameters->RList[MoverParameters->RlistSize].Mn);
frequency *= sConverter.m_frequencyfactor + sConverter.m_frequencyoffset;
sConverter.pitch( std::clamp( frequency, 0.75, 1.25 ) ); // arbitrary limits )
}
}
else {
frequency = sConverter.m_frequencyoffset + sConverter.m_frequencyfactor * frequency;
sConverter.pitch( std::clamp( frequency, 0.75, 1.25 ) ); // arbitrary limits )
}
sConverter.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sConverter.stop();
}
// Odtworzenie dzwieku wentylatora rezystora hamowania
if (MoverParameters->BRVentilators)
{
sBRVent.play(sound_flags::exclusive | sound_flags::looping);
}
else
{
sBRVent.stop();
}
if( MoverParameters->CompressorSpeed > 0.0 ) {
// McZapkie! - dzwiek compressor.wav tylko gdy dziala sprezarka
if( MoverParameters->CompressorFlag ) {
// for compressor coupled with the diesel engine sound pitch is driven by engine revolutions
if( MoverParameters->CompressorPower == 3 ) {
// presume the compressor sound is recorded for idle revolutions
// increase the pitch according to increase of engine revolutions
auto const enginefactor {
std::clamp( // try to keep the sound pitch in semi-reasonable range
MoverParameters->EngineMaxRPM() / MoverParameters->EngineIdleRPM() * MoverParameters->EngineRPMRatio(),
0.5, 2.5 ) };
sCompressor.pitch( enginefactor );
sCompressorIdle.pitch( enginefactor );
}
if( sCompressorIdle.empty() ) {
// legacy sound path, if there's no dedicated idle sound
sCompressor.play( sound_flags::exclusive | sound_flags::looping );
}
else {
// enhanced sound path, with dedicated sound for idling compressor
if( MoverParameters->CompressorGovernorLock ) {
sCompressor.stop();
sCompressorIdle.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sCompressor.play( sound_flags::exclusive | sound_flags::looping );
sCompressorIdle.stop();
}
}
}
else {
sCompressor.stop();
sCompressorIdle.stop();
}
}
// Winger 160404 - dzwiek malej sprezarki
if( MoverParameters->PantCompFlag ) {
sSmallCompressor.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sSmallCompressor.stop();
}
// heater sound
{
auto const isdieselenginepowered { MoverParameters->EngineType == TEngineType::DieselElectric || MoverParameters->EngineType == TEngineType::DieselEngine };
if( true == MoverParameters->Heating
&& ( false == isdieselenginepowered
|| std::abs(MoverParameters->enrot) > 0.01 ) ) {
sHeater
.pitch( true == sHeater.is_combined() ?
std::abs( MoverParameters->enrot ) * 60.f * 0.01f :
1.f )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sHeater.stop();
}
}
// battery sound
if( MoverParameters->Battery ) {
m_batterysound.play( sound_flags::exclusive | sound_flags::looping );
}
else {
m_batterysound.stop();
}
// brake system and braking sounds:
// brake cylinder piston
auto const brakepressureratio { std::max( 0.0, MoverParameters->BrakePress ) / std::max( 1.0, MoverParameters->MaxBrakePress[ 3 ] ) };
if( m_lastbrakepressure != -1.f ) {
// HACK: potentially reset playback of opening bookend sounds
if( false == m_brakecylinderpistonadvance.is_playing() ) {
m_brakecylinderpistonadvance.stop();
}
if( false == m_brakecylinderpistonrecede.is_playing() ) {
m_brakecylinderpistonrecede.stop();
}
// actual sound playback
auto const quantizedratio { static_cast<int>( 15 * brakepressureratio ) };
auto const lastbrakepressureratio { std::max( 0.f, m_lastbrakepressure ) / std::max( 1.0, MoverParameters->MaxBrakePress[ 3 ] ) };
auto const quantizedratiochange { quantizedratio - static_cast<int>( 15 * lastbrakepressureratio ) };
if( quantizedratiochange > 0 ) {
m_brakecylinderpistonadvance
.pitch(
true == m_brakecylinderpistonadvance.is_combined() ?
quantizedratio * 0.01f :
m_brakecylinderpistonadvance.m_frequencyoffset + m_brakecylinderpistonadvance.m_frequencyfactor * 1.f )
.play();
}
else if( quantizedratiochange < 0 ) {
m_brakecylinderpistonrecede
.pitch( true == m_brakecylinderpistonrecede.is_combined() ?
quantizedratio * 0.01f :
m_brakecylinderpistonrecede.m_frequencyoffset + m_brakecylinderpistonrecede.m_frequencyfactor * 1.f )
.play();
}
}
// epbrake - epcompact
if (MoverParameters->BrakeSystem == TBrakeSystem::ElectroPneumatic && MoverParameters->LocHandle) {
auto const epbrakepressureratio{ std::min( std::max( 0.0, MoverParameters->LocHandle->GetCP() ) / std::max( 1.0, MoverParameters->MaxBrakePress[0] ),
m_epbrakepressurechangedectimer > -1.0f ?
std::max( MoverParameters->LocalBrakePosAEIM, MoverParameters->Hamulec->GetEDBCP() / MoverParameters->MaxBrakePress[3]) :
1.0 ) };
if ( m_lastepbrakepressure != -1.f ) {
// HACK: potentially reset playback of opening bookend sounds
if ( false == m_epbrakepressureincrease.is_playing() ) {
m_epbrakepressureincrease.stop();
}
if ( false == m_epbrakepressuredecrease.is_playing() ) {
m_epbrakepressuredecrease.stop();
}
m_epbrakepressurechangeinctimer += dt;
m_epbrakepressurechangedectimer += dt;
// actual sound playback
auto const epquantizedratio{ static_cast<int>( 50 * epbrakepressureratio) };
auto const lastepbrakepressureratio { std::max( 0.f, m_lastepbrakepressure ) / std::max( 1.0, MoverParameters->MaxBrakePress[0] ) };
auto const epquantizedratiochange { epquantizedratio - static_cast<int>( 50 * lastepbrakepressureratio ) };
if ( epquantizedratiochange > 0 && m_epbrakepressurechangeinctimer > 0.05f) {
m_epbrakepressureincrease
.pitch(
true == m_epbrakepressureincrease.is_combined() ?
epquantizedratio * 0.01f :
m_epbrakepressureincrease.m_frequencyoffset + m_epbrakepressureincrease.m_frequencyfactor * 1.f )
.play();
m_epbrakepressurechangeinctimer = 0;
}
else if ( epquantizedratiochange < 0 && m_epbrakepressurechangedectimer > 0.3f) {
m_epbrakepressuredecrease
.pitch(true == m_epbrakepressuredecrease.is_combined() ?
-epquantizedratiochange * 0.01f :
m_epbrakepressuredecrease.m_frequencyoffset + m_epbrakepressuredecrease.m_frequencyfactor * 1.f )
.play();
m_epbrakepressurechangedectimer = 0;
}
}
if ( m_epbrakepressurechangeinctimer == 0 || m_epbrakepressurechangedectimer == 0 )
m_lastepbrakepressure = std::min( MoverParameters->LocHandle->GetCP(),
MoverParameters->LocalBrakePosAEIM * std::max( 1.0, MoverParameters->MaxBrakePress[0] ) );
}
// emergency brake
if( MoverParameters->EmergencyValveFlow > 0.025 ) {
// smooth out air flow rate
m_emergencybrakeflow = m_emergencybrakeflow == 0.0 ? MoverParameters->EmergencyValveFlow : std::lerp(m_emergencybrakeflow, MoverParameters->EmergencyValveFlow, 0.1);
// scale volume based on the flow rate and on the pressure in the main pipe
auto const flowpressure { std::clamp( m_emergencybrakeflow, 0.0, 1.0 ) + std::clamp( 0.1 * MoverParameters->PipePress, 0.0, 0.5 ) };
m_emergencybrake
.pitch( m_emergencybrake.m_frequencyoffset + 1.0 * m_emergencybrake.m_frequencyfactor )
.gain( m_emergencybrake.m_amplitudeoffset + std::clamp( flowpressure, 0.0, 1.0 ) * m_emergencybrake.m_amplitudefactor )
.play( sound_flags::exclusive | sound_flags::looping );
}
else if( MoverParameters->EmergencyValveFlow < 0.015 ) {
m_emergencybrakeflow = 0.0;
m_emergencybrake.stop();
}
// air release
if( m_lastbrakepressure != -1.f ) {
// calculate rate of pressure drop in brake cylinder, once it's been initialized
auto const brakepressuredifference{ m_lastbrakepressure - MoverParameters->BrakePress };
m_brakepressurechange = std::lerp( m_brakepressurechange, brakepressuredifference / dt, 0.05f );
}
m_lastbrakepressure = MoverParameters->BrakePress;
// ensure some basic level of volume and scale it up depending on pressure in the cylinder; scale this by the air release rate
volume = rsUnbrake.m_amplitudefactor * m_brakepressurechange * ( 0.25 + 0.75 * brakepressureratio );
if( m_brakepressurechange > 0.05 && brakepressureratio > 0.05 ) {
rsUnbrake
.gain( volume )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
// don't stop the sound too abruptly
volume = std::max( 0.0, rsUnbrake.gain() - 0.5 * dt );
rsUnbrake.gain( volume );
if( volume < 0.05 ) {
rsUnbrake.stop();
}
}
// Dzwiek odluzniacza
if( MoverParameters->Hamulec->Releaser() ) {
sReleaser
.gain(
std::clamp(
MoverParameters->BrakePress * 1.25, // arbitrary multiplier
0., 1. ) )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sReleaser.stop();
}
// slipping wheels
if( MoverParameters->SlippingWheels ) {
if( MoverParameters->UnitBrakeForce > 100.0
&& GetVelocity() > 1.0 ) {
auto const velocitydifference{ GetVelocity() / MoverParameters->Vmax };
rsSlippery
.gain( rsSlippery.m_amplitudeoffset + rsSlippery.m_amplitudefactor * velocitydifference )
.play( sound_flags::exclusive | sound_flags::looping );
}
}
else {
rsSlippery.stop();
}
// Dzwiek piasecznicy
if( MoverParameters->SandDose ) {
sSand.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sSand.stop();
}
// brakes
auto brakeforceratio{ 0.0 };
if( //( false == mvOccupied->SlippingWheels ) &&
MoverParameters->UnitBrakeForce > 10.0
&& MoverParameters->Vel > 0.05 ) {
brakeforceratio =
std::clamp(
MoverParameters->UnitBrakeForce / std::max( 1.0, MoverParameters->BrakeForceR( 1.0, MoverParameters->Vel ) / ( MoverParameters->NAxles * std::max( 1, MoverParameters->NBpA ) ) ),
0.0, 1.0 );
rsBrake
.pitch( rsBrake.m_frequencyoffset + MoverParameters->Vel * rsBrake.m_frequencyfactor )
.gain( rsBrake.m_amplitudeoffset + std::sqrt( brakeforceratio * std::lerp( 0.4, 1.0, MoverParameters->Vel / (1 + MoverParameters->Vmax) ) ) * rsBrake.m_amplitudefactor )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
rsBrake.stop();
}
// yB: przyspieszacz (moze zadziala, ale dzwiek juz jest)
if( ( true == network::is_multiplayer() )
&& ( MoverParameters->Hamulec != nullptr ) ) {
// peeked rather than read: reading the brake unit's flags is what clears them,
// and they belong to the vehicle, not to us
network::note_brake_sound_events( name(), MoverParameters->Hamulec->PeekSoundFlag() );
}
if( true == bBrakeAcc ) {
if( true == TestFlag( MoverParameters->Hamulec->GetSoundFlag(), sf_Acc ) ) {
sBrakeAcc.play( sound_flags::exclusive );
}
}
// McZapkie-280302 - pisk mocno zacisnietych hamulcow
if( MoverParameters->Vel > 2.5 ) {
volume = rsPisk.m_amplitudeoffset + std::lerp( -1.0, 1.0, brakeforceratio ) * rsPisk.m_amplitudefactor;
if( volume > 0.075 ) {
rsPisk
.pitch(
true == rsPisk.is_combined() ?
MoverParameters->Vel * 0.01f :
rsPisk.m_frequencyoffset + rsPisk.m_frequencyfactor * 1.f )
.gain( volume )
.play( sound_flags::exclusive | sound_flags::looping );
}
}
else {
// don't stop the sound too abruptly
volume = std::max( 0.0, rsPisk.gain() - rsPisk.gain() * 2.5 * dt );
rsPisk.gain( volume );
}
if( volume < 0.05 ) {
rsPisk.stop();
}
// spring brake
if( m_springbrakesounds.state != MoverParameters->SpringBrake.Activate ) {
m_springbrakesounds.state = MoverParameters->SpringBrake.Activate;
if( m_springbrakesounds.state ) {
m_springbrakesounds.activate.play( sound_flags::exclusive );
m_springbrakesounds.release.stop();
}
else {
m_springbrakesounds.activate.stop();
m_springbrakesounds.release.play( sound_flags::exclusive );
}
}
// other sounds
// load exchange
if( MoverParameters->LoadStatus & 1 ) {
m_exchangesounds.unloading.play( sound_flags::exclusive );
}
else {
m_exchangesounds.unloading.stop();
}
if( MoverParameters->LoadStatus & 2 ) {
m_exchangesounds.loading.play( sound_flags::exclusive );
}
else {
m_exchangesounds.loading.stop();
}
// NBMX sygnal odjazdu
if( MoverParameters->Doors.has_warning ) {
auto const lowvoltagepower { MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable };
for( auto &doorspeaker : m_doorspeakers ) {
// TBD, TODO: per-location door state triggers?
if( MoverParameters->DepartureSignal
&& lowvoltagepower
/*
|| ( ( MoverParameters->DoorCloseCtrl = control::autonomous )
&& ( ( ( false == MoverParameters->DoorLeftOpened ) && ( dDoorMoveL > 0.0 ) )
|| ( ( false == MoverParameters->DoorRightOpened ) && ( dDoorMoveR > 0.0 ) ) ) )
*/
) {
// for the autonomous doors play the warning automatically whenever a door is closing
// MC: pod warunkiem ze jest zdefiniowane w chk
doorspeaker.departure_signal.play( sound_flags::exclusive | sound_flags::looping );
}
else {
doorspeaker.departure_signal.stop();
}
}
}
// announcements
{
auto const lowvoltagepower { MoverParameters->Power24vIsAvailable || MoverParameters->Power110vIsAvailable };
if( lowvoltagepower ) {
// system is powered up, can play queued announcements
if( false == m_pasystem.announcement_queue.empty()
&& false == m_pasystem.announcement.is_playing() ) {
// pull first sound from the queue
m_pasystem.announcement = m_pasystem.announcement_queue.front();
m_pasystem.announcement.owner( this );
m_pasystem.announcement.range( 0.5 * MoverParameters->Dim.L * -1 );
if( m_pasystem.soundproofing ) {
if( !m_pasystem.announcement.soundproofing() ) {
m_pasystem.announcement.soundproofing() = m_pasystem.soundproofing;
}
}
m_pasystem.announcement.play();
m_pasystem.announcement_queue.pop_front();
}
}
else {
m_pasystem.announcement.stop();
m_pasystem.announcement_queue.clear();
}
}
// NBMX Obsluga drzwi, MC: zuniwersalnione
std::array<side, 2> const sides { side::right, side::left };
for( auto const side : sides ) {
auto const &door { MoverParameters->Doors.instances[ side ] };
if( true == door.is_opening ) {
// door sounds
if( false == door.step_unfolding // no wait if no doorstep
|| MoverParameters->Doors.step_type == 2 ) { // no wait for rotating doorstep
if( door.position < 0.5f ) { // safety measure, to keep slightly too short sounds from repeating
for( auto &doorsounds : m_doorsounds ) {
if( doorsounds.placement == side ) {
// determine left side doors from their offset
doorsounds.rsDoorOpen.play( sound_flags::exclusive );
doorsounds.rsDoorClose.stop();
}
}
}
}
}
if( true == door.is_closing ) {
// door sounds can start playing before the door begins moving but shouldn't cease once the door closes
if( door.position > 0.f ) {
for( auto &doorsounds : m_doorsounds ) {
if( doorsounds.placement == side ) {
// determine left side doors from their offset
doorsounds.rsDoorClose.play( sound_flags::exclusive );
doorsounds.rsDoorOpen.stop();
}
}
}
}
// doorstep sounds
if( door.step_unfolding ) {
for( auto &doorsounds : m_doorsounds ) {
if( doorsounds.placement == side ) {
doorsounds.step_open.play( sound_flags::exclusive );
doorsounds.step_close.stop();
}
}
}
if( door.step_folding ) {
if( door.step_position < 1.0f ) { // sanity check, the vehicles may keep the doorstep unfolded until the door close
for( auto &doorsounds : m_doorsounds ) {
if( doorsounds.placement == side ) {
// determine left side doors from their offset
doorsounds.step_close.play( sound_flags::exclusive );
doorsounds.step_open.stop();
}
}
}
}
}
// door locks
if( MoverParameters->Doors.is_locked != m_doorlocks ) {
// toggle state of the locks...
m_doorlocks = !m_doorlocks;
// ...and play relevant sounds
for( auto &door : m_doorsounds ) {
if( m_doorlocks ) {
door.lock.play( sound_flags::exclusive );
}
else {
door.unlock.play( sound_flags::exclusive );
}
}
}
// 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 ?
MoverParameters->EmergencyBrakeWarningSignal :
0 )
| MoverParameters->WarningSignal };
if( TestFlag( warningsignal, 1 ) ) {
sHorn1.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sHorn1.stop();
}
if( TestFlag( warningsignal, 2 ) ) {
sHorn2.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sHorn2.stop();
}
if( TestFlag( warningsignal, 4 ) ) {
sHorn3.play( sound_flags::exclusive | sound_flags::looping );
}
else {
sHorn3.stop();
}
}
// szum w czasie jazdy
if( GetVelocity() > 0.5
#ifdef EU07_SOUND_BOGIESOUNDS
&& false == m_bogiesounds.empty()
#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)
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
false ) ) {
#ifdef EU07_SOUND_BOGIESOUNDS
auto const &bogiesound { m_bogiesounds.front() };
#else
auto const &bogiesound { m_outernoise };
#endif
// frequency calculation
auto const normalizer { (
true == bogiesound.is_combined() ?
MoverParameters->Vmax * 0.01f :
1.f ) };
frequency =
bogiesound.m_frequencyoffset
+ bogiesound.m_frequencyfactor * MoverParameters->Vel * normalizer;
// volume calculation
volume =
bogiesound.m_amplitudeoffset
+ bogiesound.m_amplitudefactor * MoverParameters->Vel;
if( brakeforceratio > 0.0 ) {
// hamulce wzmagaja halas
volume *= 1 + 0.125 * brakeforceratio;
}
// scale volume by track quality
// TODO: track quality and/or environment factors as separate subroutine
volume *=
std::lerp(
0.8, 1.2,
std::clamp(
MyTrack->iQualityFlag / 20.0,
0.0, 1.0 ) );
// for single sample sounds muffle the playback at low speeds
if( false == bogiesound.is_combined() ) {
volume *=
std::lerp(
0.0, 1.0,
std::clamp(
MoverParameters->Vel / 40.0,
0.0, 1.0 ) );
}
switch( MyTrack->eEnvironment ) {
case e_tunnel: {
volume *= 1.3;
break;
}
case e_bridge: {
volume *= 1.5;
break;
}
default: {
break;
}
}
if( volume > 0.05 ) {
// apply calculated parameters to all motor instances
#ifdef EU07_SOUND_BOGIESOUNDS
for( auto &bogiesound : m_bogiesounds ) {
bogiesound
.pitch( frequency ) // arbitrary limits to prevent the pitch going out of whack
.gain( volume )
.play( sound_flags::exclusive | sound_flags::looping );
}
#else
m_outernoise
.pitch( frequency ) // arbitrary limits to prevent the pitch going out of whack
.gain( volume )
.play( sound_flags::exclusive | sound_flags::looping );
#endif
}
else {
// stop all noise instances
#ifdef EU07_SOUND_BOGIESOUNDS
for( auto &bogiesound : m_bogiesounds ) {
bogiesound.stop();
}
#else
m_outernoise.stop();
#endif
}
}
else {
// don't play the optional ending sound if the listener switches views
#ifdef EU07_SOUND_BOGIESOUNDS
for( auto &bogiesound : m_bogiesounds ) {
bogiesound.stop( false == FreeFlyModeFlag );
}
#else
m_outernoise.stop( false == FreeFlyModeFlag );
#endif
}
// flat spot sound
if( MoverParameters->CategoryFlag == 1 ) {
// trains only
if( GetVelocity() > 1.0
&& MoverParameters->WheelFlat > 5.0 ) {
m_wheelflat
.pitch( m_wheelflat.m_frequencyoffset + std::abs( MoverParameters->nrot ) * m_wheelflat.m_frequencyfactor )
.gain( m_wheelflat.m_amplitudeoffset + m_wheelflat.m_amplitudefactor * ( ( 1.0 + MoverParameters->Vel / MoverParameters->Vmax + std::clamp( MoverParameters->WheelFlat / 60.0, 0.0, 1.0 ) ) / 3.0 ) )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
m_wheelflat.stop();
}
}
// youBy: dzwiek ostrych lukow i ciasnych zwrotek
if( MoverParameters->Vel > 5.0
&& ts.R * ts.R > 1.0
&& std::abs(ts.R) < 15000.0 ) {
// scale volume with curve radius and vehicle speed
volume =
std::abs( MoverParameters->AccN ) // * MoverParameters->AccN
* std::lerp(
0.5, 1.0,
std::clamp(
MoverParameters->Vel / 40.0,
0.0, 1.0 ) )
* ( MyTrack->eType == tt_Switch && std::abs(ts.R) < 1500.0 ? 100.0 : 1.0 );
}
else {
volume = 0;
}
if( volume > 0.05 ) {
rscurve
.pitch(
true == rscurve.is_combined() ?
MoverParameters->Vel * 0.01f :
rscurve.m_frequencyoffset + rscurve.m_frequencyfactor * 1.f )
.gain( 2.5 * volume )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
rscurve.stop();
}
// movement start jolt
if( false == m_startjoltplayed ) {
auto const velocity { GetVelocity() };
if( ( MoverParameters->V > 0.0 ? MoverParameters->AccSVBased > 0.1 : MoverParameters->AccSVBased < 0.1 )
&& velocity > 1.0
&& velocity < 15.0 ) {
m_startjolt.play( sound_flags::exclusive );
m_startjoltplayed = true;
}
}
// McZapkie! - to wazne - SoundFlag wystawiane jest przez moje moduly gdy zachodza pewne wydarzenia komentowane dzwiekiem.
// pneumatic relay
if( TestFlag( MoverParameters->SoundFlag, sound::pneumatic ) ) {
dsbPneumaticRelay
.gain(
true == TestFlag( MoverParameters->SoundFlag, sound::loud ) ?
1.0f :
0.8f )
.play();
}
// door permit
if( TestFlag( MoverParameters->SoundFlag, sound::doorpermit ) ) {
// NOTE: current implementation doesn't discern between permit for left/right side,
// which may be undesired in weird setups with doors only on one side
// TBD, TODO: rework into dedicated sound event flag for each door location instance?
for( auto &door : m_doorsounds ) {
door.permit_granted.play( sound_flags::exclusive );
}
}
// couplers
int couplerindex { 0 };
for( auto &couplersounds : m_couplersounds ) {
auto &coupler { MoverParameters->Couplers[ couplerindex ] };
if( coupler.sounds == sound::none ) {
++couplerindex;
continue;
}
if( true == TestFlag( coupler.sounds, sound::bufferclash ) ) {
// zderzaki uderzaja o siebie
if( true == TestFlag( coupler.sounds, sound::loud ) ) {
// loud clash
if( false == couplersounds.dsbBufferClamp_loud.empty() ) {
// dedicated sound for loud clash
couplersounds.dsbBufferClamp_loud
.gain( 1.f )
.play( sound_flags::exclusive );
}
else {
// fallback on the standard sound
couplersounds.dsbBufferClamp
.gain( 1.f )
.play( sound_flags::exclusive );
}
}
else {
// basic clash
couplersounds.dsbBufferClamp
.gain( 1.f )
.play( sound_flags::exclusive );
}
}
if( true == TestFlag( coupler.sounds, sound::couplerstretch ) ) {
// sprzegi sie rozciagaja
if( true == TestFlag( coupler.sounds, sound::loud ) ) {
// loud stretch
if( false == couplersounds.dsbCouplerStretch_loud.empty() ) {
// dedicated sound for loud stretch
couplersounds.dsbCouplerStretch_loud
.gain( 1.f )
.play( sound_flags::exclusive );
}
else {
// fallback on the standard sound
couplersounds.dsbCouplerStretch
.gain( 1.f )
.play( sound_flags::exclusive );
}
}
else {
// basic clash
couplersounds.dsbCouplerStretch
.gain( 1.f )
.play( sound_flags::exclusive );
}
}
// attach/detach sounds
if( ( coupler.sounds & sound::detach ) == 0 ) {
// potentially added some couplings
if( ( coupler.sounds & sound::attachcoupler ) != 0 ) {
couplersounds.attach_coupler.play();
}
if( ( coupler.sounds & sound::attachbrakehose ) != 0 ) {
couplersounds.attach_brakehose.play();
}
if( ( coupler.sounds & sound::attachmainhose ) != 0 ) {
couplersounds.attach_mainhose.play();
}
if( ( coupler.sounds & sound::attachcontrol ) != 0 ) {
couplersounds.attach_control.play();
}
if( ( coupler.sounds & sound::attachgangway ) != 0 ) {
couplersounds.attach_gangway.play();
}
if( ( coupler.sounds & sound::attachheating ) != 0 ) {
couplersounds.attach_heating.play();
}
}
else {
// potentially removed some couplings
if( ( coupler.sounds & sound::attachcoupler ) != 0 ) {
couplersounds.detach_coupler.play();
}
if( ( coupler.sounds & sound::attachbrakehose ) != 0 ) {
couplersounds.detach_brakehose.play();
}
if( ( coupler.sounds & sound::attachmainhose ) != 0 ) {
couplersounds.detach_mainhose.play();
}
if( ( coupler.sounds & sound::attachcontrol ) != 0 ) {
couplersounds.detach_control.play();
}
if( ( coupler.sounds & sound::attachgangway ) != 0 ) {
couplersounds.detach_gangway.play();
}
if( ( coupler.sounds & sound::attachheating ) != 0 ) {
couplersounds.detach_heating.play();
}
}
if( true == TestFlag( coupler.sounds, sound::attachadapter ) ) {
couplersounds.dsbAdapterAttach.play();
}
if( true == TestFlag( coupler.sounds, sound::removeadapter ) ) {
couplersounds.dsbAdapterRemove.play();
}
++couplerindex;
coupler.sounds = 0;
}
if( true == network::is_multiplayer() ) {
// relays clicking and air hissing are set and cleared inside a single frame, so
// they have to be picked up here to have any chance of reaching the other peers
network::note_sound_events( name(), MoverParameters->SoundFlag );
}
MoverParameters->SoundFlag = 0;
// McZapkie! - koniec obslugi dzwiekow z mover.pas
// special events
if( MoverParameters->EventFlag ) {
// McZapkie: w razie wykolejenia
if( true == TestFlag( MoverParameters->DamageFlag, dtrain_out ) ) {
rsDerailment.play( sound_flags::exclusive );
}
MoverParameters->EventFlag = false;
}
}
// calculates distance between event-starting axle and front of the vehicle
double
TDynamicObject::tracing_offset() const {
auto const axletoend{ ( GetLength() - fAxleDist ) * 0.5 };
return iAxleFirst ? axletoend : axletoend + iDirection * fAxleDist;
}
// TODO: compute and cache radius during vehicle initialization
double
TDynamicObject::radius() const {
glm::vec3 diagonal(
static_cast<float>( MoverParameters->Dim.L ),
static_cast<float>( MoverParameters->Dim.H ),
static_cast<float>( MoverParameters->Dim.W ) );
return glm::length( diagonal ) * 0.5f;
}
// McZapkie-250202
// wczytywanie pliku z danymi multimedialnymi (dzwieki)
void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string const &ReplacableSkin ) {
rTypeName = TypeName;
rReplacableSkin = ReplacableSkin;
Global.asCurrentDynamicPath = asBaseDir;
std::string asFileName = asBaseDir + TypeName + ".mmd";
std::string asAnimName;
bool Stop_InternalData = false;
pants = nullptr; // wskaźnik pierwszego obiektu animującego dla pantografów
wipers = nullptr; // wskaznik pierwszego obiektu animujacego dla wycieraczek
{
// preliminary check whether the file exists
cParser parser( TypeName + ".mmd", cParser::buffer_FILE, asBaseDir );
if( false == parser.ok() ) {
ErrorLog( "Failed to load appearance data for vehicle " + MoverParameters->Name );
return;
}
}
// use #include wrapper to access the appearance data file
// this allows us to provide the file content with user-defined parameters
cParser parser(
"include " + TypeName + ".mmd"
+ " " + asName // (p1)
+ " " + TypeName // (p2)
+ " " + ReplacableSkin // (p3)
+ " end",
cParser::buffer_TEXT,
asBaseDir, true, std::vector<std::string>(), true );
std::string token;
do {
token = "";
parser.getTokens(); parser >> token;
if( token == "models:" ) {
// modele i podmodele
m_materialdata.multi_textures = 0; // czy jest wiele tekstur wymiennych?
parser.getTokens();
parser >> asModel;
replace_slashes( asModel );
if( asModel.back() == '#' ) // Ra 2015-01: nie podoba mi siê to
{ // model wymaga wielu tekstur wymiennych
m_materialdata.multi_textures = 1;
asModel.erase( asModel.length() - 1 );
}
// name can contain leading slash, erase it to avoid creation of double slashes when the name is combined with current directory
erase_leading_slashes( asModel );
/*
// never really used, may as well get rid of it
std::size_t i = asModel.find( ',' );
if ( i != std::string::npos )
{ // Ra 2015-01: może szukać przecinka w nazwie modelu, a po przecinku była by liczba tekstur?
if( i < asModel.length() ) {
m_materialdata.multi_textures = asModel[ i + 1 ] - '0';
}
m_materialdata.multi_textures = std::clamp( m_materialdata.multi_textures, 0, 1 ); // na razie ustawiamy na 1
}
*/
asModel = asBaseDir + asModel; // McZapkie 2002-07-20: dynamics maja swoje modele w dynamics/basedir
Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/...
mdModel = TModelsManager::GetModel(asModel, true);
if (ReplacableSkin != "none") {
m_materialdata.assign( ReplacableSkin );
}
Global.asCurrentTexturePath = paths::textures; // z powrotem defaultowa sciezka do tekstur
do {
token = "";
parser.getTokens(); parser >> token;
if( token == "animations:" ) {
// Ra: ustawienie ilości poszczególnych animacji - musi być jako pierwsze, inaczej ilości będą domyślne
/*
if( nullptr == pAnimations )
*/
if( true == pAnimations.empty() )
{ // jeśli nie ma jeszcze tabeli animacji, można odczytać nowe ilości
int co = 0;
iAnimations = 0;
int ile;
do
{ // kolejne liczby to ilość animacj, -1 to znacznik końca
ile = -1;
parser.getTokens( 1, false );
parser >> ile; // ilość danego typu animacji
if (ile >= 0)
{
iAnimType[co] = ile; // zapamiętanie
iAnimations += ile; // ogólna ilość animacji
}
else {
iAnimType[co] = 0;
}
++co;
} while ( ile >= 0 && co < ANIM_TYPES ); //-1 to znacznik końca
pAnimations.resize( iAnimations );
int i, j, k = 0, sm = 0;
for (j = 0; j < ANIM_TYPES; ++j) // petla po wszystkich wpisach w animations
for (i = 0; i < iAnimType[j]; ++i) // petla iteruje sie tyle razy ile mamy wpisane w animations
{
if (j == ANIM_PANTS) // zliczamy poprzednie animacje
if (!pants)
if (iAnimType[ANIM_PANTS]) // o ile jakieś pantografy są (a domyślnie są)
pants = &pAnimations[k]; // zapamiętanie na potrzeby wyszukania submodeli
if (j == ANIM_WIPERS)
if (!wipers)
if (iAnimType[ANIM_WIPERS])
wipers = &pAnimations[k];
pAnimations[k].iShift = sm; // przesunięcie do przydzielenia wskaźnika
sm += pAnimations[k++].TypeSet(j, *MoverParameters); // ustawienie typu animacji i zliczanie tablicowanych submodeli
}
if (sm) // o ile są bardziej złożone animacje
{
pAnimated = new TSubModel *[sm]; // tabela na animowane submodele
for (k = 0; k < iAnimations; ++k)
pAnimations[k].smElement = pAnimated + pAnimations[k].iShift; // przydzielenie wskaźnika do tabelki
}
}
}
else if(token == "lowpolyinterior:") {
// ABu: wnetrze lowpoly
parser.getTokens();
parser >> asModel;
replace_slashes( asModel );
erase_leading_slashes( asModel );
asModel = asBaseDir + asModel; // McZapkie-200702 - dynamics maja swoje modele w dynamic/basedir
Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/...
mdLowPolyInt = TModelsManager::GetModel(asModel, true);
}
else if(token == "coupleradapter:") {
// coupling adapter data
parser.getTokens( 3 );
parser
>> m_coupleradapter.model
>> m_coupleradapter.position.x
>> m_coupleradapter.position.y;
replace_slashes( m_coupleradapter.model );
erase_leading_slashes( m_coupleradapter.model );
}
else if(token == "attachments:") {
// additional 3d models attached to main body
// content provided as a series of values together enclosed in "{}"
// each value is a name of additional 3d model
// value can be optionally set of values enclosed in "[]" in which case one value will be picked randomly
// TBD: reconsider something more yaml-compliant and/or ability to define offset and rotation
while( (token = deserialize_random_set(parser)) != ""
&& token != "}" ) {
if( token == "{" ) { continue; }
replace_slashes( token );
Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/...
auto *attachmentmodel { TModelsManager::GetModel( asBaseDir + token, true ) };
if( attachmentmodel != nullptr ) {
mdAttachments.emplace_back( attachmentmodel );
}
}
}
else if(token == "loads:") {
// default load visualization models overrides
// content provided as "key: value" pairs together enclosed in "{}"
// value can be optionally set of values enclosed in "[]" in which case one value will be picked randomly
while( (token = parser.getToken<std::string>()) != ""
&& token != "}" ) {
if( token.back() == ':' ) {
auto loadmodel { deserialize_random_set( parser ) };
replace_slashes( loadmodel );
LoadModelOverrides.emplace( token.erase( token.size() - 1 ), loadmodel );
}
}
}
else if( token == "brakemode:" ) {
// Ra 15-01: gałka nastawy hamulca
parser.getTokens();
parser >> asAnimName;
smBrakeMode = GetSubmodelFromName( mdModel, asAnimName );
// jeszcze wczytać kąty obrotu dla poszczególnych ustawień
}
else if( token == "loadmode:" ) {
// Ra 15-01: gałka nastawy hamulca
parser.getTokens();
parser >> asAnimName;
smLoadMode = GetSubmodelFromName( mdModel, asAnimName );
// jeszcze wczytać kąty obrotu dla poszczególnych ustawień
}
else if (token == "animwheelprefix:") {
// prefiks kręcących się kół
int i, k, m;
unsigned int j;
parser.getTokens( 1, false ); parser >> token;
for (i = 0; i < iAnimType[ANIM_WHEELS]; ++i) // liczba osi
{ // McZapkie-050402: wyszukiwanie kol o nazwie str*
asAnimName = token + std::to_string(i + 1);
pAnimations[i].smAnimated = GetSubmodelFromName( mdModel, asAnimName );
if (pAnimations[i].smAnimated)
{ //++iAnimatedAxles;
pAnimations[i].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu
pAnimations[i].yUpdate = std::bind( &TDynamicObject::UpdateAxle, this, std::placeholders::_1 );
pAnimations[i].fMaxDist = Global.fDistanceFactor * MoverParameters->WheelDiameter * 200;
pAnimations[i].fMaxDist *= pAnimations[i].fMaxDist;
}
}
// Ra: ustawianie indeksów osi
for (i = 0; i < iAnimType[ANIM_WHEELS]; ++i) // ilość osi (zabezpieczenie przed błędami w CHK)
pAnimations[i].dWheelAngle = 1; // domyślnie wskaźnik na napędzające
i = 0;
j = 1;
k = 0;
m = 0; // numer osi; kolejny znak; ile osi danego typu; która średnica
if (MoverParameters->WheelDiameterL != MoverParameters->WheelDiameter ||
MoverParameters->WheelDiameterT != MoverParameters->WheelDiameter)
{ // obsługa różnych średnic, o ile występują
while (i < iAnimType[ANIM_WHEELS] &&
j <= MoverParameters->AxleArangement.length())
{ // wersja ze wskaźnikami jest bardziej elastyczna na nietypowe układy
if (k >= 'A' && k <= 'J') // 10 chyba maksimum?
{
pAnimations[i++].dWheelAngle = 1; // obrót osi napędzających
--k; // następna będzie albo taka sama, albo bierzemy kolejny znak
m = 2; // następujące toczne będą miały inną średnicę
}
else if (k >= '1' && k <= '9')
{
pAnimations[i++].dWheelAngle = m; // obrót osi tocznych
--k; // następna będzie albo taka sama, albo bierzemy kolejny znak
}
else
k = MoverParameters->AxleArangement[j++]; // pobranie kolejnego znaku
}
}
}
// else if (str==AnsiString("animrodprefix:")) //prefiks wiazarow dwoch
// {
// str= Parser->GetNextSymbol();
// for (int i=1; i<=2; i++)
// {//McZapkie-050402: wyszukiwanie max 2 wiazarow o nazwie str*
// asAnimName=str+i;
// smWiazary[i-1]=mdModel->GetFromName(asAnimName.c_str());
// smWiazary[i-1]->WillBeAnimated();
// }
// }
else if( token == "animpantprefix:" ) {
// Ra: pantografy po nowemu mają literki i numerki
}
// Pantografy - Winger 160204
else if( token == "animpantrd1prefix:" ) {
// prefiks ramion dolnych 1
parser.getTokens(); parser >> token;
float4x4 m; // macierz do wyliczenia pozycji i wektora ruchu pantografu
TSubModel *sm;
if (pants)
for (int i = 0; i < iAnimType[ANIM_PANTS]; i++)
{ // Winger 160204: wyszukiwanie max 2 patykow o nazwie str*
asAnimName = token + std::to_string(i + 1);
sm = GetSubmodelFromName( mdModel, asAnimName );
pants[i].smElement[0] = sm; // jak NULL, to nie będzie animowany
if (sm)
{ // w EP09 wywalało się tu z powodu NULL
sm->WillBeAnimated();
sm->ParentMatrix(&m); // pobranie macierzy transformacji
// m(3)[1]=m[3][1]+0.054; //w górę o wysokość ślizgu (na razie tak)
pants[i].fParamPants->vPos.z = m[3][0]; // przesunięcie w bok (asymetria)
pants[i].fParamPants->vPos.y = m[3][1]; // przesunięcie w górę odczytane z modelu
if ((sm = pants[i].smElement[0]->ChildGet()) != nullptr)
{ // jeśli ma potomny, można policzyć długość (odległość potomnego od osi obrotu)
m = float4x4(*sm->GetMatrix()); // wystarczyłby wskaźnik, nie trzeba kopiować
// może trzeba: pobrać macierz dolnego ramienia, wyzerować przesunięcie, przemnożyć przez macierz górnego
pants[i].fParamPants->fHoriz = -fabs(m[3][1]);
pants[i].fParamPants->fLenL1 = hypot(m[3][1], m[3][2]); // po osi OX nie potrzeba
pants[i].fParamPants->fAngleL0 = atan2(fabs(m[3][2]), fabs(m[3][1]));
// if (pants[i].fParamPants->fAngleL0<M_PI_2)
// pants[i].fParamPants->fAngleL0+=M_PI; //gdyby w odwrotną stronę wyszło
// if
// ((pants[i].fParamPants->fAngleL0<0.03)||(pants[i].fParamPants->fAngleL0>0.09))
// //normalnie ok. 0.05
// pants[i].fParamPants->fAngleL0=pants[i].fParamPants->fAngleL;
pants[i].fParamPants->fAngleL = pants[i].fParamPants->fAngleL0; // początkowy kąt dolnego ramienia
if ((sm = sm->ChildGet()) != nullptr)
{ // jeśli dalej jest ślizg, można policzyć długość górnego ramienia
m = float4x4(*sm->GetMatrix()); // wystarczyłby wskaźnik,
// nie trzeba kopiować trzeba by uwzględnić macierz dolnego ramienia, żeby uzyskać kąt do poziomu...
pants[i].fParamPants->fHoriz += fabs(m(3)[1]); // różnica długości rzutów ramion na
// płaszczyznę podstawy (jedna dodatnia, druga ujemna)
pants[i].fParamPants->fLenU1 = hypot( m[3][1], m[3][2] ); // po osi OX nie potrzeba
// pants[i].fParamPants->pantu=acos((1.22*cos(pants[i].fParamPants->fAngleL)+0.535)/1.755); //górne ramię
// pants[i].fParamPants->fAngleU0=acos((1.176289*cos(pants[i].fParamPants->fAngleL)+0.54555075)/1.724482197); //górne ramię
pants[i].fParamPants->fAngleU0 = atan2( fabs(m[3][2]), fabs(m[3][1]) ); // początkowy kąt górnego ramienia, odczytany z modelu
// if (pants[i].fParamPants->fAngleU0<M_PI_2)
// pants[i].fParamPants->fAngleU0+=M_PI; //gdyby w odwrotną stronę wyszło
// if (pants[i].fParamPants->fAngleU0<0)
// pants[i].fParamPants->fAngleU0=-pants[i].fParamPants->fAngleU0;
// if
// ((pants[i].fParamPants->fAngleU0<0.00)||(pants[i].fParamPants->fAngleU0>0.09)) //normalnie ok. 0.07
// pants[i].fParamPants->fAngleU0=acos((pants[i].fParamPants->fLenL1*cos(pants[i].fParamPants->fAngleL)+pants[i].fParamPants->fHoriz)/pants[i].fParamPants->fLenU1);
pants[i].fParamPants->fAngleU = pants[i].fParamPants->fAngleU0; // początkowy kąt
// Ra: ze względu na to, że niektóre modele pantografów są zrąbane, ich mierzenie ma obecnie ograniczony sens
sm->ParentMatrix(&m); // pobranie macierzy transformacji pivota ślizgu względem wstawienia pojazdu
float det = Det(m);
if (std::fabs(det - 1.0) < 0.001) // dopuszczamy 1 promil błędu na skalowaniu ślizgu
{ // skalowanie jest w normie, można pobrać wymiary z modelu
pants[i].fParamPants->fHeight = sm->MaxY(m); // przeliczenie maksimum wysokości wierzchołków względem macierzy
pants[i].fParamPants->fHeight -= m[3][1]; // odjęcie wysokości pivota ślizgu
pants[i].fParamPants->vPos.x = m[3][2]; // przy okazji odczytać z modelu pozycję w długości
// ErrorLog("Model OK: "+asModel+",
// height="+pants[i].fParamPants->fHeight);
// ErrorLog("Model OK: "+asModel+",
// pos.x="+pants[i].fParamPants->vPos.x);
}
else
{ // gdy ktoś przesadził ze skalowaniem
pants[i].fParamPants->fHeight = 0.0; // niech będzie odczyt z pantfactors:
ErrorLog(
"Bad model: " + asModel + ", scale of " + sm->pName + " is " + std::to_string(100.0 * det) + "%",
logtype::model );
}
}
}
}
else {
// brak ramienia
pants[ i ].fParamPants->fHeight = 0.0; // niech będzie odczyt z pantfactors:
ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model );
}
}
}
else if( token == "animpantrd2prefix:" ) {
// prefiks ramion dolnych 2
parser.getTokens(); parser >> token;
float4x4 m; // macierz do wyliczenia pozycji i wektora ruchu pantografu
TSubModel *sm;
if( pants ) {
for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) {
// Winger 160204: wyszukiwanie max 2 patykow o nazwie str*
asAnimName = token + std::to_string( i + 1 );
sm = GetSubmodelFromName( mdModel, asAnimName );
pants[ i ].smElement[ 1 ] = sm; // jak NULL, to nie będzie animowany
if( sm ) { // w EP09 wywalało się tu z powodu NULL
sm->WillBeAnimated();
if( pants[ i ].fParamPants->vPos.y == 0.0 ) {
// jeśli pierwsze ramię nie ustawiło tej wartości, próbować drugim
//!!!! docelowo zrobić niezależną animację ramion z każdej strony
m = float4x4(
*sm->GetMatrix()); // skopiowanie, bo będziemy mnożyć
m( 3 )[ 1 ] =
m[ 3 ][ 1 ] + 0.054; // w górę o wysokość ślizgu (na razie tak)
while( sm->Parent ) {
if( sm->Parent->GetMatrix() )
m = *sm->Parent->GetMatrix() * m;
sm = sm->Parent;
}
pants[ i ].fParamPants->vPos.z = m[3][0]; // przesunięcie w bok (asymetria)
pants[ i ].fParamPants->vPos.y = m[3][1]; // przesunięcie w górę odczytane z modelu
}
}
else
ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia
}
}
}
else if( token == "animpantrg1prefix:" ) {
// prefiks ramion górnych 1
parser.getTokens(); parser >> token;
if( pants ) {
for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) {
// Winger 160204: wyszukiwanie max 2 patykow o nazwie str*
asAnimName = token + std::to_string( i + 1 );
pants[ i ].smElement[ 2 ] = GetSubmodelFromName( mdModel, asAnimName );
if( pants[ i ].smElement[ 2 ] ) {
pants[ i ].smElement[ 2 ]->WillBeAnimated();
}
else {
ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia
}
}
}
}
else if( token == "animpantrg2prefix:" ) {
// prefiks ramion górnych 2
parser.getTokens(); parser >> token;
if( pants ) {
for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) {
// Winger 160204: wyszukiwanie max 2 patykow o nazwie str*
asAnimName = token + std::to_string( i + 1 );
pants[ i ].smElement[ 3 ] = GetSubmodelFromName( mdModel, asAnimName );
if( pants[ i ].smElement[ 3 ] ) {
pants[ i ].smElement[ 3 ]->WillBeAnimated();
}
else {
ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia
}
}
}
}
else if( token == "animpantslprefix:" ) {
// prefiks ślizgaczy
parser.getTokens(); parser >> token;
if( pants ) {
for( int i = 0; i < iAnimType[ ANIM_PANTS ]; i++ ) {
// Winger 160204: wyszukiwanie max 2 patykow o nazwie str*
asAnimName = token + std::to_string( i + 1 );
pants[ i ].smElement[ 4 ] = GetSubmodelFromName( mdModel, asAnimName );
if( pants[ i ].smElement[ 4 ] ) {
pants[ i ].smElement[ 4 ]->WillBeAnimated();
pants[ i ].yUpdate = std::bind( &TDynamicObject::UpdatePant, this, std::placeholders::_1 );
pants[ i ].fMaxDist = 300 * 300; // nie podnosić w większej odległości
pants[ i ].iNumber = i;
}
else {
ErrorLog( "Bad model: " + asFileName + " - missed submodel " + asAnimName, logtype::model ); // brak ramienia
}
}
}
}
else if( token == "pantfactors:" ) {
// Winger 010304:
// parametry pantografow
double pant1x, pant2x, pant1h, pant2h;
parser.getTokens( 4, false );
parser
>> pant1x
>> pant2x
>> pant1h // wysokość pierwszego ślizgu
>> pant2h;// wysokość drugiego ślizgu
if( pant1h > 0.5 ) {
pant1h = pant2h; // tu może być zbyt duża wartość
}
if (pant1x < 0 &&
pant2x > 0) // pierwsza powinna być dodatnia, a druga ujemna
{
pant1x = -pant1x;
pant2x = -pant2x;
}
if( pants ) {
for( int i = 0; i < iAnimType[ ANIM_PANTS ]; ++i ) {
// przepisanie współczynników do pantografów (na razie nie będzie lepiej)
auto &pantograph { *pants[i].fParamPants };
pantograph.fAngleL = pantograph.fAngleL0; // początkowy kąt dolnego ramienia
pantograph.fAngleU = pantograph.fAngleU0; // początkowy kąt
// pants[i].fParamPants->PantWys=1.22*sin(pants[i].fParamPants->fAngleL)+1.755*sin(pants[i].fParamPants->fAngleU);
// //wysokość początkowa
// pants[i].fParamPants->PantWys=1.176289*sin(pants[i].fParamPants->fAngleL)+1.724482197*sin(pants[i].fParamPants->fAngleU);
// //wysokość początkowa
if( pantograph.fHeight == 0.0 ) // gdy jest nieprawdopodobna wartość (np. nie znaleziony ślizg)
{ // gdy pomiary modelu nie udały się, odczyt podanych parametrów z MMD
pantograph.vPos.x =
i & 1 ?
pant2x :
pant1x;
pantograph.fHeight =
i & 1 ?
pant2h :
pant1h; // wysokość ślizgu jest zapisana w MMD
}
pantograph.PantWys =
pantograph.fLenL1 * sin( pantograph.fAngleL ) +
pantograph.fLenU1 * sin( pantograph.fAngleU ) +
pantograph.fHeight; // wysokość początkowa
// pants[i].fParamPants->vPos.y=panty-panth-pants[i].fParamPants->PantWys; //np. 4.429-0.097=4.332=~4.335
if( pantograph.vPos.y == 0.0 ) {
// crude fallback, place the pantograph(s) atop of the vehicle-sized box
pantograph.vPos.y = MoverParameters->Dim.H - pantograph.fHeight - pantograph.PantWys;
}
// pants[i].fParamPants->vPos.z=0; //niezerowe dla pantografów asymetrycznych
pantograph.PantTraction = pantograph.PantWys;
// połowa szerokości ślizgu; jest w "Power: CSW="
pantograph.fWidth = 0.5 * MoverParameters->EnginePowerSource.CollectorParameters.CSW;
// create sound emitters for the pantograph
m_pantographsounds.emplace_back();
}
}
}
else if (token == "animpistonprefix:") {
// prefiks tłoczysk - na razie uzywamy modeli pantografów
parser.getTokens(1, false); parser >> token;
for( int i = 1; i <= 2; ++i )
{
// asAnimName=str+i;
// smPatykird1[i-1]=mdModel->GetFromName(asAnimName.c_str());
// smPatykird1[i-1]->WillBeAnimated();
}
}
else if( token == "animconrodprefix:" ) {
// prefiks korbowodów - na razie używamy modeli pantografów
parser.getTokens(); parser >> token;
for( int i = 1; i <= 2; i++ )
{
// asAnimName=str+i;
// smPatykirg1[i-1]=mdModel->GetFromName(asAnimName.c_str());
// smPatykirg1[i-1]->WillBeAnimated();
}
}
else if( token == "pistonfactors:" ) {
// Ra: parametry
// silnika parowego
// (tłoka)
/* //Ra: tymczasowo wyłączone ze względu na porządkowanie animacji
pantografów
pant1x=Parser->GetNextSymbol().ToDouble(); //kąt przesunięcia
dla
pierwszego tłoka
pant2x=Parser->GetNextSymbol().ToDouble(); //kąt przesunięcia
dla
drugiego tłoka
panty=Parser->GetNextSymbol().ToDouble(); //długość korby (r)
panth=Parser->GetNextSymbol().ToDouble(); //długoś korbowodu
(k)
*/
MoverParameters->EnginePowerSource.PowerType =
TPowerType::SteamPower; // Ra: po chamsku, ale z CHK nie działa
}
else if( token == "animreturnprefix:" ) {
// prefiks drążka mimośrodowego - na razie używamy modeli pantografów
parser.getTokens(1, false); parser >> token;
for( int i = 1; i <= 2; i++ )
{
// asAnimName=str+i;
// smPatykird2[i-1]=mdModel->GetFromName(asAnimName.c_str());
// smPatykird2[i-1]->WillBeAnimated();
}
}
else if (token == "animexplinkprefix:"){ // animreturnprefix:
// prefiks jarzma - na razie używamy modeli pantografów
parser.getTokens(1, false); parser >> token;
for( int i = 1; i <= 2; i++ )
{
// asAnimName=str+i;
// smPatykirg2[i-1]=mdModel->GetFromName(asAnimName.c_str());
// smPatykirg2[i-1]->WillBeAnimated();
}
}
else if( token == "animpendulumprefix:" ) {
// prefiks wahaczy
parser.getTokens(); parser >> token;
asAnimName = "";
for( int i = 1; i <= 4; ++i ) {
// McZapkie-050402: wyszukiwanie max 4 wahaczy o nazwie str*
asAnimName = token + std::to_string( i );
smWahacze[ i - 1 ] = GetSubmodelFromName( mdModel, asAnimName );
if( smWahacze[ i - 1 ] ) {
smWahacze[ i - 1 ]->WillBeAnimated();
}
}
parser.getTokens(); parser >> token;
if( token == "pendulumamplitude:" ) {
parser.getTokens( 1, false );
parser >> fWahaczeAmp;
}
}
else if( token == "animdoorprefix:" ) {
// nazwa animowanych drzwi
int i, j;
parser.getTokens(1, false); parser >> token;
for (i = 0, j = 0; i < ANIM_DOORS; ++i)
j += iAnimType[i]; // zliczanie wcześniejszych animacji
for (i = 0; i < iAnimType[ANIM_DOORS]; ++i) // liczba drzwi
{ // NBMX wrzesien 2003: wyszukiwanie drzwi o nazwie str*
// ustalenie submodelu
asAnimName = token + std::to_string(i + 1);
pAnimations[ i + j ].smAnimated = GetSubmodelFromName( mdModel, asAnimName );
if (pAnimations[i + j].smAnimated)
{ //++iAnimatedDoors;
pAnimations[i + j].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu
switch( MoverParameters->Doors.type )
{ // od razu zapinamy potrzebny typ animacji
case 1:
pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorTranslate, this, std::placeholders::_1 );
break;
case 2:
pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorRotate, this, std::placeholders::_1 );
break;
case 3:
pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorFold, this, std::placeholders::_1 );
break; // obrót 3 kolejnych submodeli
case 4:
pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateDoorPlug, this, std::placeholders::_1 );
break;
default:
break;
}
pAnimations[i + j].iNumber = i + 1; // parzyste działają inaczej niż nieparzyste
pAnimations[i + j].fMaxDist = 300 * 300; // drzwi to z daleka widać
/*
// NOTE: no longer used
pAnimations[i + j].fSpeed = Random(150); // oryginalny koncept z DoorSpeedFactor
pAnimations[i + j].fSpeed = (pAnimations[i + j].fSpeed + 100) / 100;
*/
}
}
}
else if( token == "animstepprefix:" ) {
// animated doorstep submodel name prefix
int i, j;
parser.getTokens(1, false); parser >> token;
for (i = 0, j = 0; i < ANIM_DOORSTEPS; ++i)
j += iAnimType[i]; // zliczanie wcześniejszych animacji
for (i = 0; i < iAnimType[ANIM_DOORSTEPS]; ++i) // liczba drzwi
{ // NBMX wrzesien 2003: wyszukiwanie drzwi o nazwie str*
// ustalenie submodelu
asAnimName = token + std::to_string(i + 1);
pAnimations[i + j].smAnimated = GetSubmodelFromName( mdModel, asAnimName );
if (pAnimations[i + j].smAnimated)
{ //++iAnimatedDoors;
pAnimations[i + j].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu
switch( MoverParameters->Doors.step_type )
{ // od razu zapinamy potrzebny typ animacji
case 1: // shift
pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdatePlatformTranslate, this, std::placeholders::_1 );
break;
case 2: // rotate
pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdatePlatformRotate, this, std::placeholders::_1 );
break;
default:
break;
}
pAnimations[i + j].iNumber = i + 1; // parzyste działają inaczej niż nieparzyste
pAnimations[i + j].fMaxDist = 150 * 150; // drzwi to z daleka widać
/*
// NOTE: no longer used
pAnimations[i + j].fSpeed = Random(150); // oryginalny koncept z DoorSpeedFactor
pAnimations[i + j].fSpeed = (pAnimations[i + j].fSpeed + 100) / 100;
*/
}
}
}
else if( token == "animmirrorprefix:" ) {
// animated mirror submodel name prefix
int i, j;
parser.getTokens( 1, false ); parser >> token;
for( i = 0, j = 0; i < ANIM_MIRRORS; ++i )
j += iAnimType[ i ]; // zliczanie wcześniejszych animacji
for( i = 0; i < iAnimType[ ANIM_MIRRORS ]; ++i ) // liczba drzwi
{ // NBMX wrzesien 2003: wyszukiwanie drzwi o nazwie str*
// ustalenie submodelu
asAnimName = token + std::to_string( i + 1 );
pAnimations[ i + j ].smAnimated = GetSubmodelFromName( mdModel, asAnimName );
if( pAnimations[ i + j ].smAnimated ) {
pAnimations[ i + j ].smAnimated->WillBeAnimated(); // wyłączenie optymalizacji transformu
// od razu zapinamy potrzebny typ animacji
auto const offset { pAnimations[ i + j ].smAnimated->offset() };
pAnimations[ i + j ].yUpdate = std::bind( &TDynamicObject::UpdateMirror, this, std::placeholders::_1 );
// we don't expect more than 2-4 mirrors, so it should be safe to store submodel location (front/rear) in the higher bits
// parzyste działają inaczej niż nieparzyste
pAnimations[ i + j ].iNumber =
( ( pAnimations[ i + j ].smAnimated->offset().z > 0 ?
end::front :
end::rear ) << 4 )
+ i;
pAnimations[ i + j ].fMaxDist = 150 * 150; // drzwi to z daleka widać
/*
// NOTE: no longer used
pAnimations[i + j].fSpeed = Random(150); // oryginalny koncept z DoorSpeedFactor
pAnimations[i + j].fSpeed = (pAnimations[i + j].fSpeed + 100) / 100;
*/
}
}
}
else if (token == "animwiperprefix:")
{
parser.getTokens(1, false);
parser >> token;
TSubModel *sm;
if (wipers)
{
for (int i = 0; i < iAnimType[ANIM_WIPERS]; i++) // zebranie wszystkich submodeli wycieraczek
{
dWiperPos.emplace_back(0.0); // dodajemy na koniec zeby sie miejsce tam zrobilo i nie bylo invalid addressow potem
asAnimName = token + std::to_string(i + 1);
// element wycieraczki nr 1
sm = GetSubmodelFromName(mdModel, asAnimName + "_p1");
wipers[i].smElement[0] = sm;
if (sm)
{
wipers[i].smElement[0]->WillBeAnimated();
// auto const offset{wipers[i].smElement[0]->offset()};
}
// element wycieraczki nr 2
sm = GetSubmodelFromName(mdModel, asAnimName + "_p2");
wipers[i].smElement[1] = sm;
if (sm)
{
wipers[i].smElement[1]->WillBeAnimated();
// auto const offset{wipers[i].smElement[0]->offset()};
}
// element wycieraczki nr 3
sm = GetSubmodelFromName(mdModel, asAnimName + "_p3");
wipers[i].smElement[2] = sm;
if (sm)
{
wipers[i].smElement[2]->WillBeAnimated();
// auto const offset{wipers[i].smElement[0]->offset()};
}
wipers[i].yUpdate = std::bind(&TDynamicObject::UpdateWiper, this, std::placeholders::_1);
wipers[i].fMaxDist = 150 * 150;
wipers[i].iNumber = i;
}
}
}
} while( token != ""
&& token != "endmodels" );
if( false == MoverParameters->LoadAttributes.empty() ) {
// Ra: tu wczytywanie modelu ładunku jest w porządku
// bieżąca ścieżka do tekstur to dynamic/...
Global.asCurrentTexturePath = asBaseDir;
mdLoad = LoadMMediaFile_mdload( MoverParameters->LoadType.name );
// z powrotem defaultowa sciezka do tekstur
Global.asCurrentTexturePath = std::string( paths::textures );
}
} // models
else if( token == "sounds:" ) {
// dzwieki
do {
token = "";
parser.getTokens(); parser >> token;
if( token == "wheel_clatter:" ){
// polozenia osi w/m srodka pojazdu
double dSDist;
parser.getTokens( 1, false );
parser >> dSDist;
while( (token = parser.getToken<std::string>()) != ""
&& token != "end" ) {
// add another axle entry to the list
axle_sounds axle {
0,
std::atof( token.c_str() ) * -1.0, // for axle locations negative value means ahead of centre but vehicle faces +Z in 'its' space
{ sound_placement::external, static_cast<float>( dSDist ) } };
axle.clatter.deserialize( parser, sound_type::single );
axle.clatter.owner( this );
axle.clatter.offset( { 0, 0, axle.offset } );
m_axlesounds.emplace_back( axle );
}
// arrange the axles in case they're listed out of order
std::sort(
std::begin( m_axlesounds ), std::end( m_axlesounds ),
[]( axle_sounds const &Left, axle_sounds const &Right ) {
return Left.offset > Right.offset; } );
}
else if( token == "engine:"
&& MoverParameters->Power > 0 ) {
// plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest.
m_powertrainsounds.engine.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency );
m_powertrainsounds.engine.owner( this );
auto const amplitudedivisor = static_cast<float>( MoverParameters->EngineType == TEngineType::DieselEngine ? 1 :
MoverParameters->EngineType == TEngineType::DieselElectric ? 1 :
MoverParameters->nmax * 60 + MoverParameters->Power * 3 );
m_powertrainsounds.engine.m_amplitudefactor /= amplitudedivisor;
} else if (token == "fakeengine:") {
m_powertrainsounds.fake_engine.deserialize(parser, sound_type::single);
m_powertrainsounds.fake_engine.owner(this);
}
else if( token == "dieselinc:" ) {
// dzwiek przy wlazeniu na obroty woodwarda
m_powertrainsounds.engine_revving.deserialize( parser, sound_type::single, sound_parameters::range );
m_powertrainsounds.engine_revving.owner( this );
}
else if( token == "oilpump:" ) {
m_powertrainsounds.oil_pump.deserialize( parser, sound_type::single );
m_powertrainsounds.oil_pump.owner( this );
}
else if( token == "fuelpump:" ) {
m_powertrainsounds.fuel_pump.deserialize( parser, sound_type::single );
m_powertrainsounds.fuel_pump.owner( this );
}
else if( token == "waterpump:" ) {
m_powertrainsounds.water_pump.deserialize( parser, sound_type::single );
m_powertrainsounds.water_pump.owner( this );
}
else if( token == "waterheater:" ) {
m_powertrainsounds.water_heater.deserialize( parser, sound_type::single );
m_powertrainsounds.water_heater.owner( this );
}
else if( token == "tractionmotor:" && MoverParameters->Power > 0 ) {
// plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest.
sound_source motortemplate { sound_placement::external };
motortemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency );
auto const amplitudedivisor { static_cast<float>( MoverParameters->nmax * 60 + MoverParameters->Power * 3 ) };
motortemplate.m_amplitudefactor /= amplitudedivisor;
motortemplate.owner( this );
auto &motors { m_powertrainsounds.motors };
if( true == motors.empty() ) {
// fallback for cases without specified motor locations, convert sound template to a single sound source
motors.emplace_back( motortemplate );
}
else {
// apply configuration to all defined motors
for( auto &motor : motors ) {
// combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual motors
auto motoroffset { motortemplate.offset() };
motoroffset.z = motor.offset().z;
motor = motortemplate;
motor.offset( motoroffset );
// apply randomized playback start offset for each instance, to reduce potential reverb with identical nearby sources
motor.start( LocalRandom( 0.0, 1.0 ) );
}
}
}
else if( token == "tractionacmotor:" && MoverParameters->Power > 0 ) {
// plik z dzwiekiem silnika, mnozniki i ofsety amp. i czest.
sound_source motortemplate { sound_placement::external };
motortemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency );
motortemplate.owner( this );
auto &motors { m_powertrainsounds.acmotors };
if( true == motors.empty() ) {
// fallback for cases without specified motor locations, convert sound template to a single sound source
motors.emplace_back( motortemplate );
}
else {
// apply configuration to all defined motors
for( auto &motor : motors ) {
// combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual motors
auto motoroffset { motortemplate.offset() };
motoroffset.z = motor.offset().z;
motor = motortemplate;
motor.offset( motoroffset );
// apply randomized playback start offset for each instance, to reduce potential reverb with identical nearby sources
motor.start( LocalRandom( 0.0, 1.0 ) );
}
}
}
else if( token == "motorblower:" ) {
sound_source blowertemplate { sound_placement::engine };
blowertemplate.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency );
blowertemplate.owner( this );
auto const amplitudedivisor { static_cast<float>(
MoverParameters->MotorBlowers[ end::front ].speed > 0 ? MoverParameters->MotorBlowers[ end::front ].speed * MoverParameters->nmax * 60 + MoverParameters->Power * 3 :
blowertemplate.has_bookends() ? 1 : // NOTE: for motorblowers with fixed speed if the sound has defined bookends we skip revolutions-based part of frequency/volume adjustments
MoverParameters->MotorBlowers[ end::front ].speed * -1 ) };
blowertemplate.m_amplitudefactor /= amplitudedivisor;
blowertemplate.m_frequencyfactor /= amplitudedivisor;
if( true == m_powertrainsounds.motors.empty() ) {
// fallback for cases without specified motor locations, convert sound template to a single sound source
m_powertrainsounds.motorblowers.emplace_back( blowertemplate );
}
else {
// apply configuration to all defined motor blowers
for( auto &blower : m_powertrainsounds.motorblowers ) {
// combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual blowers
auto bloweroffset { blowertemplate.offset() };
bloweroffset.z = blower.offset().z;
blower = blowertemplate;
blower.offset( bloweroffset );
// apply randomized playback start offset for each instance, to reduce potential reverb with identical nearby sources
blower.start( LocalRandom( 0.0, 1.0 ) );
}
}
}
else if( token == "inverter:" ) {
// plik z dzwiekiem wentylatora, mnozniki i ofsety amp. i czest.
m_powertrainsounds.inverter.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency );
m_powertrainsounds.inverter.owner( this );
}
else if( token == "ventilator:" ) {
// plik z dzwiekiem wentylatora, mnozniki i ofsety amp. i czest.
m_powertrainsounds.rsWentylator.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude | sound_parameters::frequency );
m_powertrainsounds.rsWentylator.owner( this );
m_powertrainsounds.rsWentylator.m_amplitudefactor /= MoverParameters->RVentnmax;
m_powertrainsounds.rsWentylator.m_frequencyfactor /= MoverParameters->RVentnmax;
}
else if( token == "radiatorfan1:" ) {
// primary circuit radiator fan
m_powertrainsounds.radiator_fan.deserialize( parser, sound_type::single );
m_powertrainsounds.radiator_fan.owner( this );
}
else if( token == "radiatorfan2" ) {
// auxiliary circuit radiator fan
m_powertrainsounds.radiator_fan_aux.deserialize( parser, sound_type::single );
m_powertrainsounds.radiator_fan_aux.owner( this );
}
else if( token == "transmission:" ) {
// plik z dzwiekiem, mnozniki i ofsety amp. i czest.
// NOTE, fixed default parameters, legacy system leftover
auto &sound { m_powertrainsounds.transmission };
sound.m_amplitudefactor = 0.029;
sound.m_amplitudeoffset = 0.1;
sound.m_frequencyfactor = 0.005;
sound.m_frequencyoffset = 1.0;
sound.deserialize( parser, sound_type::single, sound_parameters::range );
sound.owner( this );
}
else if( token == "brakesound:" ) {
// hamowanie zwykle:
rsBrake.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency );
rsBrake.owner( this );
// NOTE: can't pre-calculate amplitude normalization based on max brake force, as this varies depending on vehicle speed
rsBrake.m_frequencyfactor /= 1 + MoverParameters->Vmax;
}
else if( token == "brake:" ) {
// plik z piskiem hamulca, mnozniki i ofsety amplitudy.
rsPisk.deserialize( parser, sound_type::single, sound_parameters::range | sound_parameters::amplitude );
rsPisk.owner( this );
if( rsPisk.m_amplitudefactor > 10.f ) {
// HACK: convert old style activation point threshold to the new, regular amplitude adjustment system
rsPisk.m_amplitudefactor = 1.f;
rsPisk.m_amplitudeoffset = 0.f;
}
rsPisk.m_amplitudeoffset *= ( 105.f - Random( 10.f ) ) / 100.f;
}
else if( token == "brakecylinderinc:" ) {
// brake cylinder pressure increase sounds
m_brakecylinderpistonadvance.deserialize( parser, sound_type::single );
m_brakecylinderpistonadvance.owner( this );
}
else if( token == "brakecylinderdec:" ) {
// brake cylinder pressure decrease sounds
m_brakecylinderpistonrecede.deserialize( parser, sound_type::single );
m_brakecylinderpistonrecede.owner( this );
}
else if (token == "epbrakeinc:") {
// brake cylinder pressure increase sounds
m_epbrakepressureincrease.deserialize(parser, sound_type::single);
m_epbrakepressureincrease.owner(this);
}
else if (token == "epbrakedec:") {
// brake cylinder pressure decrease sounds
m_epbrakepressuredecrease.deserialize(parser, sound_type::single);
m_epbrakepressuredecrease.owner(this);
}
else if( token == "emergencybrake:" ) {
// emergency brake sound
m_emergencybrake.deserialize( parser, sound_type::single );
m_emergencybrake.owner( this );
}
else if( token == "brakeacc:" ) {
// plik z przyspieszaczem (upust po zlapaniu hamowania)
sBrakeAcc.deserialize( parser, sound_type::single );
sBrakeAcc.owner( this );
bBrakeAcc = true;
}
else if( token == "unbrake:" ) {
// plik z piskiem hamulca, mnozniki i ofsety amplitudy.
rsUnbrake.m_amplitudefactor = 20.0f;
rsUnbrake.deserialize( parser, sound_type::single, sound_parameters::range );
rsUnbrake.owner( this );
}
// spring brake sounds
else if( token == "springbrake:" ) {
m_springbrakesounds.activate.deserialize( parser, sound_type::single );
m_springbrakesounds.activate.owner( this );
}
else if( token == "springbrakeoff:" ) {
m_springbrakesounds.release.deserialize( parser, sound_type::single );
m_springbrakesounds.release.owner( this );
}
else if( token == "derail:" ) {
// dzwiek przy wykolejeniu
rsDerailment.deserialize( parser, sound_type::single, sound_parameters::range );
rsDerailment.owner( this );
}
else if( token == "curve:" ) {
rscurve.deserialize( parser, sound_type::single, sound_parameters::range );
rscurve.owner( this );
}
else if( token == "horn1:" ) {
// pliki z trabieniem
sHorn1.deserialize( parser, sound_type::multipart, sound_parameters::range );
sHorn1.owner( this );
}
else if( token == "horn2:" ) {
// pliki z trabieniem wysokoton.
sHorn2.deserialize( parser, sound_type::multipart, sound_parameters::range );
sHorn2.owner( this );
// TBD, TODO: move horn selection to ai config file
if( iHornWarning ) {
iHornWarning = 2; // numer syreny do użycia po otrzymaniu sygnału do jazdy
}
}
else if( token == "horn3:" ) {
sHorn3.deserialize( parser, sound_type::multipart, sound_parameters::range );
sHorn3.owner( this );
}
else if( token == "pantographup:" ) {
// pliki dzwiekow pantografow
sound_source pantographup { sound_placement::external };
pantographup.deserialize( parser, sound_type::single );
pantographup.owner( this );
for( auto &pantograph : m_pantographsounds ) {
pantograph.sPantUp = pantographup;
}
}
else if( token == "pantographdown:" ) {
// pliki dzwiekow pantografow
sound_source pantographdown { sound_placement::external };
pantographdown.deserialize( parser, sound_type::single );
pantographdown.owner( this );
for( auto &pantograph : m_pantographsounds ) {
pantograph.sPantDown = pantographdown;
}
}
else if( token == "compressor:" ) {
// pliki ze sprezarka
sCompressor.deserialize( parser, sound_type::multipart, sound_parameters::range );
sCompressor.owner( this );
}
else if( token == "compressoridle:" ) {
// pliki ze sprezarka
sCompressorIdle.deserialize( parser, sound_type::multipart, sound_parameters::range );
sCompressorIdle.owner( this );
}
else if( token == "converter:" ) {
// pliki z przetwornica
sConverter.deserialize( parser, sound_type::multipart, sound_parameters::range );
sConverter.owner( this );
}
// Dzwiek wentylatora rezystora hamowania
else if (token == "brakingresistorventilator:")
{
sBRVent.deserialize(parser, sound_type::multipart, sound_parameters::range);
sBRVent.owner(this);
}
else if( token == "heater:" ) {
// train heating device
sHeater.deserialize( parser, sound_type::single );
sHeater.owner( this );
}
else if( token == "battery:" ) {
// train heating device
m_batterysound.deserialize( parser, sound_type::single );
m_batterysound.owner( this );
}
else if( token == "turbo:" ) {
// pliki z turbogeneratorem
m_powertrainsounds.engine_turbo.deserialize( parser, sound_type::multipart, sound_parameters::range );
m_powertrainsounds.engine_turbo.owner( this );
m_powertrainsounds.engine_turbo.gain( 0 );
}
else if( token == "small-compressor:" ) {
// pliki z przetwornica
sSmallCompressor.deserialize( parser, sound_type::multipart, sound_parameters::range );
sSmallCompressor.owner( this );
}
else if( token == "departuresignal:" ) {
// pliki z sygnalem odjazdu
sound_source soundtemplate { sound_placement::general, 25.f };
soundtemplate.deserialize( parser, sound_type::multipart, sound_parameters::range );
soundtemplate.owner( this );
for( auto &speaker : m_doorspeakers ) {
speaker.departure_signal = soundtemplate;
speaker.departure_signal.offset( speaker.offset );
}
}
else if( token == "dooropen:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &door : m_doorsounds ) {
// apply configuration to all defined doors, but preserve their individual offsets
auto const dooroffset { door.rsDoorOpen.offset() };
door.rsDoorOpen = soundtemplate;
door.rsDoorOpen.offset( dooroffset );
}
}
else if( token == "doorclose:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &door : m_doorsounds ) {
// apply configuration to all defined doors, but preserve their individual offsets
auto const dooroffset { door.rsDoorClose.offset() };
door.rsDoorClose = soundtemplate;
door.rsDoorClose.offset( dooroffset );
}
}
else if( token == "doorlock:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &door : m_doorsounds ) {
// apply configuration to all defined doors, but preserve their individual offsets
auto const dooroffset { door.lock.offset() };
door.lock = soundtemplate;
door.lock.offset( dooroffset );
}
}
else if( token == "doorunlock:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &door : m_doorsounds ) {
// apply configuration to all defined doors, but preserve their individual offsets
auto const dooroffset { door.unlock.offset() };
door.unlock = soundtemplate;
door.unlock.offset( dooroffset );
}
}
else if( token == "doorstepopen:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &door : m_doorsounds ) {
// apply configuration to all defined doors, but preserve their individual offsets
auto const dooroffset { door.step_open.offset() };
door.step_open = soundtemplate;
door.step_open.offset( dooroffset );
}
}
else if( token == "doorstepclose:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &door : m_doorsounds ) {
// apply configuration to all defined doors, but preserve their individual offsets
auto const dooroffset { door.step_close.offset() };
door.step_close = soundtemplate;
door.step_close.offset( dooroffset );
}
}
else if( token == "doorpermit:" ) {
sound_source soundtemplate { sound_placement::general };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &door : m_doorsounds ) {
// apply configuration to all defined doors, but preserve their individual offsets
auto const dooroffset { door.permit_granted.offset() };
door.permit_granted = soundtemplate;
door.permit_granted.offset( dooroffset );
}
}
else if( token == "unloading:" ) {
m_exchangesounds.unloading.range( MoverParameters->Dim.L * 0.5f * -1 );
m_exchangesounds.unloading.deserialize( parser, sound_type::single );
m_exchangesounds.unloading.owner( this );
}
else if( token == "loading:" ) {
m_exchangesounds.loading.range( MoverParameters->Dim.L * 0.5f * -1 );
m_exchangesounds.loading.deserialize( parser, sound_type::single );
m_exchangesounds.loading.owner( this );
}
else if( token == "sand:" ) {
sSand.deserialize( parser, sound_type::multipart, sound_parameters::range );
sSand.owner( this );
}
else if( token == "releaser:" ) {
// pliki z odluzniaczem
sReleaser.deserialize( parser, sound_type::multipart, sound_parameters::range );
sReleaser.owner( this );
}
else if( token == "outernoise:" ) {
// szum podczas jazdy:
sound_source noisetemplate{ sound_placement::external, EU07_SOUND_RUNNINGNOISECUTOFFRANGE };
noisetemplate.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, MoverParameters->Vmax );
noisetemplate.owner( this );
noisetemplate.m_amplitudefactor /= 1 + MoverParameters->Vmax;
noisetemplate.m_frequencyfactor /= 1 + MoverParameters->Vmax;
#ifdef EU07_SOUND_BOGIESOUNDS
if( true == m_bogiesounds.empty() ) {
// fallback for cases without specified noise locations, convert sound template to a single sound source
m_bogiesounds.emplace_back( noisetemplate );
}
else {
// apply configuration to all defined bogies
for( auto &bogie : m_bogiesounds ) {
// combine potential x- and y-axis offsets of the sound template with z-axis offsets of individual motors
auto bogieoffset{ noisetemplate.offset() };
bogieoffset.z = bogie.offset().z;
bogie = noisetemplate;
bogie.offset( bogieoffset );
}
}
// apply randomized playback start offset for each bogie, to reduce potential reverb with identical nearby sources
auto bogieidx( 0 );
for( auto &bogie : m_bogiesounds ) {
bogie.start( (
bogieidx % 2 ?
LocalRandom( 0.0, 30.0 ) :
LocalRandom( 50.0, 80.0 ) )
* 0.01 );
++bogieidx;
}
#else
m_outernoise = noisetemplate;
// apply randomized playback start offset, to reduce potential reverb with identical nearby sources
m_outernoise.start( Random( 0.0, 80.0 ) * 0.01 );
#endif
}
else if( token == "wheelflat:" ) {
// szum podczas jazdy:
m_wheelflat.deserialize( parser, sound_type::single, sound_parameters::frequency );
m_wheelflat.owner( this );
}
else if(token == "announcements:") {
// announcement sounds
// content provided as "key: value" pairs together enclosed in "{}"
// value can be optionally set of values enclosed in "[]" in which case one value will be picked randomly
std::unordered_map<std::string, announcement_t> const announcements = {
{ "near_stop:", announcement_t::approaching },
{ "stop:", announcement_t::current },
{ "next_stop:", announcement_t::next },
{ "destination:", announcement_t::destination },
{ "chime:", announcement_t::chime } };
while( (token = parser.getToken<std::string>()) != ""
&& token != "}" ) {
if( token.back() == ':' ) {
if( token == "soundproofing:" ) {
// custom soundproofing in format [ p1, p2, p3, p4, p5, p6 ]
parser.getTokens( 6, false, "\n\r\t ,;[]" );
std::array<float, 6> soundproofing;
parser
>> soundproofing[ 0 ]
>> soundproofing[ 1 ]
>> soundproofing[ 2 ]
>> soundproofing[ 3 ]
>> soundproofing[ 4 ]
>> soundproofing[ 5 ];
for( auto & soundproofingelement : soundproofing ) {
if( soundproofingelement != -1.f ) {
soundproofingelement = std::sqrt( std::clamp( soundproofingelement, 0.f, 1.f ) );
}
}
m_pasystem.soundproofing = soundproofing;
continue;
}
auto const lookup { announcements.find( token ) };
auto const announcementtype { (
lookup != announcements.end() ?
lookup->second :
announcement_t::idle ) };
// NOTE: we retrieve key value for all keys, not just recognized ones
if( announcementtype == announcement_t::idle ) {
token = parser.getToken<std::string>();
continue;
}
/*
auto announcementsound { deserialize_random_set( parser ) };
replace_slashes( announcementsound );
*/
sound_source soundtemplate { sound_placement::engine }; // NOTE: sound range gets filled by pa system
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
m_pasystem.announcements[ static_cast<int>( announcementtype ) ] = soundtemplate;
}
}
// set provided custom soundproofing to assigned sounds (for sounds without their own custom soundproofing)
if( m_pasystem.soundproofing ) {
for( auto &announcement : m_pasystem.announcements ) {
if( !announcement.soundproofing() ) {
announcement.soundproofing() = m_pasystem.soundproofing;
}
}
}
}
} while( token != ""
&& token != "endsounds" );
} // sounds:
else if( token == "locations:" ) {
do {
token = "";
parser.getTokens(); parser >> token;
if( token == "doors:" ) {
// a list of pairs: offset along vehicle's z-axis and sides on which the door is present; followed with "end"
while( (token = parser.getToken<std::string>()) != ""
&& token != "end" ) {
// vehicle faces +Z in 'its' space, for door locations negative value means ahead of centre
auto const offset { std::atof( token.c_str() ) * -1.f };
// recognized side specifications are "right", "left" and "both"
auto const sides { parser.getToken<std::string>() };
// NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization
door_sounds door;
// add entries to the list:
if( sides == "both"
|| sides == "left" ) {
// left...
auto const location { glm::vec3 { MoverParameters->Dim.W * 0.5f, MoverParameters->Dim.H * 0.5f, offset } };
door.placement = side::left;
door.rsDoorClose.offset( location );
door.rsDoorOpen.offset( location );
door.lock.offset( location );
door.unlock.offset( location );
door.step_close.offset( location );
door.step_open.offset( location );
door.permit_granted.offset( location );
m_doorsounds.emplace_back( door );
}
if( sides == "both"
|| sides == "right" ) {
// ...and right
auto const location { glm::vec3 { MoverParameters->Dim.W * -0.5f, 2.f, offset } };
door.placement = side::right;
door.rsDoorClose.offset( location );
door.rsDoorOpen.offset( location );
door.lock.offset( location );
door.unlock.offset( location );
door.step_close.offset( location );
door.step_open.offset( location );
door.permit_granted.offset( location );
m_doorsounds.emplace_back( door );
}
m_doorspeakers.emplace_back(
doorspeaker_sounds {
{ 0.f, 3.f, offset },
{} } );
}
}
else if( token == "tractionmotors:" ) {
// a list of offsets along vehicle's z-axis; followed with "end"
while( (token = parser.getToken<std::string>()) != ""
&& token != "end" ) {
// vehicle faces +Z in 'its' space, for motor locations negative value means ahead of centre
auto const offset { std::atof( token.c_str() ) * -1.f };
// NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization
sound_source motor { sound_placement::external }; // generic traction motor sounds
sound_source acmotor { sound_placement::external }; // inverter-specific traction motor sounds
sound_source motorblower { sound_placement::engine }; // associated motor blowers
// add entry to the list
auto const location { glm::vec3 { 0.f, 0.f, offset } };
motor.offset( location );
m_powertrainsounds.motors.emplace_back( motor );
acmotor.offset( location );
m_powertrainsounds.acmotors.emplace_back( acmotor );
motorblower.offset( location );
m_powertrainsounds.motorblowers.emplace_back( motorblower );
}
}
else if( token == "bogies:" ) {
// a list of offsets along vehicle's z-axis; followed with "end"
while( (token = parser.getToken<std::string>()) != ""
&& token != "end" ) {
// vehicle faces +Z in 'its' space, for bogie locations negative value means ahead of centre
auto const offset { std::atof( token.c_str() ) * -1.f };
// NOTE: we skip setting owner of the sounds, it'll be done during individual sound deserialization
sound_source bogienoise { sound_placement::external, EU07_SOUND_RUNNINGNOISECUTOFFRANGE };
// add entry to the list
auto const location { glm::vec3 { 0.f, 0.f, offset } };
bogienoise.offset( location );
#ifdef EU07_SOUND_BOGIESOUNDS
m_bogiesounds.emplace_back( bogienoise );
#endif
}
}
} while( token != ""
&& token != "endlocations" );
} // locations:
else if( token == "internaldata:" ) {
// dalej nie czytaj
do {
// zbieranie informacji o kabinach
token = "";
parser.getTokens(); parser >> token;
if( token == "cab0model:" ) {
parser.getTokens(); parser >> token;
if( token != "none" ) { iCabs |= 0x2; }
}
else if( token == "cab1model:" ) {
parser.getTokens(); parser >> token;
if( token != "none" ) { iCabs |= 0x1; }
}
else if( token == "cab2model:" ) {
parser.getTokens(); parser >> token;
if( token != "none" ) { iCabs |= 0x4; }
}
// engine sounds
else if( token == "ignition:" ) {
// odpalanie silnika
m_powertrainsounds.engine_ignition.deserialize( parser, sound_type::single );
m_powertrainsounds.engine_ignition.owner( this );
} else if (token == "shutdown:") {
m_powertrainsounds.engine_shutdown.deserialize(parser, sound_type::single);
m_powertrainsounds.engine_shutdown.owner(this);
} else if( token == "engageslippery:" ) {
// tarcie tarcz sprzegla:
m_powertrainsounds.rsEngageSlippery.deserialize( parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency );
m_powertrainsounds.rsEngageSlippery.owner( this );
m_powertrainsounds.rsEngageSlippery.m_frequencyfactor /= 1 + MoverParameters->nmax;
}
// dzwieki wycieraczkuf
else if (token == "wiperfrompark:")
{
sWiperFromPark.deserialize(parser, sound_type::single);
sWiperFromPark.owner(this);
}
else if (token == "wipertopark:")
{
sWiperToPark.deserialize(parser, sound_type::single);
sWiperToPark.owner(this);
}
else if (token == "retarder:") {
m_powertrainsounds.retarder.deserialize(parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency);
m_powertrainsounds.retarder.owner(this);
}
else if( token == "linebreakerclose:" ) {
m_powertrainsounds.linebreaker_close.deserialize( parser, sound_type::single );
m_powertrainsounds.linebreaker_close.owner( this );
}
else if( token == "linebreakeropen:" ) {
m_powertrainsounds.linebreaker_open.deserialize( parser, sound_type::single );
m_powertrainsounds.linebreaker_open.owner( this );
}
else if( token == "relay:" ) {
// styczniki itp:
m_powertrainsounds.motor_relay.deserialize( parser, sound_type::single );
m_powertrainsounds.motor_relay.owner( this );
}
else if( token == "shuntfield:" ) {
// styczniki itp:
m_powertrainsounds.motor_shuntfield.deserialize( parser, sound_type::single );
m_powertrainsounds.motor_shuntfield.owner( this );
}
else if( token == "wejscie_na_bezoporow:" ) {
// hunter-111211: wydzielenie wejscia na bezoporowa i na drugi uklad do pliku
m_powertrainsounds.dsbWejscie_na_bezoporow.deserialize( parser, sound_type::single );
m_powertrainsounds.dsbWejscie_na_bezoporow.owner( this );
}
// przekazniki od kierunku jazdy
else if (token == "directionrelay_d:") {
// przekaznik od kierunku jazdy do przodu
sDirectionRelayD.deserialize(parser, sound_type::single);
sDirectionRelayD.owner(this);
}
else if (token == "directionrelay_r:") {
// przekaznik od kierunku jazdy do tylu
sDirectionRelayR.deserialize(parser, sound_type::single);
sDirectionRelayR.owner(this);
}
else if (token == "directionrelay_n:")
{
// przekaznik od kierunku jazdy do tylu
sDirectionRelayN.deserialize(parser, sound_type::single);
sDirectionRelayN.owner(this);
}
else if( token == "wejscie_na_drugi_uklad:" ) {
m_powertrainsounds.motor_parallel.deserialize( parser, sound_type::single );
m_powertrainsounds.motor_parallel.owner( this );
}
else if( token == "pneumaticrelay:" ) {
// wylaczniki pneumatyczne:
dsbPneumaticRelay.deserialize( parser, sound_type::single );
dsbPneumaticRelay.owner( this );
}
else if( token == "slipperysound:" ) {
// sanie:
rsSlippery.deserialize( parser, sound_type::single, sound_parameters::amplitude );
rsSlippery.owner( this );
rsSlippery.m_amplitudefactor /= 1 + MoverParameters->Vmax;
}
// coupler sounds
else if( token == "couplerattach:" ) {
sound_source soundtemplate { sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.attach_coupler = soundtemplate;
}
}
else if( token == "brakehoseattach:" ) {
// laczenie:
sound_source soundtemplate { sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.attach_brakehose = soundtemplate;
}
}
else if( token == "mainhoseattach:" ) {
// laczenie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.attach_mainhose = soundtemplate;
}
}
else if( token == "controlattach:" ) {
// laczenie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.attach_control = soundtemplate;
}
}
else if( token == "gangwayattach:" ) {
// laczenie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.attach_gangway = soundtemplate;
}
}
else if( token == "heatingattach:" ) {
// laczenie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.attach_heating = soundtemplate;
}
}
else if( token == "couplerdetach:" ) {
// rozlaczanie:
sound_source soundtemplate { sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.detach_coupler = soundtemplate;
}
}
else if( token == "brakehosedetach:" ) {
// rozlaczanie:
sound_source soundtemplate { sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.detach_brakehose = soundtemplate;
}
}
else if( token == "mainhosedetach:" ) {
// rozlaczanie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.detach_mainhose = soundtemplate;
}
}
else if( token == "controldetach:" ) {
// rozlaczanie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.detach_control = soundtemplate;
}
}
else if( token == "gangwaydetach:" ) {
// rozlaczanie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.detach_gangway = soundtemplate;
}
}
else if( token == "heatingdetach:" ) {
// rozlaczanie:
sound_source soundtemplate{ sound_placement::external };
soundtemplate.deserialize( parser, sound_type::single );
soundtemplate.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.detach_heating = soundtemplate;
}
}
else if( token == "couplerstretch:" ) {
// coupler stretching
sound_source couplerstretch { sound_placement::external };
couplerstretch.deserialize( parser, sound_type::single );
couplerstretch.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.dsbCouplerStretch = couplerstretch;
}
}
else if( token == "couplerstretch_loud:" ) {
// coupler stretching
sound_source couplerstretch { sound_placement::external };
couplerstretch.deserialize( parser, sound_type::single );
couplerstretch.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.dsbCouplerStretch_loud = couplerstretch;
}
}
else if( token == "bufferclamp:" ) {
// buffers hitting one another
sound_source bufferclash { sound_placement::external };
bufferclash.deserialize( parser, sound_type::single );
bufferclash.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.dsbBufferClamp = bufferclash;
}
}
else if( token == "bufferclamp_loud:" ) {
// buffers hitting one another
sound_source bufferclash { sound_placement::external };
bufferclash.deserialize( parser, sound_type::single );
bufferclash.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.dsbBufferClamp_loud = bufferclash;
}
}
else if( token == "coupleradapterattach:" ) {
// laczenie:
sound_source adapterattach { sound_placement::external };
adapterattach.deserialize( parser, sound_type::single );
adapterattach.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.dsbAdapterAttach = adapterattach;
}
}
else if( token == "coupleradapterremove:" ) {
// rozlaczanie:
sound_source adapterremove { sound_placement::external };
adapterremove.deserialize( parser, sound_type::single );
adapterremove.owner( this );
for( auto &couplersounds : m_couplersounds ) {
couplersounds.dsbAdapterRemove = adapterremove;
}
}
else if( token == "startjolt:" ) {
// movement start jolt
m_startjolt.deserialize( parser, sound_type::single );
m_startjolt.owner( this );
}
else if (token == "mechspring:")
{
// parametry bujania kamery:
double ks, kd;
parser.getTokens(2, false);
parser
>> ks
>> kd;
ShakeSpring.Init(ks, kd);
parser.getTokens(6, false);
parser
>> BaseShake.jolt_scale.x
>> BaseShake.jolt_scale.y
>> BaseShake.jolt_scale.z
>> BaseShake.jolt_limit
>> BaseShake.angle_scale.x
>> BaseShake.angle_scale.z;
}
else if( token == "enginespring:" ) {
parser.getTokens( 5, false );
parser
>> EngineShake.scale
>> EngineShake.fadein_offset
>> EngineShake.fadein_factor
>> EngineShake.fadeout_offset
>> EngineShake.fadeout_factor;
// offsets values are provided as rpm for convenience
EngineShake.fadein_offset /= 60.f;
EngineShake.fadeout_offset /= 60.f;
}
else if( token == "huntingspring:" ) {
parser.getTokens( 4, false );
parser
>> HuntingShake.scale
>> HuntingShake.frequency
>> HuntingShake.fadein_begin
>> HuntingShake.fadein_end;
}
else if( token == "soundproofing:" ) {
for( auto &soundproofingtable : m_soundproofing ) {
for( auto &soundproofingelement : soundproofingtable ) {
auto const value { parser.getToken<float>( false ) };
if( value != -1.f ) {
soundproofingelement = std::sqrt( std::clamp( value, 0.f, 1.f ) );
}
}
}
}
else if( token == "jointcabs:" ) {
parser.getTokens();
parser >> JointCabs;
}
else if( token == "cablight:" ) {
parser.getTokens( 3, false ); // low power light, ignore
parser.getTokens( 3, false ); // base light
parser
>> InteriorLight.r
>> InteriorLight.g
>> InteriorLight.b;
InteriorLight = glm::clamp( InteriorLight / 255.f, glm::vec3( 0.f ), glm::vec3( 1.f ) );
parser.getTokens( 3, false ); // dimmed light, ignore
}
else if( token == "pydestinationsign:" ) {
DestinationSign.deserialize( parser );
// supply vehicle folder as script path if none is provided
if( false == DestinationSign.script.empty()
&& substr_path(DestinationSign.script).empty() ) {
DestinationSign.script = asBaseDir + DestinationSign.script;
}
}
// NOTE: legacy key, now expected as optional "background:" parameter in pydestinationsign: { parameter block }
else if( token == "destinationsignbackground:" ) {
parser.getTokens();
parser >> DestinationSign.background;
}
} while( token != "" );
} // internaldata:
} while( token != "" );
if( !iAnimations ) {
// if the animations weren't defined the model is likely to be non-functional. warrants a warning.
ErrorLog( "Animations tag is missing from the .mmd file \"" + asFileName + "\"" );
}
if( ReplacableSkin != "none" ) {
// potentially set blank destination texture
DestinationSign.destination_off = DestinationFind( DestinationSign.background.empty() ? "nowhere" : DestinationSign.background );
}
// assign default samples to sound emitters which weren't included in the config file
if( TestFlag( MoverParameters->CategoryFlag, 1 ) ) {
// rail vehicles:
// engine
if( MoverParameters->Power > 0 ) {
if( true == m_powertrainsounds.dsbWejscie_na_bezoporow.empty() ) {
// hunter-111211: domyslne, gdy brak
m_powertrainsounds.dsbWejscie_na_bezoporow.deserialize( "wejscie_na_bezoporow", sound_type::single );
m_powertrainsounds.dsbWejscie_na_bezoporow.owner( this );
}
if( true == m_powertrainsounds.motor_parallel.empty() ) {
m_powertrainsounds.motor_parallel.deserialize( "wescie_na_drugi_uklad", sound_type::single );
m_powertrainsounds.motor_parallel.owner( this );
}
}
// braking sounds
if( true == rsUnbrake.empty() ) {
rsUnbrake.deserialize( "[1007]estluz", sound_type::single );
rsUnbrake.owner( this );
}
// couplers
for( auto &couplersounds : m_couplersounds ) {
if( true == couplersounds.attach_coupler.empty() ) {
couplersounds.attach_coupler.deserialize( "couplerattach_default", sound_type::single );
couplersounds.attach_coupler.owner( this );
}
if( true == couplersounds.detach_coupler.empty() ) {
couplersounds.detach_coupler.deserialize( "couplerdetach_default", sound_type::single );
couplersounds.detach_coupler.owner( this );
}
if( true == couplersounds.dsbCouplerStretch.empty() ) {
couplersounds.dsbCouplerStretch.deserialize( "couplerstretch_default", sound_type::single );
couplersounds.dsbCouplerStretch.owner( this );
}
if( true == couplersounds.dsbBufferClamp.empty() ) {
couplersounds.dsbBufferClamp.deserialize( "bufferclamp_default", sound_type::single );
couplersounds.dsbBufferClamp.owner( this );
}
}
// other sounds
if( true == m_wheelflat.empty() ) {
m_wheelflat.deserialize( "lomotpodkucia 0.23 0.0", sound_type::single, sound_parameters::frequency );
m_wheelflat.owner( this );
}
if( true == rscurve.empty() ) {
// hunter-111211: domyslne, gdy brak
rscurve.deserialize( "curve", sound_type::single );
rscurve.owner( this );
}
}
if (mdModel)
mdModel->Init(); // obrócenie modelu oraz optymalizacja, również zapisanie binarnego
if (mdLoad)
mdLoad->Init();
if (mdLowPolyInt)
mdLowPolyInt->Init();
for( auto *attachment : mdAttachments ) {
attachment->Init();
}
Global.asCurrentTexturePath = paths::textures; // kiedyś uproszczone wnętrze mieszało tekstury nieba
Global.asCurrentDynamicPath = "";
// position sound emitters which weren't defined in the config file
// engine sounds, centre of the vehicle
auto const enginelocation { glm::vec3 {0.f, MoverParameters->Dim.H * 0.5f, 0.f } };
m_powertrainsounds.position( enginelocation );
// other engine compartment sounds
auto const nullvector { glm::vec3() };
std::vector<sound_source *> enginesounds = {
&sConverter, &sBRVent, &sCompressor, &sCompressorIdle, &sSmallCompressor, &sHeater, &m_batterysound
};
for( auto sound : enginesounds ) {
if( sound->offset() == nullvector ) {
sound->offset( enginelocation );
}
}
// pantographs
if( pants != nullptr ) {
std::size_t pantographindex { 0 };
for( auto &pantographsounds : m_pantographsounds ) {
auto const pantographoffset {
glm::vec3(
0.f,
pants[ pantographindex ].fParamPants->vPos.y,
-pants[ pantographindex ].fParamPants->vPos.x ) };
pantographsounds.sPantUp.offset( pantographoffset );
pantographsounds.sPantDown.offset( pantographoffset );
++pantographindex;
}
}
// doors
std::size_t dooranimationfirstindex { 0 };
for( std::size_t i = 0; i < ANIM_DOORS; ++i ) {
// zliczanie wcześniejszych animacji
dooranimationfirstindex += iAnimType[ i ];
}
// couplers
auto const frontcoupleroffset { glm::vec3{ 0.f, 1.f, MoverParameters->Dim.L * 0.5f } };
m_couplersounds[ end::front ].attach_coupler.offset( frontcoupleroffset );
m_couplersounds[ end::front ].attach_brakehose.offset( frontcoupleroffset );
m_couplersounds[ end::front ].attach_mainhose.offset( frontcoupleroffset );
m_couplersounds[ end::front ].attach_control.offset( frontcoupleroffset );
m_couplersounds[ end::front ].attach_gangway.offset( frontcoupleroffset );
m_couplersounds[ end::front ].attach_heating.offset( frontcoupleroffset );
m_couplersounds[ end::front ].detach_coupler.offset( frontcoupleroffset );
m_couplersounds[ end::front ].detach_brakehose.offset( frontcoupleroffset );
m_couplersounds[ end::front ].detach_mainhose.offset( frontcoupleroffset );
m_couplersounds[ end::front ].detach_control.offset( frontcoupleroffset );
m_couplersounds[ end::front ].detach_gangway.offset( frontcoupleroffset );
m_couplersounds[ end::front ].detach_heating.offset( frontcoupleroffset );
m_couplersounds[ end::front ].dsbCouplerStretch.offset( frontcoupleroffset );
m_couplersounds[ end::front ].dsbCouplerStretch_loud.offset( frontcoupleroffset );
m_couplersounds[ end::front ].dsbBufferClamp.offset( frontcoupleroffset );
m_couplersounds[ end::front ].dsbBufferClamp_loud.offset( frontcoupleroffset );
m_couplersounds[ end::front ].dsbAdapterAttach.offset( frontcoupleroffset );
m_couplersounds[ end::front ].dsbAdapterRemove.offset( frontcoupleroffset );
auto const rearcoupleroffset { glm::vec3{ 0.f, 1.f, MoverParameters->Dim.L * -0.5f } };
m_couplersounds[ end::rear ].attach_coupler.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].attach_brakehose.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].attach_mainhose.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].attach_control.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].attach_gangway.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].attach_heating.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].detach_coupler.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].detach_brakehose.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].detach_mainhose.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].detach_control.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].detach_gangway.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].detach_heating.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].dsbCouplerStretch.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].dsbCouplerStretch_loud.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].dsbBufferClamp.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].dsbBufferClamp_loud.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].dsbAdapterAttach.offset( rearcoupleroffset );
m_couplersounds[ end::rear ].dsbAdapterRemove.offset( rearcoupleroffset );
}
TModel3d *
TDynamicObject::LoadMMediaFile_mdload( std::string const &Name ) const {
auto const loadname { ( Name.empty() ? "none" : Name ) };
TModel3d *loadmodel { nullptr };
// check if we don't have model override for this load type
if ( loadmodel == nullptr )
{
auto const lookup { LoadModelOverrides.find( loadname ) };
if( lookup != LoadModelOverrides.end() ) {
loadmodel = TModelsManager::GetModel( asBaseDir + lookup->second, true );
}
}
// regular routine if there's no override or it couldn't be loaded
// try first specialized version of the load model, vehiclename_loadname
if ( loadmodel == nullptr )
{
auto const specializedloadfilename { asBaseDir + MoverParameters->TypeName + "_" + loadname };
loadmodel = TModelsManager::GetModel( specializedloadfilename, true, false );
}
// try generic version of the load model next, loadname
if ( loadmodel == nullptr )
{
auto const genericloadfilename { asBaseDir + loadname };
loadmodel = TModelsManager::GetModel( genericloadfilename, true, false );
}
if ( loadmodel != nullptr )
loadmodel->GetSMRoot()->WillBeAnimated();
return loadmodel;
}
//---------------------------------------------------------------------------
void TDynamicObject::RadioStop()
{ // zatrzymanie pojazdu
if( Mechanik ) {
// o ile ktoś go prowadzi
if( MoverParameters->SecuritySystem.radiostop_available()
&& MoverParameters->Radio ) {
// jeśli pojazd ma RadioStop i jest on aktywny
// HACK cast until math types unification
glm::dvec3 pos = static_cast<glm::dvec3>(vPosition);
Mechanik->PutCommand( "Emergency_brake", 1.0, 1.0, &pos, stopRadio );
// add onscreen notification for human driver
// TODO: do it selectively for the 'local' driver once the multiplayer is in
if( false == Mechanik->AIControllFlag ) {
ui::Transcripts.AddLine( "!! RADIO-STOP !!", 0.0, 10.0, false );
}
}
}
};
//---------------------------------------------------------------------------
void TDynamicObject::Damage(char flag)
{
if (flag & 1) //różnicówka nie robi nic
{
MoverParameters->MainSwitch(false);
MoverParameters->FuseOff();
}
else
{
}
if (flag & 2) //usterka sterowania
{
MoverParameters->StLinFlag = false;
if (MoverParameters->InitialCtrlDelay<100000000)
MoverParameters->InitialCtrlDelay += 100000001;
}
else
{
if (MoverParameters->InitialCtrlDelay>100000000)
MoverParameters->InitialCtrlDelay -= 100000001;
}
if (flag & 4) //blokada przetwornicy
{
if( MoverParameters->ConverterStart != start_t::disabled ) {
MoverParameters->ConvOvldFlag = true;
}
}
else
{
}
if (flag & 8) //blokada sprezarki
{
if (MoverParameters->MinCompressor>0)
MoverParameters->MinCompressor -= 100000001;
if (MoverParameters->MaxCompressor>0)
MoverParameters->MaxCompressor -= 100000001;
}
else
{
if (MoverParameters->MinCompressor<0)
MoverParameters->MinCompressor += 100000001;
if (MoverParameters->MaxCompressor<0)
MoverParameters->MaxCompressor += 100000001;
}
if (flag & 16) //blokada wału
{
if (MoverParameters->CtrlDelay<100000000)
MoverParameters->CtrlDelay += 100000001;
if (MoverParameters->CtrlDownDelay<100000000)
MoverParameters->CtrlDownDelay += 100000001;
}
else
{
if (MoverParameters->CtrlDelay>100000000)
MoverParameters->CtrlDelay -= 100000001;
if (MoverParameters->CtrlDownDelay>100000000)
MoverParameters->CtrlDownDelay -= 100000001;
}
if (flag & 32) //hamowanie nagŁe
{
}
else
{
}
MoverParameters->EngDmgFlag = flag;
};
void TDynamicObject::SetLights() {
auto const isfrontcaboccupied { MoverParameters->CabOccupied * DirectionGet() >= 0 };
int const automaticmarkers { MoverParameters->CabActive == 0 && MoverParameters->InactiveCabFlag & activation::redmarkers
? light::redmarker_left + light::redmarker_right : 0 };
int const front { ( isfrontcaboccupied ? end::front : end::rear ) };
int const rear { 1 - front };
auto const lightpos { MoverParameters->LightsPos - 1 };
auto const frontlights { automaticmarkers > 0 ? automaticmarkers : MoverParameters->Lights[ front ][ lightpos ] };
auto const rearlights { automaticmarkers > 0 ? automaticmarkers : MoverParameters->Lights[ rear ][ lightpos ] };
auto *vehicle { GetFirstDynamic( MoverParameters->CabOccupied >= 0 ? end::front : end::rear, coupling::control ) };
while( vehicle != nullptr ) {
// set lights on given side if there's no coupling with another vehicle, turn them off otherwise
auto const *frontvehicle{(isfrontcaboccupied ? vehicle->Prev(coupling::coupler) : vehicle->Next(coupling::coupler))};
auto const *rearvehicle{(isfrontcaboccupied ? vehicle->Next(coupling::coupler) : vehicle->Prev(coupling::coupler))};
if (MoverParameters->LightsPos == 18)
{
frontvehicle = nullptr;
rearvehicle = nullptr;
}
vehicle->RaLightsSet(
frontvehicle == nullptr ? frontlights : 0,
rearvehicle == nullptr ? rearlights : 0 );
vehicle = isfrontcaboccupied ? vehicle->Next(coupling::control) : vehicle->Prev(coupling::control);
}
};
void TDynamicObject::RaLightsSet(int head, int rear)
{ // zapalenie świateł z przodu i z
// tyłu, zależne od kierunku
// pojazdu
if (!MoverParameters)
return; // może tego nie być na początku
if (rear == ( light::redmarker_left | light::redmarker_right | light::rearendsignals ) )
{ // jeśli koniec pociągu, to trzeba ustalić, czy
// jest tam czynna lokomotywa
// EN57 może nie mieć końcówek od środka członu
if (MoverParameters->Power > 1.0) // jeśli ma moc napędową
if (!MoverParameters->DirActive) // jeśli nie ma ustawionego kierunku
{ // jeśli ma zarówno światła jak i końcówki, ustalić, czy jest w stanie aktywnym
// np. lokomotywa na zimno będzie mieć końcówki a nie światła
rear = light::rearendsignals; // tablice blaszane
// trzeba to uzależnić od "załączenia baterii" w pojeździe
}
if( rear == ( light::redmarker_left | light::redmarker_right | light::rearendsignals ) ) // jeśli nadal obydwie możliwości
if( iInventory[
( iDirection ?
end::rear :
end::front ) ] & ( light::redmarker_left | light::redmarker_right ) ) {
// czy ma jakieś światła czerowone od danej strony
rear = light::redmarker_left | light::redmarker_right; // dwa światła czerwone
}
else {
rear = light::rearendsignals; // tablice blaszane
}
}
// w zależności od kierunku pojazdu w składzie
if( head >= 0 ) {
auto const vehicleend { iDirection > 0 ? end::front : end::rear };
MoverParameters->iLights[ vehicleend ] = head & iInventory[vehicleend];
bool tLeft = MoverParameters->iLights[vehicleend] & (light::auxiliary_left | light::headlight_left); // roboczo czy jakiekolwiek swiatlo z lewej jest zapalone
bool tRight = MoverParameters->iLights[vehicleend] & (light::auxiliary_right | light::headlight_right); // a tu z prawej
if (Controller == Humandriver) {
int &currentDimPos = MoverParameters->modernDimmerPosition;
auto &dimmerPositions = MoverParameters->dimPositions;
TMoverParameters::dimPosition dps = dimmerPositions[currentDimPos];
// domyślnie
HighBeamLights = false;
DimHeadlights = false;
// obsługa OFF
if (dps.isOff)
{
//MoverParameters->iLights[vehicleend] &= 0 | light::rearendsignals; // zostawiamy tylko tabliczki jesli sa
HighBeamLights = false;
DimHeadlights = false;
switch (MoverParameters->CabActive)
{
case end::front:
HeadlightsAoff = true;
break;
case end::rear:
HeadlightsBoff = true;
break;
default:
break;
}
}
else
{
switch (MoverParameters->CabActive)
{
case end::front:
HeadlightsAoff = false;
break;
case end::rear:
HeadlightsBoff = false;
break;
default:
break;
}
}
// obsługa przyciemnionych
DimHeadlights = dps.isDimmed;
// obsługa długich
if (dps.isHighBeam)
{
HighBeamLights = true;
}
}
}
if( rear >= 0 ) {
auto const vehicleend{ iDirection > 0 ? end::rear : end::front };
MoverParameters->iLights[ vehicleend ] = rear & iInventory[vehicleend];
}
};
void TDynamicObject::SetLightDimmings()
{
auto const vehicleend{iDirection > 0 ? end::front : end::rear};
if (Controller == Humandriver)
{
int &currentDimPos = MoverParameters->modernDimmerPosition;
auto &dimmerPositions = MoverParameters->dimPositions;
TMoverParameters::dimPosition dps = dimmerPositions[currentDimPos];
// domyślnie
HighBeamLights = false;
DimHeadlights = false;
// obsługa OFF
if (dps.isOff)
{
// MoverParameters->iLights[vehicleend] &= 0 | light::rearendsignals; // zostawiamy tylko tabliczki jesli sa
HighBeamLights = false;
DimHeadlights = false;
switch (vehicleend)
{
case end::front:
HeadlightsAoff = true;
break;
case end::rear:
HeadlightsBoff = true;
break;
default:
break;
}
}
else
{
switch (vehicleend)
{
case end::front:
HeadlightsAoff = false;
break;
case end::rear:
HeadlightsBoff = false;
break;
default:
break;
}
}
// obsługa przyciemnionych
DimHeadlights = dps.isDimmed;
// obsługa długich
if (dps.isHighBeam)
{
// obsługa długich
if (dps.isHighBeam)
{
HighBeamLights = true;
}
}
}
}
bool TDynamicObject::has_signal_pc1_on() const {
auto const vehicleend { iDirection > 0 ? end::front : end::rear };
auto const equippedlights { iInventory[ vehicleend ] };
auto const pattern { equippedlights & ( light::headlight_left | light::headlight_right | light::headlight_upper ) };
auto const patternfallback { equippedlights & ( light::auxiliary_left | light::auxiliary_right | light::headlight_upper ) };
auto const hasauxiliarylights { ( equippedlights & ( light::auxiliary_left | light::auxiliary_right ) ) != 0 };
auto const activelights { MoverParameters->iLights[ vehicleend ] };
return (pattern != 0 && activelights == pattern) || (hasauxiliarylights && activelights == patternfallback) ||
(pattern == 0 && patternfallback == 0 && activelights == light::rearendsignals); // pc4
}
bool TDynamicObject::has_signal_pc2_on() const {
auto const vehicleend { iDirection > 0 ? end::front : end::rear };
auto const equippedlights { iInventory[ vehicleend ] };
auto const pattern { equippedlights & ( light::redmarker_left | light::headlight_right | light::headlight_upper ) };
auto const patternfallback { equippedlights & ( light::redmarker_left | light::auxiliary_right | light::headlight_upper ) };
auto const hasauxiliarylights { ( equippedlights & ( light::auxiliary_left | light::auxiliary_right ) ) != 0 };
auto const activelights { MoverParameters->iLights[ vehicleend ] };
return activelights == pattern || (hasauxiliarylights && activelights == patternfallback);
}
bool TDynamicObject::has_signal_pc5_on() const {
auto const vehicleend { iDirection > 0 ? end::rear : end::front };
auto const equippedlights { iInventory[ vehicleend ] };
auto const pattern { equippedlights & ( light::redmarker_left | light::redmarker_right ) };
auto const patternfallback { equippedlights & light::rearendsignals };
auto const activelights { MoverParameters->iLights[ vehicleend ] };
return (pattern != 0 && activelights == pattern) || (patternfallback != 0 && activelights == patternfallback);
}
bool TDynamicObject::has_signal_on( int const Side, int const Pattern ) const {
/*
auto const vehicleend { iDirection > 0 ? Side : 1 - Side };
auto const pattern { iInventory[ vehicleend ] & Pattern };
return ( MoverParameters->iLights[ vehicleend ] == pattern );
*/
return MoverParameters->iLights[Side] == (Pattern & iInventory[Side]);
}
int TDynamicObject::DirectionSet(int d)
{ // ustawienie kierunku w składzie (wykonuje AI)
auto const lastdirection { iDirection };
iDirection = d > 0 ? 1 : 0; // d:1=zgodny,-1=przeciwny; iDirection:1=zgodny,0=przeciwny;
if( iDirection != lastdirection ) {
// direction was flipped, switch recorded servicable platform sides for potentially ongoing load exchange
auto const left { lastdirection > 0 ? 1 : 2 };
auto const right { 3 - left };
m_exchange.platforms =
( ( m_exchange.platforms & left ) != 0 ? right : 0 )
+ ( ( m_exchange.platforms & right ) != 0 ? left : 0 );
}
if (MyTrack)
{ // podczas wczytywania wstawiane jest AI, ale może jeszcze nie być toru
// AI ustawi kierunek ponownie po uruchomieniu silnika
update_neighbours();
}
// informacja o położeniu następnego
return 1 - ( iDirection > 0 ? NextConnectedNo() : PrevConnectedNo() );
};
// wskaźnik na poprzedni, nawet wirtualny
TDynamicObject * TDynamicObject::PrevAny() const {
return MoverParameters->Neighbours[ iDirection ^ 1 ].vehicle;
}
TDynamicObject * TDynamicObject::Prev(int C) const {
return MoverParameters->Couplers[iDirection ^ 1].CouplingFlag & C ? MoverParameters->Neighbours[iDirection ^ 1].vehicle : nullptr;// gdy sprzęg wirtualny, to jakby nic nie było
}
TDynamicObject * TDynamicObject::Next(int C) const {
return MoverParameters->Couplers[iDirection].CouplingFlag & C ? MoverParameters->Neighbours[iDirection].vehicle : nullptr;// gdy sprzęg wirtualny, to jakby nic nie było
}
// checks whether there's unbroken connection of specified type to specified vehicle
bool
TDynamicObject::is_connected( TDynamicObject const *Vehicle, coupling const Coupling ) const {
auto *vehicle { this };
if( vehicle == Vehicle ) {
// edge case, vehicle is always "connected" with itself
return true;
}
// check ahead, it's more likely the "owner" using this method is located there
while( ( vehicle = vehicle->Prev( Coupling ) ) != nullptr ) {
if( vehicle == Vehicle ) {
return true;
}
if( vehicle == this ) {
// edge case, looping consist
return false;
}
}
// start anew in the other direction
vehicle = this;
while( ( vehicle = vehicle->Next( Coupling ) ) != nullptr ) {
if( vehicle == Vehicle ) {
return true;
}
}
// no luck in either direction, give up
return false;
}
// ustalenie następnego (1) albo poprzedniego (0) w składzie bez względu na prawidłowość iDirection
TDynamicObject *
TDynamicObject::Neighbour(int &dir) {
auto *neighbour { (
MoverParameters->Couplers[ dir ].CouplingFlag != coupling::faux ?
MoverParameters->Neighbours[ dir ].vehicle :
nullptr ) };
// nowa wartość
dir = 1 - MoverParameters->Neighbours[ dir ].vehicle_end;
return neighbour;
};
// updates potential collision sources
void
TDynamicObject::update_neighbours() {
for( int end = end::front; end <= end::rear; ++end ) {
auto &neighbour { MoverParameters->Neighbours[ end ] };
auto const &coupler { MoverParameters->Couplers[ end ] };
if( coupler.Connected != nullptr
&& neighbour.vehicle != nullptr ) {
// physical connection with another vehicle locks down collision source on this end
// neighbour.vehicle = coupler.Connected;
// neighbour.vehicle_end = coupler.ConnectedNr;
neighbour.distance = TMoverParameters::CouplerDist( MoverParameters, coupler.Connected );
// take into account potential adapters attached to the couplers
auto const &othercoupler { neighbour.vehicle->MoverParameters->Couplers[ neighbour.vehicle_end ] };
neighbour.distance -= coupler.adapter_length;
neighbour.distance -= othercoupler.adapter_length;
}
else {
// if there's no connected vehicle check for potential collision sources in the vicinity
// NOTE: we perform a new scan on each update to ensure we always locate the nearest potential source
neighbour = neighbour_data();
// 10m ~= 140 km/h at 4 fps + safety margin, potential distance between outer axles of involved vehicles
auto const scanrange { std::max( 10.0, std::abs( MoverParameters->V ) ) + 40.0 };
auto const lookup { find_vehicle( end, scanrange ) };
if( false == std::get<bool>( lookup ) ) { continue; }
neighbour.vehicle = std::get<TDynamicObject *>( lookup );
neighbour.vehicle_end = std::get<int>( lookup );
neighbour.distance = std::get<double>( lookup );
if( neighbour.vehicle
&& neighbour.distance < (neighbour.vehicle->MoverParameters->CategoryFlag == 2 ? 50 : 100) ) {
// at short distances (re)calculate range between couplers directly
neighbour.distance = TMoverParameters::CouplerDist( MoverParameters, neighbour.vehicle->MoverParameters );
// take into account potential adapters attached to the couplers
auto const &othercoupler { neighbour.vehicle->MoverParameters->Couplers[ neighbour.vehicle_end ] };
neighbour.distance -= coupler.adapter_length;
neighbour.distance -= othercoupler.adapter_length;
}
}
}
}
// locates potential collision source within specified range, scanning track in specified direction. returns: true if neighbour was located, false otherwise
// NOTE: reuses legacy code. TBD, TODO: review, refactor?
std::tuple<TDynamicObject *, int, double, bool>
TDynamicObject::find_vehicle( int const Direction, double const Distance ) const {
auto direction { ( Direction == end::front ? 1 : -1 ) };
auto const initialdirection { direction }; // zapamiętanie kierunku poszukiwań na torze początkowym, względem sprzęgów
auto const *track { RaTrackGet() };
if( RaDirectionGet() < 0 ) {
// czy oś jest ustawiona w stronę Point1?
direction = -direction;
}
// (teraz względem toru)
auto const mycoupler { ( initialdirection < 0 ? end::rear : end::front ) }; // numer sprzęgu do podłączenia w obiekcie szukajacym
auto foundcoupler { -1 }; // numer sprzęgu w znalezionym obiekcie (znaleziony wypełni)
auto distance { 0.0 }; // przeskanowana odleglość; odległość do zawalidrogi
auto *foundobject { ABuFindObject( foundcoupler, distance, track, direction, mycoupler ) }; // zaczynamy szukać na tym samym torze
if( foundobject == nullptr ) {
// jeśli nie ma na tym samym, szukamy po okolicy szukanie najblizszego toru z jakims obiektem
// praktycznie przeklejone z TraceRoute()...
if (direction >= 0) // uwzględniamy kawalek przeanalizowanego wcześniej toru
distance = track->Length() - RaTranslationGet(); // odległość osi od Point2 toru
else
distance = RaTranslationGet(); // odległość osi od Point1 toru
while (distance < Distance) {
if (direction > 0) {
// w kierunku Point2 toru
if( track ?
track->iNextDirection :
false ) {
// jeśli następny tor jest podpięty od Point2
direction = -direction; // to zmieniamy kierunek szukania na tym torze
}
if( track ) {
track = track->CurrentNext(); // potem dopiero zmieniamy wskaźnik
}
}
else {
// w kierunku Point1
if( track ?
!track->iPrevDirection :
true ) {
// jeśli poprzedni tor nie jest podpięty od Point2
direction = -direction; // to zmieniamy kierunek szukania na tym torze
}
if( track ) {
track = track->CurrentPrev(); // potem dopiero zmieniamy wskaźnik
}
}
if (track) {
// jesli jest kolejny odcinek toru
foundobject = ABuFindObject(foundcoupler, distance, track, direction, mycoupler); // przejrzenie pojazdów tego toru
if (foundobject) {
break;
}
}
else {
// jeśli toru nie ma, to wychodzimy
distance = Distance + 1.0; // koniec przeglądania torów
break;
}
}
} // Koniec szukania najbliższego toru z jakimś obiektem.
if( foundobject == nullptr ) { return {}; }
/*
auto const *vehicle { MoverParameters };
auto const *foundvehicle { foundobject->MoverParameters };
if( ( false == TestFlag( track->iCategoryFlag, 1 ) )
&& ( distance > 50.0 ) ) {
// Ra: jeśli dwa samochody się mijają na odcinku przed zawrotką, to odległość między nimi nie może być liczona w linii prostej!
// NOTE: the distance is approximated, and additionally less accurate for cars heading in opposite direction
distance -= ( 0.5 * ( vehicle->Dim.L + foundvehicle->Dim.L ) );
}
else if( distance < 100.0 ) {
// at short distances start to calculate range between couplers directly
// odległość do najbliższego pojazdu w linii prostej
distance = TMoverParameters::CouplerDist( vehicle, foundvehicle );
}
*/
return { foundobject, foundcoupler, distance, true };
}
TDynamicObject * TDynamicObject::FindPowered()
{ // taka proteza:
// chcę podłączyć kabinę EN57 bezpośrednio z silnikowym, aby nie robić tego przez ukrotnienie
// drugi silnikowy i tak musi być ukrotniony, podobnie jak kolejna jednostka
// lepiej by było przesyłać komendy sterowania, co jednak wymaga przebudowy transmisji komend (LD)
// problem się robi ze światłami, które będą zapalane w silnikowym, ale muszą świecić się w rozrządczych
// dla EZT światłą czołowe będą "zapalane w silnikowym", ale widziane z rozrządczych
// również wczytywanie MMD powinno dotyczyć aktualnego członu
// problematyczna może być kwestia wybranej kabiny (w silnikowym...)
// jeśli silnikowy będzie zapięty odwrotnie (tzn. -1), to i tak powinno jeździć dobrze
// również hamowanie wykonuje się zaworem w członie, a nie w silnikowym...
auto const coupling { MoverParameters->TrainType == dt_EZT || MoverParameters->TrainType == dt_DMU ?
coupling::permanent :
coupling::control };
auto *lookup {
find_vehicle(
coupling,
[]( TDynamicObject * vehicle ) {
return vehicle->MoverParameters->Power > 1.0; } ) };
return lookup != nullptr ? lookup : this; // always return valid vehicle for backward compatibility
}
TDynamicObject *
TDynamicObject::FindPantographCarrier() {
// try first within a single unit, broaden to all vehicles under our control if first attempt fails
std::array<coupling, 2> const couplings = { coupling::permanent, coupling::control };
for( auto const coupling : couplings ) {
auto *result =
find_vehicle(
coupling,
[]( TDynamicObject * vehicle ) {
return vehicle->MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector && vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo > 0; } );
if( result != nullptr ) {
return result;
}
}
// if we're still here, admit failure
return nullptr;
}
//---------------------------------------------------------------------------
void TDynamicObject::ParamSet(int what, int into)
{ // ustawienie lokalnego parametru (what) na stan (into)
switch (what & 0xFF00)
{
case 0x0100: // to np. są drzwi, bity 0..7 określają numer 1..254 albo maskę
// dla 8 różnych
if (what & 1) // na razie mamy lewe oraz prawe, czyli używamy maskę 1=lewe,
// 2=prawe, 3=wszystkie
if( MoverParameters->Doors.instances[side::left].is_open )
{ // są otwarte
if (!into) // jeśli zamykanie
{
// dźwięk zamykania
}
}
else
{ // są zamknięte
if (into) // jeśli otwieranie
{
// dźwięk otwierania
}
}
if (what & 2) // prawe działają niezależnie od lewych
if( MoverParameters->Doors.instances[side::right].is_open )
{ // są otwarte
if (!into) // jeśli zamykanie
{
// dźwięk zamykania
}
}
else
{ // są zamknięte
if (into) // jeśli otwieranie
{
// dźwięk otwierania
}
}
break;
}
};
int TDynamicObject::RouteWish(TTrack *tr)
{ // zapytanie do AI, po którym
// segmencie (-6..6) jechać na
// skrzyżowaniu (tr)
return Mechanik ? Mechanik->CrossRoute(tr) : 0; // wg AI albo prosto
};
void TDynamicObject::DestinationSet(std::string to, std::string 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
// ale to jeszcze potrwa, zanim będzie możliwe, na razie można wpisać stację z
// rozkładu
asDestination = to;
if( std::abs( m_materialdata.multi_textures ) >= 4 ) { return; } // jak są 4 tekstury wymienne, to nie zmieniać rozkładem
if( DestinationSign.sign == nullptr ) { return; } // no sign submodel, no problem
// now see if we can find any version of the destination texture
std::vector<std::string> const destinations = {
numer, // try dedicated timetable sign first...
to }; // ...then generic destination sign
for( auto const &destination : destinations ) {
DestinationSign.destination = DestinationFind( destination );
if( DestinationSign.destination != null_handle ) {
// got what we wanted, we're done here
return;
}
}
// if we didn't get static texture we might be able to make one
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
std::string signrequest {
"make:"
+ DestinationSign.script
+ "?"
// timetable include
+ "$timetable=" + (
ctOwner == nullptr ?
MoverParameters->Name : // leading vehicle, can point to it directly
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
// 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" );
}
// optionl extra parameters
if( false == DestinationSign.parameters.empty() ) {
signrequest += "&" + DestinationSign.parameters;
}
DestinationSign.destination = GfxRenderer->Fetch_Material( signrequest );
}
material_handle TDynamicObject::DestinationFind( std::string Destination ) {
if( Destination.empty() ) { return null_handle; }
// destination textures are kept in the vehicle's directory so we point the current texture path there
auto const currenttexturepath { Global.asCurrentTexturePath };
Global.asCurrentTexturePath = asBaseDir;
auto destinationhandle { null_handle };
if( Destination.starts_with("make:") ) {
// autogenerated texture
destinationhandle = GfxRenderer->Fetch_Material( Destination );
}
else {
// regular texture
Destination = Bezogonkow( Destination ); // do szukania pliku obcinamy ogonki
// now see if we can find any version of the texture
std::vector<std::string> const destinations {
Destination + '@' + MoverParameters->TypeName,
Destination };
for( auto const &destination : destinations ) {
auto material = TextureTest( ToLower( destination ) );
if( false == material.empty() ) {
destinationhandle = GfxRenderer->Fetch_Material( material );
break;
}
}
}
// whether we got anything, restore previous texture path
Global.asCurrentTexturePath = currenttexturepath;
return destinationhandle;
}
void TDynamicObject::announce( announcement_t const Announcement, bool const Chime ) {
if( m_doorspeakers.empty() ) { return; }
auto const *driver { (
ctOwner != nullptr ?
ctOwner :
Mechanik ) };
if( driver == nullptr ) { return; }
auto const &timetable { driver->TrainTimetable() };
auto const &announcements { m_pasystem.announcements };
auto playchime { Chime };
if( announcements[ static_cast<int>( Announcement ) ].empty() ) {
goto followup;
}
// if the announcement sound was defined queue playback
{
sound_source stopnamesound;
switch( Announcement ) {
case announcement_t::approaching:
case announcement_t::next: {
stopnamesound = timetable.next_stop_sound();
break;
}
case announcement_t::current: {
stopnamesound = timetable.current_stop_sound();
break;
}
case announcement_t::destination: {
stopnamesound = timetable.last_stop_sound();
break;
}
default: {
break;
}
}
if( stopnamesound.empty() ) {
goto followup;
}
// potentially precede the announcement with a chime...
if( true == playchime
&& false == announcements[static_cast<int>(announcement_t::chime)].empty() ) {
m_pasystem.announcement_queue.emplace_back( announcements[ static_cast<int>( announcement_t::chime ) ] );
playchime = false;
}
// ...then play the announcement itself
m_pasystem.announcement_queue.emplace_back( announcements[ static_cast<int>( Announcement ) ] );
m_pasystem.announcement_queue.emplace_back( stopnamesound );
}
followup:
// potentially follow up with another announcement
if( Announcement == announcement_t::next ) {
announce( announcement_t::destination, playchime );
}
}
void TDynamicObject::OverheadTrack(float o)
{ // ewentualne wymuszanie jazdy
// bezprądowej z powodu informacji
// w torze
if (ctOwner) // jeśli ma obiekt nadzorujący
{ // trzeba zaktualizować mapę flag bitowych jazdy bezprądowej
if (o < 0.0)
{ // normalna jazda po tym torze
ctOwner->iOverheadZero &= ~iOverheadMask; // zerowanie bitu - może pobierać prąd
ctOwner->iOverheadDown &= ~iOverheadMask; // zerowanie bitu - może podnieść pantograf
}
else if (o > 0.0)
{ // opuszczenie pantografów
ctOwner->iOverheadZero |= iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu
ctOwner->iOverheadDown |= iOverheadMask; // ustawienie bitu - ma opuścić pantograf
}
else
{ // jazda bezprądowa z podniesionym pantografem
ctOwner->iOverheadZero |= iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu
ctOwner->iOverheadDown &= ~iOverheadMask; // zerowanie bitu - może podnieść pantograf
}
}
};
void
TDynamicObject::update_shake( double const Timedelta ) {
// Ra: mechanik powinien być telepany niezależnie od pozycji pojazdu
// Ra: trzeba zrobić model bujania głową i wczepić go do pojazdu
// Ra: tu by się przydało uwzględnić rozkład sił:
// - na postoju horyzont prosto, kabina skosem
// - przy szybkiej jeździe kabina prosto, horyzont pochylony
// McZapkie: najpierw policzę pozycję w/m kabiny
// ABu: rzucamy kabina tylko przy duzym FPS!
// Mala histereza, zeby bez przerwy nie przelaczalo przy FPS~17
// Granice mozna ustalic doswiadczalnie. Ja proponuje 14:20
if( Global.iSlowMotion == 0 ) { // musi być pełna prędkość
glm::dvec3 shakevector;
if( MoverParameters->EngineType == TEngineType::DieselElectric
|| MoverParameters->EngineType == TEngineType::DieselEngine ) {
if( std::abs( MoverParameters->enrot ) > 0.0 ) {
// engine vibration
shakevector.x +=
std::sin(MoverParameters->eAngle * 4.0) * Timedelta *
EngineShake.scale
// fade in with rpm above threshold
* std::clamp(
( MoverParameters->enrot - EngineShake.fadein_offset ) * EngineShake.fadein_factor,
0.0, 1.0 )
// fade out with rpm above threshold
* std::lerp(
1.0, 0.0,
std::clamp(
( MoverParameters->enrot - EngineShake.fadeout_offset ) * EngineShake.fadeout_factor,
0.0, 1.0 ) );
}
}
if( HuntingShake.fadein_begin > 0.f
&& true == MoverParameters->TruckHunting ) {
// hunting oscillation
HuntingAngle = clamp_circular( HuntingAngle + 4.0 * HuntingShake.frequency * Timedelta * MoverParameters->Vel, 360.0 );
auto const huntingamount =
std::lerp(
0.0, 1.0,
std::clamp(
( MoverParameters->Vel - HuntingShake.fadein_begin ) / ( HuntingShake.fadein_end - HuntingShake.fadein_begin ),
0.0, 1.0 ) );
shakevector.x +=
std::sin(glm::radians(HuntingAngle)) * Timedelta * HuntingShake.scale
* huntingamount;
IsHunting = huntingamount > 0.025;
}
if (FreeFlyModeFlag)
shakevector *= 0;
auto const iVel { std::min( GetVelocity(), 150.0 ) };
if( iVel > 0.5 ) {
// acceleration-driven base shake
shakevector += glm::dvec3(
-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
}
auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) };
if( LocalRandom( iVel ) > 25.0 ) {
// extra shake at increased velocity
shake += ShakeSpring.ComputateForces(
glm::dvec3(
( LocalRandom( iVel * 2 ) - iVel ) / ( iVel * 2 * 4 ) * BaseShake.jolt_scale.x,
( LocalRandom( iVel * 2 ) - iVel ) / ( iVel * 2 * 4 ) * BaseShake.jolt_scale.y,
( LocalRandom( iVel * 2 ) - iVel ) / ( iVel * 2 * 4 ) * BaseShake.jolt_scale.z )
// * (( 200 - DynamicObject->MyTrack->iQualityFlag ) * 0.0075 ) // scale to 75-150% based on track quality
* 1.25,
ShakeState.offset );
}
shake *= 0.85;
ShakeState.velocity -= ( shake + ShakeState.velocity * 100.0 ) * (double)( BaseShake.jolt_scale.x + BaseShake.jolt_scale.y + BaseShake.jolt_scale.z ) / 200.0;
// McZapkie:
ShakeState.offset += ShakeState.velocity * Timedelta;
if( std::abs( ShakeState.offset.y ) > std::abs( BaseShake.jolt_limit ) ) {
ShakeState.velocity.y = -ShakeState.velocity.y;
}
}
else { // hamowanie rzucania przy spadku FPS
ShakeState.offset -= ShakeState.offset * std::min( Timedelta, 1.0 ); // po tym chyba potrafią zostać jakieś ułamki, które powodują zjazd
}
}
std::pair<double, double>
TDynamicObject::shake_angles() const {
return {
std::atan( ShakeState.velocity.x * BaseShake.angle_scale.x ),
std::atan( ShakeState.velocity.z * BaseShake.angle_scale.z ) };
}
void
TDynamicObject::powertrain_sounds::position( glm::vec3 const Location ) {
auto const nullvector { glm::vec3() };
std::vector<sound_source *> enginesounds = {
&inverter,
&motor_relay, &dsbWejscie_na_bezoporow, &motor_parallel, &motor_shuntfield, &rsWentylator,
&engine, &engine_ignition, &engine_shutdown, &engine_revving, &engine_turbo, &oil_pump, &fuel_pump, &water_pump, &water_heater, &radiator_fan, &radiator_fan_aux,
&transmission, &rsEngageSlippery, &retarder
};
for( auto sound : enginesounds ) {
if( sound->offset() == nullvector ) {
sound->offset( Location );
}
}
}
void
TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, double const Deltatime ) {
if( Vehicle.Power == 0 ) { return; }
double frequency { 1.0 };
double volume { 0.0 };
// oil pump
if( true == Vehicle.OilPump.is_active ) {
oil_pump
.pitch( oil_pump.m_frequencyoffset + oil_pump.m_frequencyfactor * 1.f )
.gain( oil_pump.m_amplitudeoffset + oil_pump.m_amplitudefactor * 1.f )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
oil_pump.stop();
}
// fuel pump
if( true == Vehicle.FuelPump.is_active ) {
fuel_pump
.pitch( fuel_pump.m_frequencyoffset + fuel_pump.m_frequencyfactor * 1.f )
.gain( fuel_pump.m_amplitudeoffset + fuel_pump.m_amplitudefactor * 1.f )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
fuel_pump.stop();
}
// water pump
if( true == Vehicle.WaterPump.is_active ) {
water_pump
.pitch( water_pump.m_frequencyoffset + water_pump.m_frequencyfactor * 1.f )
.gain( water_pump.m_amplitudeoffset + water_pump.m_amplitudefactor * 1.f )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
water_pump.stop();
}
// water heater
if( true == Vehicle.WaterHeater.is_active ) {
water_heater
.pitch( water_heater.m_frequencyoffset + water_heater.m_frequencyfactor * 1.f )
.gain( water_heater.m_amplitudeoffset + water_heater.m_amplitudefactor * 1.f )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
water_heater.stop();
}
// engine sounds
// ignition
if( engine_state_last != Vehicle.Mains ) {
if( true == Vehicle.Mains ) {
// TODO: separate engine and main circuit
// engine activation
engine_shutdown.stop();
engine_ignition
.pitch( engine_ignition.m_frequencyoffset + engine_ignition.m_frequencyfactor * 1.f )
.gain( engine_ignition.m_amplitudeoffset + engine_ignition.m_amplitudefactor * 1.f )
.play( sound_flags::exclusive );
// main circuit activation
linebreaker_close
.pitch( linebreaker_close.m_frequencyoffset + linebreaker_close.m_frequencyfactor * 1.f )
.gain( linebreaker_close.m_amplitudeoffset + linebreaker_close.m_amplitudefactor * 1.f )
.play();
}
else {
// engine deactivation
engine_ignition.stop();
engine_shutdown.pitch( engine_shutdown.m_frequencyoffset + engine_shutdown.m_frequencyfactor * 1.f )
.gain( engine_shutdown.m_amplitudeoffset + engine_shutdown.m_amplitudefactor * 1.f )
.play( sound_flags::exclusive );
// main circuit deactivation
linebreaker_open
.pitch( linebreaker_open.m_frequencyoffset + linebreaker_open.m_frequencyfactor * 1.f )
.gain( linebreaker_open.m_amplitudeoffset + linebreaker_open.m_amplitudefactor * 1.f )
.play();
}
engine_state_last = Vehicle.Mains;
}
// main engine sound
if( true == Vehicle.Mains ) {
if( std::abs(Vehicle.enrot) > 0.01
// McZapkie-280503: zeby dla dumb dzialal silnik na jalowych obrotach
|| Vehicle.EngineType == TEngineType::Dumb ) {
// frequency calculation
auto const normalizer { (
true == engine.is_combined() ?
// for combined engine sound we calculate sound point in rpm, to make .mmd files setup easier
// NOTE: 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
60.f * 0.01f :
// for legacy single-piece sounds the standard frequency calculation is left untouched
1.f ) };
frequency =
engine.m_frequencyoffset
+ engine.m_frequencyfactor * std::abs( Vehicle.enrot ) * normalizer;
if( Vehicle.EngineType == TEngineType::Dumb ) {
frequency -= 0.2 * Vehicle.EnginePower / ( 1 + Vehicle.Power * 1000 );
}
// base volume calculation
switch( Vehicle.EngineType ) {
// TODO: check calculated values
case TEngineType::DieselElectric: {
volume =
engine.m_amplitudeoffset
+ engine.m_amplitudefactor * (
0.25 * ( Vehicle.EnginePower / Vehicle.Power )
+ 0.75 * Vehicle.EngineRPMRatio() );
break;
}
case TEngineType::DieselEngine: {
if( Vehicle.enrot > 0.0 ) {
volume = Vehicle.EnginePower > 0 ? engine.m_amplitudeoffset + engine.m_amplitudefactor * Vehicle.dizel_fill :
engine.m_amplitudeoffset + engine.m_amplitudefactor * std::fabs(Vehicle.enrot / Vehicle.dizel_nmax);
}
break;
}
default: {
volume =
engine.m_amplitudeoffset
+ engine.m_amplitudefactor * ( Vehicle.EnginePower + std::fabs( Vehicle.enrot ) * 60.0 );
break;
}
}
if( engine_volume >= 0.05 ) {
auto enginerevvolume { 0.f };
if( Vehicle.EngineType == TEngineType::DieselElectric
|| Vehicle.EngineType == TEngineType::DieselEngine ) {
// diesel engine revolutions increase; it can potentially decrease volume of base engine sound
if( engine_revs_last != -1.f ) {
// calculate potential recent increase of engine revolutions
auto const revolutionsperminute { Vehicle.enrot * 60 };
auto const revolutionsdifference { revolutionsperminute - engine_revs_last };
auto const idlerevolutionsthreshold { 1.01 * Vehicle.EngineIdleRPM() };
engine_revs_change = std::max( 0.0, engine_revs_change - 2.5 * Deltatime );
if( revolutionsperminute > idlerevolutionsthreshold
&& revolutionsdifference > 1.0 * Deltatime ) {
engine_revs_change = std::clamp( engine_revs_change + 5.0 * Deltatime, 0.0, 1.25 );
}
enginerevvolume = 0.8 * engine_revs_change;
}
engine_revs_last = Vehicle.enrot * 60;
auto const enginerevfrequency { (
true == engine_revving.is_combined() ?
// if the sound contains multiple samples we reuse rpm-based calculation from the engine
frequency :
engine_revving.m_frequencyoffset + 1.f * engine_revving.m_frequencyfactor ) };
if( enginerevvolume > 0.02 ) {
engine_revving
.pitch( enginerevfrequency )
.gain( enginerevvolume )
.play( sound_flags::exclusive );
}
else {
engine_revving.stop();
}
} // diesel engines
// multi-part revving sound pieces replace base engine sound, single revving simply gets mixed with the base
auto const enginevolume { (
enginerevvolume > 0.02 && true == engine_revving.is_combined() ?
std::max( 0.0, engine_volume - enginerevvolume ) :
engine_volume ) };
engine
.pitch( frequency )
.gain( enginevolume )
.play( sound_flags::exclusive | sound_flags::looping );
} // enginevolume > 0.05
}
else {
engine.stop();
}
}
if (Vehicle.Mains) {
float power = std::clamp(Vehicle.eimv_pr, 0.0, 1.0);
fake_engine
.pitch(fake_engine.m_frequencyoffset + power * fake_engine.m_frequencyfactor)
.gain(fake_engine.m_amplitudeoffset + power * fake_engine.m_amplitudefactor)
.play(sound_flags::exclusive | sound_flags::looping);
}
else {
fake_engine.stop();
}
engine_volume = std::lerp( engine_volume, volume, 0.25 );
if( engine_volume < 0.05 ) {
engine.stop();
}
// youBy - przenioslem, bo diesel tez moze miec turbo
if( Vehicle.TurboTest > 0 ) {
// udawanie turbo:
auto const pitch_diesel{Vehicle.EngineType == TEngineType::DieselEngine || Vehicle.EngineType == TEngineType::DieselElectric ? Vehicle.enrot / Vehicle.dizel_nmax * Vehicle.dizel_fill : 1};
auto const goalpitch { std::max( 0.025, ( /*engine_volume **/ pitch_diesel + engine_turbo.m_frequencyoffset ) * engine_turbo.m_frequencyfactor ) };
auto const goalvolume { (
Vehicle.MainCtrlPos >= Vehicle.TurboTest && Vehicle.enrot > 0.1 ?
std::max( 0.0, ( engine_turbo_pitch + engine_turbo.m_amplitudeoffset ) * engine_turbo.m_amplitudefactor ) :
0.0 ) };
auto const currentvolume { engine_turbo.gain() };
auto const changerate { 0.4 * Deltatime };
engine_turbo_pitch = engine_turbo_pitch > goalpitch ? std::max(goalpitch, engine_turbo_pitch - changerate * 0.5) : std::min(goalpitch, engine_turbo_pitch + changerate);
volume = currentvolume > goalvolume ? std::max(goalvolume, currentvolume - changerate) : std::min(goalvolume, currentvolume + changerate);
engine_turbo
.pitch( 0.4 + engine_turbo_pitch * 0.4 )
.gain( volume );
if( volume > 0.05 ) {
engine_turbo.play( sound_flags::exclusive | sound_flags::looping );
}
else {
engine_turbo.stop();
}
}
if( Vehicle.dizel_engage > 0.1 ) {
if( std::abs( Vehicle.dizel_engagedeltaomega ) > 0.2 ) {
frequency = rsEngageSlippery.m_frequencyoffset + rsEngageSlippery.m_frequencyfactor * std::fabs( Vehicle.dizel_engagedeltaomega );
volume = rsEngageSlippery.m_amplitudeoffset + rsEngageSlippery.m_amplitudefactor * Vehicle.dizel_engage;
}
else {
frequency = 1.f; // rsEngageSlippery.FA+0.7*rsEngageSlippery.FM*(fabs(mvControlled->enrot)+mvControlled->nmax);
volume = Vehicle.dizel_engage > 0.2 ? rsEngageSlippery.m_amplitudeoffset + 0.2 * rsEngageSlippery.m_amplitudefactor * (Vehicle.enrot / Vehicle.nmax) : 0.f;
}
rsEngageSlippery
.pitch( frequency )
.gain( volume )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
rsEngageSlippery.stop();
}
if (Vehicle.hydro_R) {
float speed = std::abs(Vehicle.hydro_R_n);
retarder
.pitch(retarder.m_frequencyoffset + speed * retarder.m_frequencyfactor)
.gain(retarder.m_amplitudeoffset + Vehicle.hydro_R_Fill * retarder.m_amplitudefactor);
if (retarder.gain() > 0.01 && speed > 1 && Vehicle.hydro_R_ClutchActive) {
retarder.play(sound_flags::exclusive | sound_flags::looping);
}
else {
retarder.stop();
}
}
// motor sounds
volume = 0.0;
// generic traction motor sounds
if( false == motors.empty() ) {
if( std::abs( Vehicle.nrot ) > 0.01 ) {
auto const &motor { motors.front() };
// frequency calculation
auto normalizer { 1.f };
if( true == motor.is_combined() ) {
// for combined motor sound we calculate sound point in rpm, to make .mmd files setup easier
// NOTE: 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
normalizer = 60.f * 0.01f;
}
auto const motorrevolutions { std::abs( Vehicle.nrot ) * Vehicle.Transmision.Ratio };
frequency =
motor.m_frequencyoffset
+ motor.m_frequencyfactor * motorrevolutions * normalizer;
// base volume calculation
switch( Vehicle.EngineType ) {
case TEngineType::ElectricInductionMotor: {
volume =
motor.m_amplitudeoffset
+ motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 2 );
break;
}
case TEngineType::ElectricSeriesMotor: {
volume =
motor.m_amplitudeoffset
+ motor.m_amplitudefactor * ( Vehicle.EnginePower + motorrevolutions * 60.0 );
break;
}
default: {
volume =
motor.m_amplitudeoffset
+ motor.m_amplitudefactor * motorrevolutions * 60.0;
break;
}
}
if( Vehicle.EngineType == TEngineType::ElectricSeriesMotor ) {
// volume variation
if( volume < 1.0
&& Vehicle.EnginePower < 100 ) {
auto const volumevariation { LocalRandom( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) };
if( volumevariation < 2 ) {
volume += volumevariation / 200;
}
}
if( Vehicle.DynamicBrakeFlag
&& Vehicle.EnginePower > 0.1 ) {
// Szociu - 29012012 - jeżeli uruchomiony jest hamulec elektrodynamiczny, odtwarzany jest dźwięk silnika
volume += 0.8;
}
}
// scale motor volume based on whether they're active
motor_momentum =
std::clamp(
motor_momentum
- 1.0 * Deltatime // smooth out decay
+ std::abs( Vehicle.Mm ) / 60.0 * Deltatime,
0.0, 1.25 );
volume *= std::max( 0.25f, motor_momentum );
motor_volume = std::lerp( motor_volume, volume, 0.25 );
if( motor_volume >= 0.05 ) {
// apply calculated parameters to all motor instances
for( auto &motor : motors ) {
motor
.pitch( frequency )
.gain( motor_volume )
.play( sound_flags::exclusive | sound_flags::looping );
}
}
else {
// stop all motor instances
for( auto &motor : motors ) {
motor.stop();
}
}
}
else {
// stop all motor instances
motor_volume = 0.0;
for( auto &motor : motors ) {
motor.stop();
}
}
}
// inverter-specific traction motor sounds
if( false == acmotors.empty() ) {
if( Vehicle.EngineType == TEngineType::ElectricInductionMotor ) {
if( Vehicle.InverterFrequency > 0.001 ) {
for( auto &motor : acmotors ) {
motor
.pitch( motor.m_frequencyoffset + motor.m_frequencyfactor * Vehicle.InverterFrequency )
.gain( motor.m_amplitudeoffset + motor.m_amplitudefactor * std::sqrt( std::abs( Vehicle.eimv_pr ) ) )
.play( sound_flags::exclusive | sound_flags::looping );
}
}
else {
for( auto &motor : acmotors ) {
motor.stop();
}
}
}
}
// motor blowers
if( false == motorblowers.empty() ) {
for( auto &blowersound : motorblowers ) {
// match the motor blower and the sound source based on whether they're located in the front or the back of the vehicle
auto const &blower { Vehicle.MotorBlowers[ ( blowersound.offset().z > 0 ? end::front : end::rear ) ] };
// TODO: for the sounds with provided bookends invoke stop() when the stop is triggered and revolutions start dropping, instead of after full stop
if( blower.revolutions > 1 ) {
// NOTE: for motorblowers with fixed speed if the sound has defined bookends we skip revolutions-based part of frequency/volume adjustments
auto const revolutionmodifier { Vehicle.MotorBlowers[end::front].speed < 0.f && blowersound.has_bookends() ? 1.f : blower.revolutions };
blowersound
.pitch(
true == blowersound.is_combined() ?
blower.revolutions * 0.01f :
blowersound.m_frequencyoffset + blowersound.m_frequencyfactor * revolutionmodifier )
.gain( blowersound.m_amplitudeoffset + blowersound.m_amplitudefactor * revolutionmodifier )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
blowersound.stop();
}
}
}
// inverter sounds
if( Vehicle.EngineType == TEngineType::ElectricInductionMotor ) {
if( Vehicle.InverterFrequency > 0.001 ) {
volume = inverter.m_amplitudeoffset + inverter.m_amplitudefactor * std::sqrt( std::abs( Vehicle.eimv_pr) );
inverter
.pitch( inverter.m_frequencyoffset + inverter.m_frequencyfactor * Vehicle.InverterFrequency )
.gain( volume )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
inverter.stop();
}
}
// ventillator sounds
if( Vehicle.RventRot > 0.1 ) {
rsWentylator
.pitch( rsWentylator.m_frequencyoffset + rsWentylator.m_frequencyfactor * Vehicle.RventRot )
.gain( rsWentylator.m_amplitudeoffset + rsWentylator.m_amplitudefactor * Vehicle.RventRot )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
// ...otherwise shut down the sound
rsWentylator.stop();
}
// radiator fan sounds
if( Vehicle.EngineType == TEngineType::DieselEngine
|| Vehicle.EngineType == TEngineType::DieselElectric ) {
if( Vehicle.dizel_heat.rpmw > 0.1 ) {
// NOTE: fan speed tends to max out at ~100 rpm; by default we try to get pitch range of 0.5-1.5 and volume range of 0.5-1.0
radiator_fan
.pitch( 0.5 + radiator_fan.m_frequencyoffset + radiator_fan.m_frequencyfactor * Vehicle.dizel_heat.rpmw * 0.01 )
.gain( 0.5 + radiator_fan.m_amplitudeoffset + 0.5 * radiator_fan.m_amplitudefactor * Vehicle.dizel_heat.rpmw * 0.01 )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
// ...otherwise shut down the sound
radiator_fan.stop();
}
if( Vehicle.dizel_heat.rpmw2 > 0.1 ) {
// NOTE: fan speed tends to max out at ~100 rpm; by default we try to get pitch range of 0.5-1.5 and volume range of 0.5-1.0
radiator_fan_aux
.pitch( 0.5 + radiator_fan_aux.m_frequencyoffset + radiator_fan_aux.m_frequencyfactor * Vehicle.dizel_heat.rpmw2 * 0.01 )
.gain( 0.5 + radiator_fan_aux.m_amplitudeoffset + 0.5 * radiator_fan_aux.m_amplitudefactor * Vehicle.dizel_heat.rpmw2 * 0.01 )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
// ...otherwise shut down the sound
radiator_fan_aux.stop();
}
}
// relay sounds
auto const soundflags { Vehicle.SoundFlag };
if( TestFlag( soundflags, sound::relay ) ) {
// przekaznik - gdy bezpiecznik, automatyczny rozruch itp
if( true == TestFlag( soundflags, sound::shuntfield ) ) {
// shunt field
motor_shuntfield
.pitch(
true == motor_shuntfield.is_combined() ?
Vehicle.ScndCtrlActualPos * 0.01f :
motor_shuntfield.m_frequencyoffset + motor_shuntfield.m_frequencyfactor * 1.f )
.gain(
motor_shuntfield.m_amplitudeoffset + (
true == TestFlag( soundflags, sound::loud ) ?
1.0f :
0.8f )
* motor_shuntfield.m_amplitudefactor )
.play();
}
else if( true == TestFlag( soundflags, sound::parallel ) ) {
// parallel mode
if( TestFlag( soundflags, sound::loud ) ) {
dsbWejscie_na_bezoporow.play();
}
else {
motor_parallel.play();
}
}
else {
// series mode
motor_relay
.pitch(
true == motor_relay.is_combined() ?
Vehicle.MainCtrlActualPos * 0.01f :
motor_relay.m_frequencyoffset + motor_relay.m_frequencyfactor * 1.f )
.gain(
motor_relay.m_amplitudeoffset + (
true == TestFlag( soundflags, sound::loud ) ?
1.0f :
0.8f )
* motor_relay.m_amplitudefactor )
.play();
}
}
if( Vehicle.Vel > 0.1 ) {
transmission
.pitch( transmission.m_frequencyoffset + transmission.m_frequencyfactor * Vehicle.Vel )
.gain( transmission.m_amplitudeoffset + transmission.m_amplitudefactor * Vehicle.Vel )
.play( sound_flags::exclusive | sound_flags::looping );
}
else {
transmission.stop();
}
}
// legacy method, calculates changes in simulation state over specified time
void
vehicle_table::update( double Deltatime, int Iterationcount ) {
// Ra: w zasadzie to trzeba by utworzyć oddzielną listę taboru do liczenia fizyki
// na którą by się zapisywały wszystkie pojazdy będące w ruchu
// pojazdy stojące nie potrzebują aktualizacji, chyba że np. ktoś im zmieni nastawę hamulca
// oddzielną listę można by zrobić na pojazdy z napędem, najlepiej posortowaną wg typu napędu
for( auto *vehicle : m_items ) {
if( false == vehicle->bEnabled ) { continue; }
// Ra: zmienić warunek na sprawdzanie pantografów w jednej zmiennej: czy pantografy i czy podniesione
if( vehicle->MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) {
update_traction( vehicle );
}
vehicle->MoverParameters->ComputeConstans();
vehicle->update_neighbours();
}
if( Iterationcount > 1 ) {
// ABu: ponizsze wykonujemy tylko jesli wiecej niz jedna iteracja
for( int iteration = 0; iteration < Iterationcount - 1; ++iteration ) {
for( auto *vehicle : m_items ) {
vehicle->UpdateForce( Deltatime );
}
for( auto *vehicle : m_items ) {
vehicle->FastUpdate( Deltatime );
}
}
}
for( auto *vehicle : m_items ) {
vehicle->UpdateForce( Deltatime );
}
auto const totaltime { Deltatime * Iterationcount }; // całkowity czas
for( auto *vehicle : m_items ) {
// Ra 2015-01: tylko tu przelicza sieć trakcyjną
vehicle->Update( Deltatime, totaltime );
}
// jeśli jest coś do usunięcia z listy, to trzeba na końcu
erase_disabled();
}
// legacy method, checks for presence and height of traction wire for specified vehicle
void
vehicle_table::update_traction( TDynamicObject *Vehicle ) {
auto const vFront = Vehicle->VectorFront(); // wektor normalny dla płaszczyzny ruchu pantografu
auto const vUp = Vehicle->VectorUp(); // wektor pionu pudła (pochylony od pionu na przechyłce)
auto const vLeft = Vehicle->VectorLeft(); // wektor odległości w bok (odchylony od poziomu na przechyłce)
auto const position = Vehicle->GetPosition(); // współrzędne środka pojazdu
for( int pantographindex = 0; pantographindex < Vehicle->iAnimType[ ANIM_PANTS ]; ++pantographindex ) {
// pętla po pantografach
auto *pantograph { Vehicle->pants[ pantographindex ].fParamPants };
if( true == Vehicle->MoverParameters->Pantographs[ pantographindex ].is_active ) {
// jeśli pantograf podniesiony
auto const pant0 { position + vLeft * pantograph->vPos.z + vUp * pantograph->vPos.y + vFront * pantograph->vPos.x };
if( pantograph->hvPowerWire != nullptr ) {
// jeżeli znamy drut z poprzedniego przebiegu
for( int attempts = 0; attempts < 30; ++attempts ) {
// sanity check. shouldn't happen in theory, but did happen in practice
if( pantograph->hvPowerWire == nullptr ) { break; }
// powtarzane aż do znalezienia odpowiedniego odcinka na liście dwukierunkowej
if( pantograph->hvPowerWire->iLast & 0x3 ) {
// dla ostatniego i przedostatniego przęsła wymuszamy szukanie innego
// nie to, że nie ma, ale trzeba sprawdzić inne
pantograph->hvPowerWire = nullptr;
break;
}
if( pantograph->hvPowerWire->hvParallel ) {
// jeśli przęsło tworzy bieżnię wspólną, to trzeba sprawdzić pozostałe
// nie to, że nie ma, ale trzeba sprawdzić inne
pantograph->hvPowerWire = nullptr;
break;
}
// obliczamy wyraz wolny równania płaszczyzny (to miejsce nie jest odpowienie)
// podstawiamy równanie parametryczne drutu do równania płaszczyzny pantografu
// TODO: investigate this routine with reardriver/negative speed, does it picks the right wire?
auto const fRaParam =
-( glm::dot( pantograph->hvPowerWire->pPoint1, vFront ) - glm::dot( pant0, vFront ) )
/ glm::dot( pantograph->hvPowerWire->vParametric, vFront );
if( fRaParam < -0.001 ) {
// histereza rzędu 7cm na 70m typowego przęsła daje 1 promil
pantograph->hvPowerWire = pantograph->hvPowerWire->hvNext[ 0 ];
continue;
}
if( fRaParam > 1.001 ) {
pantograph->hvPowerWire = pantograph->hvPowerWire->hvNext[ 1 ];
continue;
}
// jeśli t jest w przedziale, wyznaczyć odległość wzdłuż wektorów vUp i vLeft
// punkt styku płaszczyzny z drutem (dla generatora łuku el.)
auto const vStyk { pantograph->hvPowerWire->pPoint1 + fRaParam * pantograph->hvPowerWire->vParametric };
auto const vGdzie { vStyk - pant0 }; // wektor
// odległość w pionie musi być w zasięgu ruchu "pionowego" pantografu
// musi się mieścić w przedziale ruchu pantografu
auto const fVertical { glm::dot( vGdzie, vUp ) };
// odległość w bok powinna być mniejsza niż pół szerokości pantografu
// to się musi mieścić w przedziale zależnym od szerokości pantografu
auto const fHorizontal { std::abs( glm::dot( vGdzie, vLeft ) ) - pantograph->fWidth };
// jeśli w pionie albo w bok jest za daleko, to dany drut jest nieużyteczny
if( fHorizontal <= 0.0 ) {
// koniec pętli, aktualny drut pasuje
pantograph->PantTraction = fVertical;
break;
}
else {
// the wire is outside contact area and as of now we don't have good detection of parallel sections
// as such there's no guaratee there isn't parallel section present.
// therefore we don't bother checking if the wire is still within range of guide horns
// but simply force area search for potential better option
pantograph->hvPowerWire = nullptr;
break;
}
}
}
if( pantograph->hvPowerWire == nullptr ) {
// look in the region for a suitable traction piece if we don't already have any
simulation::Region->update_traction( Vehicle, pantographindex );
}
if( pantograph->hvPowerWire == nullptr
&& false == Global.bLiveTraction ) {
// jeśli drut nie znaleziony ale można oszukiwać to dajemy coś tam dla picu
Vehicle->pants[ pantographindex ].fParamPants->PantTraction = 1.4;
}
}
else {
// pantograph is down
pantograph->hvPowerWire = nullptr;
}
}
}
// legacy method, sends list of vehicles over network
void
vehicle_table::DynamicList( bool const Onlycontrolled ) const {
// odesłanie nazw pojazdów dostępnych na scenerii (nazwy, szczególnie wagonów, mogą się powtarzać!)
for( auto const *vehicle : m_items ) {
if( false == Onlycontrolled
|| vehicle->Mechanik != nullptr ) {
// same nazwy pojazdów
multiplayer::WyslijString( vehicle->asName, 6 );
}
}
// informacja o końcu listy
multiplayer::WyslijString( "none", 6 );
}
// maintenance; removes from tracks consists with vehicles marked as disabled
bool
vehicle_table::erase_disabled() {
if( false == TDynamicObject::bDynamicRemove ) { return false; }
// go through the list and retrieve vehicles scheduled for removal...
type_sequence disabledvehicles;
for( auto *vehicle : m_items ) {
if( false == vehicle->bEnabled ) {
disabledvehicles.emplace_back( vehicle );
}
}
// ...now propagate removal flag through affected consists...
for( auto *vehicle : disabledvehicles ) {
TDynamicObject *coupledvehicle { vehicle };
while( ( coupledvehicle = coupledvehicle->Next() ) != nullptr ) {
coupledvehicle->bEnabled = false;
}
// (try to) run propagation in both directions, it's simpler than branching based on direction etc
coupledvehicle = vehicle;
while( ( coupledvehicle = coupledvehicle->Prev() ) != nullptr ) {
coupledvehicle->bEnabled = false;
}
}
// ...then actually remove all disabled vehicles...
auto vehicleiter = std::begin( m_items );
while( vehicleiter != std::end( m_items ) ) {
auto *vehicle { *vehicleiter };
if( true == vehicle->bEnabled ) {
++vehicleiter;
}
else {
if( vehicle->MyTrack != nullptr
&& true == vehicle->MyTrack->RemoveDynamicObject(vehicle) ) {
vehicle->MyTrack = nullptr;
}
if( simulation::Train != nullptr
&& simulation::Train->Dynamic() == vehicle ) {
// clear potential train binding
// TBD, TODO: kill vehicle sounds
simulation::Train = nullptr;
}
simulation::Trains.purge(vehicle->name());
// remove potential entries in the light array
simulation::Lights.remove( vehicle );
/*
// finally get rid of the vehicle and its record themselves
// BUG: deleting the vehicle leaves dangling pointers in event->Activator and potentially elsewhere
// TBD, TODO: either mark 'dead' vehicles with additional flag, or delete the references as well
SafeDelete( vehicle );
vehicleiter = m_items.erase( vehicleiter );
*/
++vehicleiter; // NOTE: instead of the erase in the disabled section
}
}
// ...and call it a day
TDynamicObject::bDynamicRemove = false;
return true;
}