16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-23 15:39:19 +02:00

Merge branch 'tmj-dev' into milek-dev

This commit is contained in:
milek7
2018-11-10 23:52:46 +01:00
15 changed files with 659 additions and 473 deletions

View File

@@ -14,44 +14,30 @@ http://mozilla.org/MPL/2.0/.
#include "utilities.h" #include "utilities.h"
#include "Console.h" #include "Console.h"
#include "Timer.h" #include "Timer.h"
#include "DynObj.h"
#include "MOVER.h" #include "MOVER.h"
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void TCamera::Init( Math3D::vector3 const &NPos, Math3D::vector3 const &NAngle, TCameraType const NType ) { void TCamera::Init( Math3D::vector3 const &NPos, Math3D::vector3 const &NAngle/*, TCameraType const NType*/, TDynamicObject *Owner ) {
vUp = { 0, 1, 0 }; vUp = { 0, 1, 0 };
Velocity = { 0, 0, 0 }; Velocity = { 0, 0, 0 };
Pitch = NAngle.x; Angle = NAngle;
Yaw = NAngle.y;
Roll = NAngle.z;
Pos = NPos; Pos = NPos;
Type = NType; m_owner = Owner;
}; };
void TCamera::Reset() { void TCamera::Reset() {
Pitch = Yaw = Roll = 0; Angle = {};
m_rotationoffsets = {}; m_rotationoffsets = {};
}; };
void TCamera::OnCursorMove(double x, double y) { void TCamera::OnCursorMove(double x, double y) {
/*
Yaw -= x;
while( Yaw > M_PI ) {
Yaw -= 2 * M_PI;
}
while( Yaw < -M_PI ) {
Yaw += 2 * M_PI;
}
Pitch -= y;
if (Type == tp_Follow) {
// jeżeli jazda z pojazdem ograniczenie kąta spoglądania w dół i w górę
Pitch = clamp( Pitch, -M_PI_4, M_PI_4 );
}
*/
m_rotationoffsets += glm::dvec3 { y, x, 0.0 }; m_rotationoffsets += glm::dvec3 { y, x, 0.0 };
} }
@@ -76,19 +62,21 @@ TCamera::OnCommand( command_data const &Command ) {
case user_command::movehorizontalfast: { case user_command::movehorizontalfast: {
auto const movespeed = ( auto const movespeed = (
Type == TCameraType::tp_Free ? runspeed : m_owner == nullptr ? runspeed : // free roam
Type == TCameraType::tp_Follow ? walkspeed : false == FreeFlyModeFlag ? walkspeed : // vehicle cab
0.0 ); 0.0 ); // vehicle external
// if( movespeed == 0.0 ) { break; } // enable to fix external cameras in place
auto const speedmultiplier = ( auto const speedmultiplier = (
( ( Type == TCameraType::tp_Free ) && ( Command.command == user_command::movehorizontalfast ) ) ? ( ( m_owner == nullptr ) && ( Command.command == user_command::movehorizontalfast ) ) ?
30.0 : 30.0 :
1.0 ); 1.0 );
// left-right // left-right
auto const movexparam { Command.param1 }; auto const movexparam { Command.param1 };
// 2/3rd of the stick range enables walk speed, past that we lerp between walk and run speed // 2/3rd of the stick range enables walk speed, past that we lerp between walk and run speed
auto const movex { walkspeed + ( std::max( 0.0, std::abs( movexparam ) - 0.65 ) / 0.35 ) * ( movespeed - walkspeed ) }; auto const movex { walkspeed + ( std::max( 0.0, std::abs( movexparam ) - 0.65 ) / 0.35 ) * std::max( 0.0, movespeed - walkspeed ) };
m_moverate.x = ( m_moverate.x = (
movexparam > 0.0 ? movex * speedmultiplier : movexparam > 0.0 ? movex * speedmultiplier :
@@ -97,7 +85,7 @@ TCamera::OnCommand( command_data const &Command ) {
// forward-back // forward-back
double const movezparam { Command.param2 }; double const movezparam { Command.param2 };
auto const movez { walkspeed + ( std::max( 0.0, std::abs( movezparam ) - 0.65 ) / 0.35 ) * ( movespeed - walkspeed ) }; auto const movez { walkspeed + ( std::max( 0.0, std::abs( movezparam ) - 0.65 ) / 0.35 ) * std::max( 0.0, movespeed - walkspeed ) };
// NOTE: z-axis is flipped given world coordinate system // NOTE: z-axis is flipped given world coordinate system
m_moverate.z = ( m_moverate.z = (
movezparam > 0.0 ? -movez * speedmultiplier : movezparam > 0.0 ? -movez * speedmultiplier :
@@ -111,15 +99,16 @@ TCamera::OnCommand( command_data const &Command ) {
case user_command::moveverticalfast: { case user_command::moveverticalfast: {
auto const movespeed = ( auto const movespeed = (
Type == TCameraType::tp_Free ? runspeed * 0.5 : m_owner == nullptr ? runspeed * 0.5 : // free roam
Type == TCameraType::tp_Follow ? walkspeed : false == FreeFlyModeFlag ? walkspeed : // vehicle cab
0.0 ); 0.0 ); // vehicle external
// if( movespeed == 0.0 ) { break; } // enable to fix external cameras in place
auto const speedmultiplier = ( auto const speedmultiplier = (
( ( Type == TCameraType::tp_Free ) && ( Command.command == user_command::moveverticalfast ) ) ? ( ( m_owner == nullptr ) && ( Command.command == user_command::moveverticalfast ) ) ?
10.0 : 10.0 :
1.0 ); 1.0 );
// up-down // up-down
auto const moveyparam { Command.param1 }; auto const moveyparam { Command.param1 };
// 2/3rd of the stick range enables walk speed, past that we lerp between walk and run speed // 2/3rd of the stick range enables walk speed, past that we lerp between walk and run speed
@@ -145,9 +134,6 @@ TCamera::OnCommand( command_data const &Command ) {
void TCamera::Update() void TCamera::Update()
{ {
if( FreeFlyModeFlag == true ) { Type = TCameraType::tp_Free; }
else { Type = TCameraType::tp_Follow; }
// check for sent user commands // check for sent user commands
// NOTE: this is a temporary arrangement, for the transition period from old command setup to the new one // NOTE: this is a temporary arrangement, for the transition period from old command setup to the new one
// ultimately we'll need to track position of camera/driver for all human entities present in the scenario // ultimately we'll need to track position of camera/driver for all human entities present in the scenario
@@ -161,8 +147,28 @@ void TCamera::Update()
auto const deltatime { Timer::GetDeltaRenderTime() }; // czas bez pauzy auto const deltatime { Timer::GetDeltaRenderTime() }; // czas bez pauzy
// update rotation
auto const rotationfactor { std::min( 1.0, 20 * deltatime ) };
Angle.y -= m_rotationoffsets.y * rotationfactor;
m_rotationoffsets.y *= ( 1.0 - rotationfactor );
while( Angle.y > M_PI ) {
Angle.y -= 2 * M_PI;
}
while( Angle.y < -M_PI ) {
Angle.y += 2 * M_PI;
}
Angle.x -= m_rotationoffsets.x * rotationfactor;
m_rotationoffsets.x *= ( 1.0 - rotationfactor );
if( m_owner != nullptr ) {
// jeżeli jazda z pojazdem ograniczenie kąta spoglądania w dół i w górę
Angle.x = clamp( Angle.x, -M_PI_4, M_PI_4 );
}
// update position // update position
if( ( Type == TCameraType::tp_Free ) if( ( m_owner == nullptr )
|| ( false == Global.ctrlState ) || ( false == Global.ctrlState )
|| ( true == DebugCameraFlag ) ) { || ( true == DebugCameraFlag ) ) {
// ctrl is used for mirror view, so we ignore the controls when in vehicle if ctrl is pressed // ctrl is used for mirror view, so we ignore the controls when in vehicle if ctrl is pressed
@@ -171,47 +177,34 @@ void TCamera::Update()
Velocity.z = clamp( Velocity.z + m_moverate.z * 10.0 * deltatime, -std::abs( m_moverate.z ), std::abs( m_moverate.z ) ); Velocity.z = clamp( Velocity.z + m_moverate.z * 10.0 * deltatime, -std::abs( m_moverate.z ), std::abs( m_moverate.z ) );
Velocity.y = clamp( Velocity.y + m_moverate.y * 10.0 * deltatime, -std::abs( m_moverate.y ), std::abs( m_moverate.y ) ); Velocity.y = clamp( Velocity.y + m_moverate.y * 10.0 * deltatime, -std::abs( m_moverate.y ), std::abs( m_moverate.y ) );
} }
if( ( m_owner == nullptr )
if( ( Type == TCameraType::tp_Free )
|| ( true == DebugCameraFlag ) ) { || ( true == DebugCameraFlag ) ) {
// free movement position update is handled here, movement while in vehicle is handled by train update // free movement position update
Math3D::vector3 Vec = Velocity; auto movement { Velocity };
Vec.RotateY( Yaw ); movement.RotateY( Angle.y );
Pos += Vec * 5.0 * deltatime; Pos += movement * 5.0 * deltatime;
} }
// update rotation else {
auto const rotationfactor { std::min( 1.0, 20 * deltatime ) }; // attached movement position update
auto movement { Velocity * -2.0 };
movement.y = -movement.y;
if( m_owner->MoverParameters->ActiveCab < 0 ) {
movement *= -1.f;
movement.y = -movement.y;
}
movement.RotateY( Angle.y );
Yaw -= rotationfactor * m_rotationoffsets.y; m_owneroffset += movement * deltatime;
m_rotationoffsets.y *= ( 1.0 - rotationfactor );
while( Yaw > M_PI ) {
Yaw -= 2 * M_PI;
} }
while( Yaw < -M_PI ) {
Yaw += 2 * M_PI;
}
Pitch -= rotationfactor * m_rotationoffsets.x;
m_rotationoffsets.x *= ( 1.0 - rotationfactor );
if( Type == TCameraType::tp_Follow ) {
// jeżeli jazda z pojazdem ograniczenie kąta spoglądania w dół i w górę
Pitch = clamp( Pitch, -M_PI_4, M_PI_4 );
}
}
Math3D::vector3 TCamera::GetDirection() {
glm::vec3 v = glm::normalize( glm::rotateY<float>( glm::vec3{ 0.f, 0.f, 1.f }, Yaw ) );
return Math3D::vector3(v.x, v.y, v.z);
} }
bool TCamera::SetMatrix( glm::dmat4 &Matrix ) { bool TCamera::SetMatrix( glm::dmat4 &Matrix ) {
Matrix = glm::rotate( Matrix, -Roll, glm::dvec3( 0.0, 0.0, 1.0 ) ); // po wyłączeniu tego kręci się pojazd, a sceneria nie Matrix = glm::rotate( Matrix, -Angle.z, glm::dvec3( 0.0, 0.0, 1.0 ) ); // po wyłączeniu tego kręci się pojazd, a sceneria nie
Matrix = glm::rotate( Matrix, -Pitch, glm::dvec3( 1.0, 0.0, 0.0 ) ); Matrix = glm::rotate( Matrix, -Angle.x, glm::dvec3( 1.0, 0.0, 0.0 ) );
Matrix = glm::rotate( Matrix, -Yaw, glm::dvec3( 0.0, 1.0, 0.0 ) ); // w zewnętrznym widoku: kierunek patrzenia Matrix = glm::rotate( Matrix, -Angle.y, glm::dvec3( 0.0, 1.0, 0.0 ) ); // w zewnętrznym widoku: kierunek patrzenia
if( ( Type == TCameraType::tp_Follow ) && ( false == DebugCameraFlag ) ) { if( ( m_owner != nullptr ) && ( false == DebugCameraFlag ) ) {
Matrix *= glm::lookAt( Matrix *= glm::lookAt(
glm::dvec3{ Pos }, glm::dvec3{ Pos },
@@ -227,14 +220,14 @@ bool TCamera::SetMatrix( glm::dmat4 &Matrix ) {
void TCamera::RaLook() void TCamera::RaLook()
{ // zmiana kierunku patrzenia - przelicza Yaw { // zmiana kierunku patrzenia - przelicza Yaw
Math3D::vector3 where = LookAt - Pos + Math3D::vector3(0, 3, 0); // trochę w górę od szyn Math3D::vector3 where = LookAt - Pos /*+ Math3D::vector3(0, 3, 0)*/; // trochę w górę od szyn
if( ( where.x != 0.0 ) || ( where.z != 0.0 ) ) { if( ( where.x != 0.0 ) || ( where.z != 0.0 ) ) {
Yaw = atan2( -where.x, -where.z ); // kąt horyzontalny Angle.y = atan2( -where.x, -where.z ); // kąt horyzontalny
m_rotationoffsets.y = 0.0; m_rotationoffsets.y = 0.0;
} }
double l = Math3D::Length3(where); double l = Math3D::Length3(where);
if( l > 0.0 ) { if( l > 0.0 ) {
Pitch = asin( where.y / l ); // kąt w pionie Angle.x = asin( where.y / l ); // kąt w pionie
m_rotationoffsets.x = 0.0; m_rotationoffsets.x = 0.0;
} }
}; };

View File

@@ -11,36 +11,30 @@ http://mozilla.org/MPL/2.0/.
#include "dumb3d.h" #include "dumb3d.h"
#include "command.h" #include "command.h"
#include "DynObj.h"
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
enum class TCameraType
{ // tryby pracy kamery
tp_Follow, // jazda z pojazdem
tp_Free, // stoi na scenerii
tp_Satelite // widok z góry (nie używany)
};
class TCamera { class TCamera {
public: // McZapkie: potrzebuje do kiwania na boki public: // McZapkie: potrzebuje do kiwania na boki
void Init( Math3D::vector3 const &Location, Math3D::vector3 const &Angle, TCameraType const Type ); void Init( Math3D::vector3 const &Location, Math3D::vector3 const &Angle, TDynamicObject *Owner );
void Reset(); void Reset();
void OnCursorMove(double const x, double const y); void OnCursorMove(double const x, double const y);
bool OnCommand( command_data const &Command ); bool OnCommand( command_data const &Command );
void Update(); void Update();
Math3D::vector3 GetDirection();
bool SetMatrix(glm::dmat4 &Matrix); bool SetMatrix(glm::dmat4 &Matrix);
void RaLook(); void RaLook();
TCameraType Type; Math3D::vector3 Angle; // pitch, yaw, roll
double Pitch;
double Yaw; // w środku: 0=do przodu; na zewnątrz: 0=na południe
double Roll;
Math3D::vector3 Pos; // współrzędne obserwatora Math3D::vector3 Pos; // współrzędne obserwatora
Math3D::vector3 LookAt; // współrzędne punktu, na który ma patrzeć Math3D::vector3 LookAt; // współrzędne punktu, na który ma patrzeć
Math3D::vector3 vUp; Math3D::vector3 vUp;
Math3D::vector3 Velocity; Math3D::vector3 Velocity;
TDynamicObject *m_owner { nullptr }; // TODO: change to const when shake calculations are part of vehicles update
Math3D::vector3 m_owneroffset {};
private: private:
glm::dvec3 m_moverate; glm::dvec3 m_moverate;
glm::dvec3 m_rotationoffsets; // requested changes to pitch, yaw and roll glm::dvec3 m_rotationoffsets; // requested changes to pitch, yaw and roll

View File

@@ -5152,6 +5152,24 @@ TController::UpdateSituation(double dt) {
( mvControlling->CategoryFlag == 2 ? -2.0 : -0.9 ), ( mvControlling->CategoryFlag == 2 ? -2.0 : -0.9 ),
( mvControlling->CategoryFlag == 2 ? 2.0 : 0.9 ) ); ( mvControlling->CategoryFlag == 2 ? 2.0 : 0.9 ) );
if ((-AccDesired > fBrake_a0[0] + 8 * fBrake_a1[0]) && (HelperState == 0))
{
HelperState = 1;
}
if ((-AccDesired > fBrake_a0[0] + 12 * fBrake_a1[0]) && (HelperState == 1))
{
HelperState = 2;
}
if ((-AccDesired > 0) && (HelperState == 2) && (-ActualProximityDist > 5))
{
HelperState = 3;
}
if ((-AccDesired < fBrake_a0[0] + 2 * fBrake_a1[0]) && (HelperState > 0) && (vel>1))
{
HelperState = 0;
}
if (AIControllFlag) { if (AIControllFlag) {
// część wykonawcza tylko dla AI, dla człowieka jedynie napisy // część wykonawcza tylko dla AI, dla człowieka jedynie napisy

View File

@@ -255,6 +255,7 @@ private:
sound_source tsGuardSignal { sound_placement::internal }; sound_source tsGuardSignal { sound_placement::internal };
std::array<int, 2> m_lighthints { -1 }; // suggested light patterns std::array<int, 2> m_lighthints { -1 }; // suggested light patterns
public: public:
int HelperState = 0; //stan pomocnika maszynisty
double AccPreferred = 0.0; // preferowane przyspieszenie (wg psychiki kierującego, zmniejszana przy wykryciu kolizji) double AccPreferred = 0.0; // preferowane przyspieszenie (wg psychiki kierującego, zmniejszana przy wykryciu kolizji)
double AccDesired = AccPreferred; // przyspieszenie, jakie ma utrzymywać (<0:nie przyspieszaj,<-0.1:hamuj) double AccDesired = AccPreferred; // przyspieszenie, jakie ma utrzymywać (<0:nie przyspieszaj,<-0.1:hamuj)
double VelDesired = 0.0; // predkość, z jaką ma jechać, wynikająca z analizy tableki; <=VelSignal double VelDesired = 0.0; // predkość, z jaką ma jechać, wynikająca z analizy tableki; <=VelSignal
@@ -427,4 +428,6 @@ private:
return iDirection; } return iDirection; }
TDynamicObject const *Vehicle() const { TDynamicObject const *Vehicle() const {
return pVehicle; } return pVehicle; }
TDynamicObject *Vehicle( side const Side ) const {
return pVehicles[ Side ]; }
}; };

View File

@@ -649,6 +649,22 @@ TDynamicObject::toggle_lights() {
} }
} }
void
TDynamicObject::set_cab_lights( float const Level ) {
if( Level == InteriorLightLevel ) { return; }
InteriorLightLevel = 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.find( "cab" ) != 0 ) { return; }
section.light_level = Level;
}
}
// ABu 29.01.05 przeklejone z render i renderalpha: ********************* // ABu 29.01.05 przeklejone z render i renderalpha: *********************
void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) void TDynamicObject::ABuLittleUpdate(double ObjSqrDist)
{ // ABu290105: pozbierane i uporzadkowane powtarzajace { // ABu290105: pozbierane i uporzadkowane powtarzajace
@@ -2094,6 +2110,10 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424"
create_controller( DriverType, !TrainName.empty() ); create_controller( DriverType, !TrainName.empty() );
ShakeSpring.Init( 125.0 );
BaseShake = baseshake_config {};
ShakeState = shake_state {};
// McZapkie-250202 // McZapkie-250202
/* /*
iAxles = std::min( MoverParameters->NAxles, MaxAxles ); // ilość osi iAxles = std::min( MoverParameters->NAxles, MaxAxles ); // ilość osi
@@ -4388,7 +4408,8 @@ void TDynamicObject::RenderSounds() {
0.0, 1.0, 0.0, 1.0,
clamp( clamp(
MoverParameters->Vel / 40.0, MoverParameters->Vel / 40.0,
0.0, 1.0 ) ); 0.0, 1.0 ) )
+ ( MyTrack->eType == tt_Switch ? 0.25 : 0.0 );
} }
else { else {
volume = 0; volume = 0;
@@ -5842,6 +5863,46 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co
m_startjolt.owner( this ); 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;
}
} while( token != "" ); } while( token != "" );
} // internaldata: } // internaldata:
@@ -6456,6 +6517,102 @@ TDynamicObject::ConnectedEnginePowerSource( TDynamicObject const *Caller ) const
return MoverParameters->EnginePowerSource.SourceType; return MoverParameters->EnginePowerSource.SourceType;
} }
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( false == Global.iSlowMotion ) { // musi być pełna prędkość
Math3D::vector3 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
* clamp(
( MoverParameters->enrot - EngineShake.fadein_offset ) * EngineShake.fadein_factor,
0.0, 1.0 )
// fade out with rpm above threshold
* interpolate(
1.0, 0.0,
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 =
interpolate(
0.0, 1.0,
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 );
}
auto const iVel { std::min( GetVelocity(), 150.0 ) };
if( iVel > 0.5 ) {
// acceleration-driven base shake
shakevector += Math3D::vector3(
-MoverParameters->AccN * Timedelta * 5.0, // highlight side sway
-MoverParameters->AccVert * Timedelta,
-MoverParameters->AccSVBased * Timedelta * 1.25 ); // accent acceleration/deceleration
}
auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) };
if( Random( iVel ) > 25.0 ) {
// extra shake at increased velocity
shake += ShakeSpring.ComputateForces(
Math3D::vector3(
( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.x,
( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.y,
( Random( 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 ) * ( BaseShake.jolt_scale.x + BaseShake.jolt_scale.y + BaseShake.jolt_scale.z ) / ( 200 );
// 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 void
TDynamicObject::powertrain_sounds::position( glm::vec3 const Location ) { TDynamicObject::powertrain_sounds::position( glm::vec3 const Location ) {

View File

@@ -20,6 +20,7 @@ http://mozilla.org/MPL/2.0/.
#include "AirCoupler.h" #include "AirCoupler.h"
#include "Texture.h" #include "Texture.h"
#include "sound.h" #include "sound.h"
#include "Spring.h"
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@@ -445,8 +446,6 @@ private:
exchange_data m_exchange; // state of active load exchange procedure, if any exchange_data m_exchange; // state of active load exchange procedure, if any
exchange_sounds m_exchangesounds; // sounds associated with the load exchange exchange_sounds m_exchangesounds; // sounds associated with the load exchange
Math3D::vector3 modelShake;
bool renderme; // yB - czy renderowac bool renderme; // yB - czy renderowac
float ModCamRot; float ModCamRot;
int iInventory[ 2 ] { 0, 0 }; // flagi bitowe posiadanych submodeli (np. świateł) int iInventory[ 2 ] { 0, 0 }; // flagi bitowe posiadanych submodeli (np. świateł)
@@ -541,6 +540,9 @@ private:
void RenderSounds(); void RenderSounds();
inline Math3D::vector3 GetPosition() const { inline Math3D::vector3 GetPosition() const {
return vPosition; }; return vPosition; };
// converts location from vehicle coordinates frame to world frame
inline Math3D::vector3 GetWorldPosition( Math3D::vector3 const &Location ) const {
return vPosition + mMatrix * Location; }
// pobranie współrzędnych czoła // pobranie współrzędnych czoła
inline Math3D::vector3 HeadPosition() { inline Math3D::vector3 HeadPosition() {
return vCoulpler[iDirection ^ 1]; }; return vCoulpler[iDirection ^ 1]; };
@@ -615,6 +617,7 @@ private:
void Damage(char flag); void Damage(char flag);
void RaLightsSet(int head, int rear); void RaLightsSet(int head, int rear);
int LightList( side const Side ) const { return iInventory[ Side ]; } int LightList( side const Side ) const { return iInventory[ Side ]; }
void set_cab_lights( float const Level );
TDynamicObject * FirstFind(int &coupler_nr, int cf = 1); TDynamicObject * FirstFind(int &coupler_nr, int cf = 1);
float GetEPP(); // wyliczanie sredniego cisnienia w PG float GetEPP(); // wyliczanie sredniego cisnienia w PG
int DirectionSet(int d); // ustawienie kierunku w składzie int DirectionSet(int d); // ustawienie kierunku w składzie
@@ -638,6 +641,41 @@ private:
double MEDLogTime = 0; double MEDLogTime = 0;
double MEDLogInactiveTime = 0; double MEDLogInactiveTime = 0;
int MEDLogCount = 0; int MEDLogCount = 0;
// vehicle shaking calculations
// TBD, TODO: make an object out of it
public:
// methods
void update_shake( double const Timedelta );
std::pair<double, double> shake_angles() const;
// members
struct baseshake_config {
Math3D::vector3 angle_scale { 0.05, 0.0, 0.1 }; // roll, yaw, pitch
Math3D::vector3 jolt_scale { 0.2, 0.2, 0.1 };
double jolt_limit { 0.15 };
} BaseShake;
struct engineshake_config {
float scale { 2.f };
float fadein_offset { 1.5f }; // 90 rpm
float fadein_factor { 0.3f };
float fadeout_offset { 10.f }; // 600 rpm
float fadeout_factor { 0.5f };
} EngineShake;
struct huntingshake_config {
float scale { 1.f };
float frequency { 1.f };
float fadein_begin { 0.f }; // effect start speed in km/h
float fadein_end { 0.f }; // full effect speed in km/h
} HuntingShake;
float HuntingAngle { 0.f }; // crude approximation of hunting oscillation; current angle of sine wave
bool IsHunting { false };
TSpring ShakeSpring;
struct shake_state {
Math3D::vector3 velocity {}; // current shaking vector
Math3D::vector3 offset {}; // overall shake-driven offset from base position
} ShakeState;
Math3D::vector3 modelShake;
}; };

View File

@@ -3609,7 +3609,7 @@ void TMoverParameters::UpdatePipePressure(double dt)
Hamulec->PLC( MaxBrakePress[ LoadFlag ] ); Hamulec->PLC( MaxBrakePress[ LoadFlag ] );
else else
//(Hamulec as TWest).PLC(TotalMass); //(Hamulec as TWest).PLC(TotalMass);
Hamulec->PLC( TotalMass ); Hamulec->PLC( TotalMass-Mred );
break; break;
} }
@@ -3639,7 +3639,7 @@ void TMoverParameters::UpdatePipePressure(double dt)
if( MBPM < 2 ) if( MBPM < 2 )
Hamulec->PLC( MaxBrakePress[ LoadFlag ] ); Hamulec->PLC( MaxBrakePress[ LoadFlag ] );
else else
Hamulec->PLC( TotalMass ); Hamulec->PLC( TotalMass-Mred );
break; break;
} }
@@ -3653,7 +3653,7 @@ void TMoverParameters::UpdatePipePressure(double dt)
case TBrakeValve::EP2: case TBrakeValve::EP2:
{ {
Hamulec->PLC( TotalMass ); Hamulec->PLC( TotalMass-Mred );
break; break;
} }
case TBrakeValve::ESt3AL2: case TBrakeValve::ESt3AL2:
@@ -3666,7 +3666,7 @@ void TMoverParameters::UpdatePipePressure(double dt)
Hamulec->PLC( MaxBrakePress[ LoadFlag ] ); Hamulec->PLC( MaxBrakePress[ LoadFlag ] );
else else
//(Hamulec as TNESt3).PLC(TotalMass); //(Hamulec as TNESt3).PLC(TotalMass);
Hamulec->PLC( TotalMass ); Hamulec->PLC( TotalMass-Mred );
LocBrakePress = LocHandle->GetCP(); LocBrakePress = LocHandle->GetCP();
//(Hamulec as TNESt3).SetLBP(LocBrakePress); //(Hamulec as TNESt3).SetLBP(LocBrakePress);
Hamulec->SetLBP( LocBrakePress ); Hamulec->SetLBP( LocBrakePress );
@@ -3682,7 +3682,7 @@ void TMoverParameters::UpdatePipePressure(double dt)
Hamulec->PLC( MaxBrakePress[ LoadFlag ] ); Hamulec->PLC( MaxBrakePress[ LoadFlag ] );
else else
//(Hamulec as TKE).PLC(TotalMass); //(Hamulec as TKE).PLC(TotalMass);
Hamulec->PLC( TotalMass ); Hamulec->PLC( TotalMass-Mred );
break; break;
} }
default: default:

253
Train.cpp
View File

@@ -365,8 +365,6 @@ TTrain::TTrain() {
fPPress = fNPress = 0; fPPress = fNPress = 0;
// asMessage=""; // asMessage="";
pMechShake = Math3D::vector3(0, 0, 0);
vMechMovement = Math3D::vector3(0, 0, 0);
pMechOffset = Math3D::vector3(0, 0, 0); pMechOffset = Math3D::vector3(0, 0, 0);
fBlinkTimer = 0; fBlinkTimer = 0;
fHaslerTimer = 0; fHaslerTimer = 0;
@@ -419,15 +417,6 @@ bool TTrain::Init(TDynamicObject *NewDynamicObject, bool e3d)
DynamicObject->MechInside = true; DynamicObject->MechInside = true;
MechSpring.Init(125.0);
vMechVelocity = Math3D::vector3(0, 0, 0);
pMechOffset = Math3D::vector3( 0, 0, 0 );
fMechSpringX = 0.2;
fMechSpringY = 0.2;
fMechSpringZ = 0.1;
fMechMaxSpring = 0.15;
fMechRoll = 0.05;
fMechPitch = 0.1;
fMainRelayTimer = 0; // Hunter, do k...y nędzy, ustawiaj wartości początkowe zmiennych! fMainRelayTimer = 0; // Hunter, do k...y nędzy, ustawiaj wartości początkowe zmiennych!
if( false == LoadMMediaFile( DynamicObject->asBaseDir + DynamicObject->MoverParameters->TypeName + ".mmd" ) ) { if( false == LoadMMediaFile( DynamicObject->asBaseDir + DynamicObject->MoverParameters->TypeName + ".mmd" ) ) {
@@ -4718,144 +4707,25 @@ void TTrain::OnCommand_cabchangebackward( TTrain *Train, command_data const &Com
} }
// cab movement update, fixed step part // cab movement update, fixed step part
void TTrain::UpdateMechPosition(double dt) void TTrain::UpdateCab() {
{ // Ra: mechanik powinien być
// telepany niezależnie od pozycji
// pojazdu
// Ra: trzeba zrobić model bujania głową i wczepić go do pojazdu
// DynamicObject->vFront=DynamicObject->GetDirection(); //to jest już // Ra: przesiadka, jeśli AI zmieniło kabinę (a człon?)...
// policzone if( ( DynamicObject->Mechanik ) // może nie być?
&& ( DynamicObject->Mechanik->AIControllFlag ) ) {
// Ra: tu by się przydało uwzględnić rozkład sił: if( iCabn != ( // numer kabiny (-1: kabina B)
// - na postoju horyzont prosto, kabina skosem DynamicObject->MoverParameters->ActiveCab == -1 ?
// - przy szybkiej jeździe kabina prosto, horyzont pochylony
Math3D::vector3 shake;
// 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
double const iVel = std::min( DynamicObject->GetVelocity(), 150.0 );
if( ( false == Global.iSlowMotion ) // musi być pełna prędkość
&& ( pMechOffset.y < 4.0 ) ) // Ra 15-01: przy oglądaniu pantografu bujanie przeszkadza
{
Math3D::vector3 shakevector;
if( ( mvOccupied->EngineType == TEngineType::DieselElectric )
|| ( mvOccupied->EngineType == TEngineType::DieselEngine ) ) {
if( std::abs( mvOccupied->enrot ) > 0.0 ) {
// engine vibration
shakevector.x +=
( std::sin( mvOccupied->eAngle * 4.0 ) * dt * EngineShake.scale )
// fade in with rpm above threshold
* clamp(
( mvOccupied->enrot - EngineShake.fadein_offset ) * EngineShake.fadein_factor,
0.0, 1.0 )
// fade out with rpm above threshold
* interpolate(
1.0, 0.0,
clamp(
( mvOccupied->enrot - EngineShake.fadeout_offset ) * EngineShake.fadeout_factor,
0.0, 1.0 ) );
}
}
if( ( HuntingShake.fadein_begin > 0.f )
&& ( true == mvOccupied->TruckHunting ) ) {
// hunting oscillation
HuntingAngle = clamp_circular( HuntingAngle + 4.0 * HuntingShake.frequency * dt * mvOccupied->Vel, 360.0 );
auto const huntingamount =
interpolate(
0.0, 1.0,
clamp(
( mvOccupied->Vel - HuntingShake.fadein_begin ) / ( HuntingShake.fadein_end - HuntingShake.fadein_begin ),
0.0, 1.0 ) );
shakevector.x +=
( std::sin( glm::radians( HuntingAngle ) ) * dt * HuntingShake.scale )
* huntingamount;
IsHunting = ( huntingamount > 0.025 );
}
if( iVel > 0.5 ) {
// acceleration-driven base shake
shakevector += Math3D::vector3(
-mvOccupied->AccN * dt * 5.0, // highlight side sway
-mvOccupied->AccVert * dt,
-mvOccupied->AccSVBased * dt * 1.25 ); // accent acceleration/deceleration
}
shake += 1.25 * MechSpring.ComputateForces( shakevector, pMechShake );
if( Random( iVel ) > 25.0 ) {
// extra shake at increased velocity
shake += MechSpring.ComputateForces(
Math3D::vector3(
( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * fMechSpringX,
( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * fMechSpringY,
( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * fMechSpringZ )
* 1.25,
pMechShake );
// * (( 200 - DynamicObject->MyTrack->iQualityFlag ) * 0.0075 ); // scale to 75-150% based on track quality
}
shake *= 0.85;
vMechVelocity -= ( shake + vMechVelocity * 100 ) * ( fMechSpringX + fMechSpringY + fMechSpringZ ) / ( 200 );
// shake *= 0.95 * dt; // shake damping
// McZapkie:
pMechShake += vMechVelocity * dt;
if( ( pMechShake.y > fMechMaxSpring )
|| ( pMechShake.y < -fMechMaxSpring ) ) {
vMechVelocity.y = -vMechVelocity.y;
}
// Ra 2015-01: dotychczasowe rzucanie
pMechOffset += vMechMovement * dt;
// ABu011104: 5*pMechShake.y, zeby ladnie pudlem rzucalo :)
pMechPosition = pMechOffset + Math3D::vector3( 1.5 * pMechShake.x, 2.0 * pMechShake.y, 1.5 * pMechShake.z );
// pMechShake = interpolate( pMechShake, Math3D::vector3(), clamp( dt, 0.0, 1.0 ) );
}
else { // hamowanie rzucania przy spadku FPS
pMechShake -= pMechShake * std::min( dt, 1.0 ); // po tym chyba potrafią zostać jakieś ułamki, które powodują zjazd
pMechOffset += vMechMovement * dt;
vMechVelocity.y = 0.5 * vMechVelocity.y;
pMechPosition = pMechOffset + Math3D::vector3( pMechShake.x, 5 * pMechShake.y, pMechShake.z );
}
// numer kabiny (-1: kabina B)
if( DynamicObject->Mechanik ) // może nie być?
if( DynamicObject->Mechanik->AIControllFlag ) // jeśli prowadzi AI
{ // Ra: przesiadka, jeśli AI zmieniło kabinę (a człon?)...
if( iCabn != ( DynamicObject->MoverParameters->ActiveCab == -1 ?
2 : 2 :
DynamicObject->MoverParameters->ActiveCab ) ) DynamicObject->MoverParameters->ActiveCab ) ) {
InitializeCab( DynamicObject->MoverParameters->ActiveCab,
DynamicObject->asBaseDir + DynamicObject->MoverParameters->TypeName + InitializeCab(
".mmd" ); DynamicObject->MoverParameters->ActiveCab,
DynamicObject->asBaseDir + DynamicObject->MoverParameters->TypeName + ".mmd" );
}
} }
iCabn = ( DynamicObject->MoverParameters->ActiveCab == -1 ? iCabn = ( DynamicObject->MoverParameters->ActiveCab == -1 ?
2 : 2 :
DynamicObject->MoverParameters->ActiveCab ); DynamicObject->MoverParameters->ActiveCab );
if( !DebugModeFlag ) { // sprawdzaj więzy //Ra: nie tu!
pMechPosition.x = clamp( pMechPosition.x, Cabine[ iCabn ].CabPos1.x, Cabine[ iCabn ].CabPos2.x );
pMechPosition.y = clamp( pMechPosition.y, Cabine[ iCabn ].CabPos1.y + 0.5, Cabine[ iCabn ].CabPos2.y + 1.8 );
pMechPosition.z = clamp( pMechPosition.z, Cabine[ iCabn ].CabPos1.z, Cabine[ iCabn ].CabPos2.z );
pMechOffset.x = clamp( pMechOffset.x, Cabine[ iCabn ].CabPos1.x, Cabine[ iCabn ].CabPos2.x );
pMechOffset.y = clamp( pMechOffset.y, Cabine[ iCabn ].CabPos1.y + 0.5, Cabine[ iCabn ].CabPos2.y + 1.8 );
pMechOffset.z = clamp( pMechOffset.z, Cabine[ iCabn ].CabPos1.z, Cabine[ iCabn ].CabPos2.z );
}
};
// returns position of the mechanic in the scene coordinates
Math3D::vector3
TTrain::GetWorldMechPosition() {
auto position = DynamicObject->mMatrix * pMechPosition; // położenie względem środka pojazdu w układzie scenerii
position += DynamicObject->GetPosition();
return position;
} }
bool TTrain::Update( double const Deltatime ) bool TTrain::Update( double const Deltatime )
@@ -4936,17 +4806,7 @@ bool TTrain::Update( double const Deltatime )
} }
} }
// update driver's position UpdateCab();
{
auto Vec = Global.pCamera.Velocity * -2.0;// -7.5 * Timer::GetDeltaRenderTime();
Vec.y = -Vec.y;
if( mvOccupied->ActiveCab < 0 ) {
Vec *= -1.0f;
Vec.y = -Vec.y;
}
Vec.RotateY( Global.pCamera.Yaw );
vMechMovement = Vec;
}
if (DynamicObject->mdKabina) if (DynamicObject->mdKabina)
{ // Ra: TODO: odczyty klawiatury/pulpitu nie powinny być uzależnione od istnienia modelu kabiny { // Ra: TODO: odczyty klawiatury/pulpitu nie powinny być uzależnione od istnienia modelu kabiny
@@ -5877,6 +5737,8 @@ bool TTrain::Update( double const Deltatime )
ggHornLowButton.Update(); ggHornLowButton.Update();
ggHornHighButton.Update(); ggHornHighButton.Update();
ggWhistleButton.Update(); ggWhistleButton.Update();
ggHelperButton.UpdateValue(DynamicObject->Mechanik->HelperState);
ggHelperButton.Update();
for( auto &universal : ggUniversals ) { for( auto &universal : ggUniversals ) {
universal.Update(); universal.Update();
} }
@@ -5906,11 +5768,12 @@ bool TTrain::Update( double const Deltatime )
// calculate current level of interior illumination // calculate current level of interior illumination
// TODO: organize it along with rest of train update in a more sensible arrangement // TODO: organize it along with rest of train update in a more sensible arrangement
auto interiorlightlevel { 0.f };
switch( iCabLightFlag ) // Ra: uzeleżnic od napięcia w obwodzie sterowania switch( iCabLightFlag ) // Ra: uzeleżnic od napięcia w obwodzie sterowania
{ // hunter-091012: uzaleznienie jasnosci od przetwornicy { // hunter-091012: uzaleznienie jasnosci od przetwornicy
case 0: { case 0: {
//światło wewnętrzne zgaszone //światło wewnętrzne zgaszone
DynamicObject->InteriorLightLevel = 0.0f; interiorlightlevel = 0.0f;
break; break;
} }
case 1: { case 1: {
@@ -5920,7 +5783,7 @@ bool TTrain::Update( double const Deltatime )
|| ( ( ( mvOccupied->Couplers[ side::front ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::front ].Connected->ConverterFlag ) || ( ( ( mvOccupied->Couplers[ side::front ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::front ].Connected->ConverterFlag )
|| ( ( ( mvOccupied->Couplers[ side::rear ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::rear ].Connected->ConverterFlag ) ) }; || ( ( ( mvOccupied->Couplers[ side::rear ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::rear ].Connected->ConverterFlag ) ) };
DynamicObject->InteriorLightLevel = ( interiorlightlevel = (
converteractive ? converteractive ?
0.4f : 0.4f :
0.2f ); 0.2f );
@@ -5933,13 +5796,14 @@ bool TTrain::Update( double const Deltatime )
|| ( ( ( mvOccupied->Couplers[ side::front ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::front ].Connected->ConverterFlag ) || ( ( ( mvOccupied->Couplers[ side::front ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::front ].Connected->ConverterFlag )
|| ( ( ( mvOccupied->Couplers[ side::rear ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::rear ].Connected->ConverterFlag ) ) }; || ( ( ( mvOccupied->Couplers[ side::rear ].CouplingFlag & coupling::permanent ) != 0 ) && mvOccupied->Couplers[ side::rear ].Connected->ConverterFlag ) ) };
DynamicObject->InteriorLightLevel = ( interiorlightlevel = (
converteractive ? converteractive ?
1.0f : 1.0f :
0.5f ); 0.5f );
break; break;
} }
} }
DynamicObject->set_cab_lights( interiorlightlevel );
// anti slip system activation, maintained while the control button is down // anti slip system activation, maintained while the control button is down
if( mvOccupied->BrakeSystem != TBrakeSystem::ElectroPneumatic ) { if( mvOccupied->BrakeSystem != TBrakeSystem::ElectroPneumatic ) {
@@ -6168,7 +6032,7 @@ TTrain::update_sounds( double const Deltatime ) {
if( ( false == FreeFlyModeFlag ) if( ( false == FreeFlyModeFlag )
&& ( false == Global.CabWindowOpen ) && ( false == Global.CabWindowOpen )
&& ( DynamicObject->GetVelocity() > 0.5 ) && ( DynamicObject->GetVelocity() > 0.5 )
&& ( IsHunting ) ) { && ( DynamicObject->IsHunting ) ) {
update_sounds_runningnoise( rsHuntingNoise ); update_sounds_runningnoise( rsHuntingNoise );
// modify calculated sound volume by hunting amount // modify calculated sound volume by hunting amount
@@ -6176,7 +6040,7 @@ TTrain::update_sounds( double const Deltatime ) {
interpolate( interpolate(
0.0, 1.0, 0.0, 1.0,
clamp( clamp(
( mvOccupied->Vel - HuntingShake.fadein_begin ) / ( HuntingShake.fadein_end - HuntingShake.fadein_begin ), ( mvOccupied->Vel - DynamicObject->HuntingShake.fadein_begin ) / ( DynamicObject->HuntingShake.fadein_end - DynamicObject->HuntingShake.fadein_begin ),
0.0, 1.0 ) ); 0.0, 1.0 ) );
rsHuntingNoise.gain( rsHuntingNoise.gain() * huntingamount ); rsHuntingNoise.gain( rsHuntingNoise.gain() * huntingamount );
@@ -6494,44 +6358,6 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName)
rsHuntingNoise.m_amplitudefactor /= ( 1 + mvOccupied->Vmax ); rsHuntingNoise.m_amplitudefactor /= ( 1 + mvOccupied->Vmax );
rsHuntingNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax ); rsHuntingNoise.m_frequencyfactor /= ( 1 + mvOccupied->Vmax );
} }
else if (token == "mechspring:")
{
// parametry bujania kamery:
double ks, kd;
parser.getTokens(2, false);
parser
>> ks
>> kd;
MechSpring.Init(ks, kd);
parser.getTokens(6, false);
parser
>> fMechSpringX
>> fMechSpringY
>> fMechSpringZ
>> fMechMaxSpring
>> fMechRoll
>> fMechPitch;
}
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;
}
} while (token != ""); } while (token != "");
} }
@@ -6576,8 +6402,6 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
} }
// reset view angles // reset view angles
pMechViewAngle = { 0.0, 0.0 }; pMechViewAngle = { 0.0, 0.0 };
Global.pCamera.Pitch = pMechViewAngle.x;
Global.pCamera.Yaw = pMechViewAngle.y;
bool parse = false; bool parse = false;
int cabindex = 0; int cabindex = 0;
DynamicObject->mdKabina = NULL; // likwidacja wskaźnika na dotychczasową kabinę DynamicObject->mdKabina = NULL; // likwidacja wskaźnika na dotychczasową kabinę
@@ -6675,9 +6499,10 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
>> viewangle.y // yaw first, then pitch >> viewangle.y // yaw first, then pitch
>> viewangle.x; >> viewangle.x;
pMechViewAngle = glm::radians( viewangle ); pMechViewAngle = glm::radians( viewangle );
/*
Global.pCamera.Pitch = pMechViewAngle.x; Global.pCamera.Pitch = pMechViewAngle.x;
Global.pCamera.Yaw = pMechViewAngle.y; Global.pCamera.Yaw = pMechViewAngle.y;
*/
parser.getTokens(); parser.getTokens();
parser >> token; parser >> token;
} }
@@ -6868,14 +6693,6 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
return (token == "none"); return (token == "none");
} }
void TTrain::MechStop()
{ // likwidacja ruchu kamery w kabinie (po powrocie przez [F4])
pMechPosition = Math3D::vector3(0, 0, 0);
pMechShake = Math3D::vector3(0, 0, 0);
vMechMovement = Math3D::vector3(0, 0, 0);
vMechVelocity = Math3D::vector3(0, 0, 0); // tu zostawały jakieś ułamki, powodujące uciekanie kamery
};
Math3D::vector3 TTrain::MirrorPosition(bool lewe) Math3D::vector3 TTrain::MirrorPosition(bool lewe)
{ // zwraca współrzędne widoku kamery z lusterka { // zwraca współrzędne widoku kamery z lusterka
switch (iCabn) switch (iCabn)
@@ -6953,6 +6770,26 @@ void TTrain::DynamicSet(TDynamicObject *d)
} }
}; };
// checks whether specified point is within boundaries of the active cab
bool
TTrain::point_inside( Math3D::vector3 const Point ) const {
return ( Point.x >= Cabine[ iCabn ].CabPos1.x ) && ( Point.x <= Cabine[ iCabn ].CabPos2.x )
&& ( Point.y >= Cabine[ iCabn ].CabPos1.y + 0.5 ) && ( Point.y <= Cabine[ iCabn ].CabPos2.y + 1.8 )
&& ( Point.z >= Cabine[ iCabn ].CabPos1.z ) && ( Point.z <= Cabine[ iCabn ].CabPos2.z );
}
Math3D::vector3
TTrain::clamp_inside( Math3D::vector3 const &Point ) const {
if( DebugModeFlag ) { return Point; }
return {
clamp( Point.x, Cabine[ iCabn ].CabPos1.x, Cabine[ iCabn ].CabPos2.x ),
clamp( Point.y, Cabine[ iCabn ].CabPos1.y + 0.5, Cabine[ iCabn ].CabPos2.y + 1.8 ),
clamp( Point.z, Cabine[ iCabn ].CabPos1.z, Cabine[ iCabn ].CabPos2.z ) };
}
void void
TTrain::radio_message( sound_source *Message, int const Channel ) { TTrain::radio_message( sound_source *Message, int const Channel ) {
@@ -7058,6 +6895,7 @@ void TTrain::clear_cab_controls()
ggHornLowButton.Clear(); ggHornLowButton.Clear();
ggHornHighButton.Clear(); ggHornHighButton.Clear();
ggWhistleButton.Clear(); ggWhistleButton.Clear();
ggHelperButton.Clear();
ggNextCurrentButton.Clear(); ggNextCurrentButton.Clear();
for( auto &universal : ggUniversals ) { for( auto &universal : ggUniversals ) {
universal.Clear(); universal.Clear();
@@ -7653,6 +7491,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con
{ "hornlow_bt:", ggHornLowButton }, { "hornlow_bt:", ggHornLowButton },
{ "hornhigh_bt:", ggHornHighButton }, { "hornhigh_bt:", ggHornHighButton },
{ "whistle_bt:", ggWhistleButton }, { "whistle_bt:", ggWhistleButton },
{ "helper_bt:", ggHelperButton },
{ "fuse_bt:", ggFuseButton }, { "fuse_bt:", ggFuseButton },
{ "converterfuse_bt:", ggConverterFuseButton }, { "converterfuse_bt:", ggConverterFuseButton },
{ "stlinoff_bt:", ggStLinOffButton }, { "stlinoff_bt:", ggStLinOffButton },

48
Train.h
View File

@@ -13,7 +13,6 @@ http://mozilla.org/MPL/2.0/.
#include "DynObj.h" #include "DynObj.h"
#include "Button.h" #include "Button.h"
#include "Gauge.h" #include "Gauge.h"
#include "Spring.h"
#include "sound.h" #include "sound.h"
#include "PyInt.h" #include "PyInt.h"
#include "command.h" #include "command.h"
@@ -106,10 +105,8 @@ class TTrain
inline Math3D::vector3 GetDirection() { return DynamicObject->VectorFront(); }; inline Math3D::vector3 GetDirection() { return DynamicObject->VectorFront(); };
inline Math3D::vector3 GetUp() { return DynamicObject->VectorUp(); }; inline Math3D::vector3 GetUp() { return DynamicObject->VectorUp(); };
inline std::string GetLabel( TSubModel const *Control ) const { return m_controlmapper.find( Control ); } inline std::string GetLabel( TSubModel const *Control ) const { return m_controlmapper.find( Control ); }
void UpdateMechPosition(double dt); void UpdateCab();
Math3D::vector3 GetWorldMechPosition();
bool Update( double const Deltatime ); bool Update( double const Deltatime );
void MechStop();
void SetLights(); void SetLights();
// McZapkie-310302: ladowanie parametrow z pliku // McZapkie-310302: ladowanie parametrow z pliku
bool LoadMMediaFile(std::string const &asFileName); bool LoadMMediaFile(std::string const &asFileName);
@@ -423,6 +420,7 @@ public: // reszta może by?publiczna
TGauge ggHornLowButton; TGauge ggHornLowButton;
TGauge ggHornHighButton; TGauge ggHornHighButton;
TGauge ggWhistleButton; TGauge ggWhistleButton;
TGauge ggHelperButton;
TGauge ggNextCurrentButton; TGauge ggNextCurrentButton;
std::array<TGauge, 10> ggUniversals; // NOTE: temporary arrangement until we have dynamically built control table std::array<TGauge, 10> ggUniversals; // NOTE: temporary arrangement until we have dynamically built control table
@@ -565,40 +563,6 @@ public: // reszta może by?publiczna
// Ra 2013-12: wirtualne "lampki" do odbijania na haslerze w PoKeys // Ra 2013-12: wirtualne "lampki" do odbijania na haslerze w PoKeys
TButton btHaslerBrakes; // ciśnienie w cylindrach TButton btHaslerBrakes; // ciśnienie w cylindrach
TButton btHaslerCurrent; // prąd na silnikach TButton btHaslerCurrent; // prąd na silnikach
/*
vector3 pPosition;
*/
Math3D::vector3 pMechOffset; // driverNpos
Math3D::vector3 vMechMovement;
Math3D::vector3 pMechPosition;
Math3D::vector3 pMechShake;
Math3D::vector3 vMechVelocity;
// McZapkie: do poruszania sie po kabinie
// McZapkie: opis kabiny - obszar poruszania sie mechanika oraz zajetosc
TCab Cabine[maxcab + 1]; // przedzial maszynowy, kabina 1 (A), kabina 2 (B)
int iCabn;
TSpring MechSpring;
double fMechSpringX; // McZapkie-250303: parametry bujania
double fMechSpringY;
double fMechSpringZ;
double fMechMaxSpring;
double fMechRoll;
double fMechPitch;
struct engineshake_config {
float scale { 2.f };
float fadein_offset { 1.5f }; // 90 rpm
float fadein_factor { 0.3f };
float fadeout_offset { 10.f }; // 600 rpm
float fadeout_factor { 0.5f };
} EngineShake;
struct huntingshake_config {
float scale { 1.f };
float frequency { 1.f };
float fadein_begin { 0.f }; // effect start speed in km/h
float fadein_end { 0.f }; // full effect speed in km/h
} HuntingShake;
float HuntingAngle { 0.f }; // crude approximation of hunting oscillation; current angle of sine wave
bool IsHunting { false };
sound_source dsbReverserKey { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // hunter-121211 sound_source dsbReverserKey { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // hunter-121211
sound_source dsbNastawnikJazdy { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE }; sound_source dsbNastawnikJazdy { sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE };
@@ -631,8 +595,13 @@ public: // reszta może by?publiczna
bool bCabLight; // hunter-091012: czy swiatlo jest zapalone? bool bCabLight; // hunter-091012: czy swiatlo jest zapalone?
bool bCabLightDim; // hunter-091012: czy przyciemnienie kabiny jest zapalone? bool bCabLightDim; // hunter-091012: czy przyciemnienie kabiny jest zapalone?
// McZapkie: opis kabiny - obszar poruszania sie mechanika oraz zajetosc
TCab Cabine[ maxcab + 1 ]; // przedzial maszynowy, kabina 1 (A), kabina 2 (B)
int iCabn;
// McZapkie: do poruszania sie po kabinie
Math3D::vector3 pMechSittingPosition; // ABu 180404 Math3D::vector3 pMechSittingPosition; // ABu 180404
Math3D::vector3 MirrorPosition( bool lewe ); Math3D::vector3 MirrorPosition( bool lewe );
Math3D::vector3 pMechOffset; // base position of the driver in the cab
glm::vec2 pMechViewAngle { 0.0, 0.0 }; // camera pitch and yaw values, preserved while in external view glm::vec2 pMechViewAngle { 0.0, 0.0 }; // camera pitch and yaw values, preserved while in external view
private: private:
@@ -687,6 +656,9 @@ private:
inline TMoverParameters *Occupied() { return mvOccupied; }; inline TMoverParameters *Occupied() { return mvOccupied; };
inline TMoverParameters const *Occupied() const { return mvOccupied; }; inline TMoverParameters const *Occupied() const { return mvOccupied; };
void DynamicSet(TDynamicObject *d); void DynamicSet(TDynamicObject *d);
// checks whether specified point is within boundaries of the active cab
bool point_inside( Math3D::vector3 const Point ) const;
Math3D::vector3 clamp_inside( Math3D::vector3 const &Point ) const;
float get_tacho(); float get_tacho();
float get_tank_pressure(); float get_tank_pressure();

View File

@@ -169,12 +169,9 @@ driver_mode::update() {
iPause = Global.iPause; iPause = Global.iPause;
} }
// fixed step part of the camera update // TODO: generic shake update pass for vehicles within view range
if( ( simulation::Train != nullptr ) if( Camera.m_owner != nullptr ) {
&& ( Camera.Type == TCameraType::tp_Follow ) Camera.m_owner->update_shake( m_secondaryupdaterate );
&& ( false == DebugCameraFlag ) ) {
// jeśli jazda w kabinie, przeliczyć trzeba parametry kamery
simulation::Train->UpdateMechPosition( m_secondaryupdaterate );
} }
m_secondaryupdateaccumulator -= m_secondaryupdaterate; // these should be inexpensive enough we have no cap m_secondaryupdateaccumulator -= m_secondaryupdaterate; // these should be inexpensive enough we have no cap
@@ -253,15 +250,15 @@ driver_mode::update() {
void void
driver_mode::enter() { driver_mode::enter() {
Camera.Init(Global.FreeCameraInit[0], Global.FreeCameraInitAngle[0], ( FreeFlyModeFlag ? TCameraType::tp_Free : TCameraType::tp_Follow ) );
Global.pCamera = Camera;
Global.pDebugCamera = DebugCamera;
TDynamicObject *nPlayerTrain { ( TDynamicObject *nPlayerTrain { (
( Global.asHumanCtrlVehicle != "ghostview" ) ? ( Global.asHumanCtrlVehicle != "ghostview" ) ?
simulation::Vehicles.find( Global.asHumanCtrlVehicle ) : simulation::Vehicles.find( Global.asHumanCtrlVehicle ) :
nullptr ) }; nullptr ) };
Camera.Init(Global.FreeCameraInit[0], Global.FreeCameraInitAngle[0], nPlayerTrain );
Global.pCamera = Camera;
Global.pDebugCamera = DebugCamera;
if (nPlayerTrain) if (nPlayerTrain)
{ {
WriteLog( "Initializing player train, \"" + Global.asHumanCtrlVehicle + "\"" ); WriteLog( "Initializing player train, \"" + Global.asHumanCtrlVehicle + "\"" );
@@ -275,14 +272,14 @@ driver_mode::enter() {
Application.set_title( Global.AppName + " (" + simulation::Train->Controlled()->Name + " @ " + Global.SceneryFile + ")" ); Application.set_title( Global.AppName + " (" + simulation::Train->Controlled()->Name + " @ " + Global.SceneryFile + ")" );
FollowView(); CabView();
} }
else else
{ {
Error("Bad init: player train initialization failed"); Error("Bad init: player train initialization failed");
FreeFlyModeFlag = true; // Ra: automatycznie włączone latanie FreeFlyModeFlag = true; // Ra: automatycznie włączone latanie
SafeDelete( simulation::Train ); SafeDelete( simulation::Train );
Camera.Type = TCameraType::tp_Free; Camera.m_owner = nullptr;
} }
} }
else else
@@ -292,7 +289,7 @@ driver_mode::enter() {
Error("Bad scenario: failed to locate player train, \"" + Global.asHumanCtrlVehicle + "\"" ); Error("Bad scenario: failed to locate player train, \"" + Global.asHumanCtrlVehicle + "\"" );
} }
FreeFlyModeFlag = true; // Ra: automatycznie włączone latanie FreeFlyModeFlag = true; // Ra: automatycznie włączone latanie
Camera.Type = TCameraType::tp_Free; Camera.m_owner = nullptr;
DebugCamera = Camera; DebugCamera = Camera;
} }
@@ -395,8 +392,10 @@ driver_mode::update_camera( double const Deltatime ) {
nullptr ); nullptr );
if( false == Global.ControlPicking ) { if( false == Global.ControlPicking ) {
if( m_input.mouse.button( GLFW_MOUSE_BUTTON_LEFT ) == GLFW_PRESS ) { if( m_input.mouse.button( GLFW_MOUSE_BUTTON_LEFT ) == GLFW_PRESS ) {
Camera.Reset(); // likwidacja obrotów - patrzy horyzontalnie na południe Camera.Reset(); // likwidacja obrotów - patrzy horyzontalnie na południe
if( Camera.m_owner == nullptr ) {
if( controlled && LengthSquared3( controlled->GetPosition() - Camera.Pos ) < ( 1500 * 1500 ) ) { if( controlled && LengthSquared3( controlled->GetPosition() - Camera.Pos ) < ( 1500 * 1500 ) ) {
// gdy bliżej niż 1.5km // gdy bliżej niż 1.5km
Camera.LookAt = controlled->GetPosition(); Camera.LookAt = controlled->GetPosition();
@@ -418,11 +417,17 @@ driver_mode::update_camera( double const Deltatime ) {
if( pDynamicNearest ) if( pDynamicNearest )
Camera.LookAt = pDynamicNearest->GetPosition(); Camera.LookAt = pDynamicNearest->GetPosition();
} }
if( FreeFlyModeFlag )
Camera.RaLook(); // jednorazowe przestawienie kamery Camera.RaLook(); // jednorazowe przestawienie kamery
} }
else {
if( false == FreeFlyModeFlag ) {
// reset cached view angle in the cab
simulation::Train->pMechViewAngle = { Camera.Angle.x, Camera.Angle.y };
}
}
}
else if( m_input.mouse.button( GLFW_MOUSE_BUTTON_RIGHT ) == GLFW_PRESS ) { else if( m_input.mouse.button( GLFW_MOUSE_BUTTON_RIGHT ) == GLFW_PRESS ) {
FollowView( false ); // bez wyciszania dźwięków CabView();
} }
} }
@@ -436,28 +441,49 @@ driver_mode::update_camera( double const Deltatime ) {
Global.ZoomFactor = std::max( 1.0f, Global.ZoomFactor - 15.0f * static_cast<float>( Deltatime ) ); Global.ZoomFactor = std::max( 1.0f, Global.ZoomFactor - 15.0f * static_cast<float>( Deltatime ) );
} }
if( DebugCameraFlag ) { DebugCamera.Update(); } // uwzględnienie ruchu wywołanego klawiszami
else { Camera.Update(); } // uwzględnienie ruchu wywołanego klawiszami if( false == DebugCameraFlag ) {
// regular camera
if( ( false == FreeFlyModeFlag )
&& ( false == Global.CabWindowOpen ) ) {
// if in cab potentially alter camera placement based on changes in train object
Camera.m_owneroffset = simulation::Train->pMechOffset;
Camera.Angle.x = simulation::Train->pMechViewAngle.x;
Camera.Angle.y = simulation::Train->pMechViewAngle.y;
}
Camera.Update();
if( false == FreeFlyModeFlag ) {
// keep the camera within cab boundaries
Camera.m_owneroffset = simulation::Train->clamp_inside( Camera.m_owneroffset );
}
if( ( false == FreeFlyModeFlag ) if( ( false == FreeFlyModeFlag )
&& ( false == Global.CabWindowOpen ) && ( false == Global.CabWindowOpen ) ) {
&& ( simulation::Train != nullptr ) ) { // cache cab camera in case of view type switch
// cache cab camera view angles in case of view type switch simulation::Train->pMechViewAngle = { Camera.Angle.x, Camera.Angle.y };
simulation::Train->pMechViewAngle = { Camera.Pitch, Camera.Yaw }; simulation::Train->pMechOffset = Camera.m_owneroffset;
}
}
else {
// debug camera
DebugCamera.Update();
} }
// reset window state, it'll be set again if applicable in a check below // reset window state, it'll be set again if applicable in a check below
Global.CabWindowOpen = false; Global.CabWindowOpen = false;
if( ( simulation::Train != nullptr ) if( ( simulation::Train != nullptr )
&& ( Camera.Type == TCameraType::tp_Follow ) && ( Camera.m_owner != nullptr )
&& ( false == DebugCameraFlag ) ) { && ( false == DebugCameraFlag ) ) {
// jeśli jazda w kabinie, przeliczyć trzeba parametry kamery // jeśli jazda w kabinie, przeliczyć trzeba parametry kamery
/* /*
auto tempangle = controlled->VectorFront() * ( controlled->MoverParameters->ActiveCab == -1 ? -1 : 1 ); auto tempangle = controlled->VectorFront() * ( controlled->MoverParameters->ActiveCab == -1 ? -1 : 1 );
double modelrotate = atan2( -tempangle.x, tempangle.z ); double modelrotate = atan2( -tempangle.x, tempangle.z );
*/ */
if( ( true == Global.ctrlState ) if( ( false == FreeFlyModeFlag )
&& ( true == Global.ctrlState )
&& ( ( m_input.keyboard.key( GLFW_KEY_LEFT ) != GLFW_RELEASE ) && ( ( m_input.keyboard.key( GLFW_KEY_LEFT ) != GLFW_RELEASE )
|| ( m_input.keyboard.key( GLFW_KEY_RIGHT ) != GLFW_RELEASE ) ) ) { || ( m_input.keyboard.key( GLFW_KEY_RIGHT ) != GLFW_RELEASE ) ) ) {
// jeśli lusterko lewe albo prawe (bez rzucania na razie) // jeśli lusterko lewe albo prawe (bez rzucania na razie)
@@ -466,9 +492,11 @@ driver_mode::update_camera( double const Deltatime ) {
auto const lr { m_input.keyboard.key( GLFW_KEY_LEFT ) != GLFW_RELEASE }; auto const lr { m_input.keyboard.key( GLFW_KEY_LEFT ) != GLFW_RELEASE };
// Camera.Yaw powinno być wyzerowane, aby po powrocie patrzeć do przodu // Camera.Yaw powinno być wyzerowane, aby po powrocie patrzeć do przodu
Camera.Pos = controlled->GetPosition() + simulation::Train->MirrorPosition( lr ); // pozycja lusterka Camera.Pos = controlled->GetPosition() + simulation::Train->MirrorPosition( lr ); // pozycja lusterka
Camera.Yaw = 0; // odchylenie na bok od Camera.LookAt Camera.Angle.y = 0; // odchylenie na bok od Camera.LookAt
if( simulation::Train->Occupied()->ActiveCab == 0 ) if( simulation::Train->Occupied()->ActiveCab == 0 ) {
Camera.LookAt = Camera.Pos - simulation::Train->GetDirection(); // gdy w korytarzu // gdy w korytarzu
Camera.LookAt = Camera.Pos - simulation::Train->GetDirection();
}
else if( Global.shiftState ) { else if( Global.shiftState ) {
// patrzenie w bok przez szybę // patrzenie w bok przez szybę
Camera.LookAt = Camera.Pos - ( lr ? -1 : 1 ) * controlled->VectorLeft() * simulation::Train->Occupied()->ActiveCab; Camera.LookAt = Camera.Pos - ( lr ? -1 : 1 ) * controlled->VectorLeft() * simulation::Train->Occupied()->ActiveCab;
@@ -477,25 +505,47 @@ driver_mode::update_camera( double const Deltatime ) {
// ale bez odbicia // ale bez odbicia
Camera.LookAt = Camera.Pos - simulation::Train->GetDirection() * simulation::Train->Occupied()->ActiveCab; //-1 albo 1 Camera.LookAt = Camera.Pos - simulation::Train->GetDirection() * simulation::Train->Occupied()->ActiveCab; //-1 albo 1
} }
Camera.Roll = std::atan( simulation::Train->pMechShake.x * simulation::Train->fMechRoll ); // hustanie kamery na boki auto const shakeangles { simulation::Train->Dynamic()->shake_angles() };
Camera.Pitch = 0.5 * std::atan( simulation::Train->vMechVelocity.z * simulation::Train->fMechPitch ); // hustanie kamery przod tyl Camera.Angle.x = 0.5 * shakeangles.second; // hustanie kamery przod tyl
Camera.Angle.z = shakeangles.first; // hustanie kamery na boki
/*
Camera.Roll = std::atan( simulation::Train->pMechShake.x * simulation::Train->BaseShake.angle_scale.x ); // hustanie kamery na boki
Camera.Pitch = 0.5 * std::atan( simulation::Train->vMechVelocity.z * simulation::Train->BaseShake.angle_scale.z ); // hustanie kamery przod tyl
*/
Camera.vUp = controlled->VectorUp(); Camera.vUp = controlled->VectorUp();
} }
else { else {
// patrzenie standardowe // patrzenie standardowe
if( false == FreeFlyModeFlag ) {
// potentially restore view angle after returning from external view // potentially restore view angle after returning from external view
// TODO: mirror view toggle as separate method // TODO: mirror view toggle as separate method
Camera.Pitch = simulation::Train->pMechViewAngle.x; Camera.Angle.x = simulation::Train->pMechViewAngle.x;
Camera.Yaw = simulation::Train->pMechViewAngle.y; Camera.Angle.y = simulation::Train->pMechViewAngle.y;
}
auto const shakescale { FreeFlyModeFlag ? 5.0 : 1.0 };
auto shakencamerapos {
Camera.m_owneroffset
+ shakescale * Math3D::vector3(
1.5 * Camera.m_owner->ShakeState.offset.x,
2.0 * Camera.m_owner->ShakeState.offset.y,
1.5 * Camera.m_owner->ShakeState.offset.z ) };
Camera.Pos = (
Camera.m_owner->GetWorldPosition (
FreeFlyModeFlag ?
shakencamerapos : // TODO: vehicle collision box for the external vehicle camera
simulation::Train->clamp_inside( shakencamerapos ) ) );
Camera.Pos = simulation::Train->GetWorldMechPosition(); // Train.GetPosition1();
if( !Global.iPause ) { if( !Global.iPause ) {
// podczas pauzy nie przeliczać kątów przypadkowymi wartościami // podczas pauzy nie przeliczać kątów przypadkowymi wartościami
// hustanie kamery na boki auto const shakeangles { Camera.m_owner->shake_angles() };
Camera.Roll = atan( simulation::Train->vMechVelocity.x * simulation::Train->fMechRoll ); Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl
// hustanie kamery przod tyl Camera.Angle.z = shakeangles.first; // hustanie kamery na boki
// Ra: tu jest uciekanie kamery w górę!!! /*
Camera.Pitch -= 0.5 * atan( simulation::Train->vMechVelocity.z * simulation::Train->fMechPitch ); Camera.Roll = std::atan( simulation::Train->vMechVelocity.x * simulation::Train->BaseShake.angle_scale.x ); // hustanie kamery na boki
Camera.Pitch -= 0.5 * atan( simulation::Train->vMechVelocity.z * simulation::Train->BaseShake.angle_scale.z ); // hustanie kamery przod tyl
*/
} }
/* /*
// ABu011104: rzucanie pudlem // ABu011104: rzucanie pudlem
@@ -510,10 +560,19 @@ driver_mode::update_camera( double const Deltatime ) {
Controlled->ABuSetModelShake( temp ); Controlled->ABuSetModelShake( temp );
// ABu: koniec rzucania // ABu: koniec rzucania
*/ */
if( simulation::Train->Occupied()->ActiveCab == 0 ) if( simulation::Train->Occupied()->ActiveCab == 0 ) {
Camera.LookAt = simulation::Train->GetWorldMechPosition() + simulation::Train->GetDirection() * 5.0; // gdy w korytarzu // gdy w korytarzu
else // patrzenie w kierunku osi pojazdu, z uwzględnieniem kabiny Camera.LookAt =
Camera.LookAt = simulation::Train->GetWorldMechPosition() + simulation::Train->GetDirection() * 5.0 * simulation::Train->Occupied()->ActiveCab; //-1 albo 1 Camera.m_owner->GetWorldPosition( Camera.m_owneroffset )
+ simulation::Train->GetDirection() * 5.0;
}
else {
// patrzenie w kierunku osi pojazdu, z uwzględnieniem kabiny
Camera.LookAt =
Camera.m_owner->GetWorldPosition( Camera.m_owneroffset )
+ simulation::Train->GetDirection() * 5.0
* simulation::Train->Occupied()->ActiveCab; //-1 albo 1
}
Camera.vUp = simulation::Train->GetUp(); Camera.vUp = simulation::Train->GetUp();
} }
} }
@@ -588,9 +647,7 @@ driver_mode::OnKeyDown(int cKey) {
&& ( Global.FreeCameraInit[ i ].z == 0.0 ) ) { && ( Global.FreeCameraInit[ i ].z == 0.0 ) ) {
// jeśli kamera jest w punkcie zerowym, zapamiętanie współrzędnych i kątów // jeśli kamera jest w punkcie zerowym, zapamiętanie współrzędnych i kątów
Global.FreeCameraInit[ i ] = Camera.Pos; Global.FreeCameraInit[ i ] = Camera.Pos;
Global.FreeCameraInitAngle[ i ].x = Camera.Pitch; Global.FreeCameraInitAngle[ i ] = Camera.Angle;
Global.FreeCameraInitAngle[ i ].y = Camera.Yaw;
Global.FreeCameraInitAngle[ i ].z = Camera.Roll;
// logowanie, żeby można było do scenerii przepisać // logowanie, żeby można było do scenerii przepisać
WriteLog( WriteLog(
"camera " + std::to_string( Global.FreeCameraInit[ i ].x ) + " " "camera " + std::to_string( Global.FreeCameraInit[ i ].x ) + " "
@@ -604,7 +661,7 @@ driver_mode::OnKeyDown(int cKey) {
else // również przeskakiwanie else // również przeskakiwanie
{ // Ra: to z tą kamerą (Camera.Pos i Global.pCameraPosition) jest trochę bez sensu { // Ra: to z tą kamerą (Camera.Pos i Global.pCameraPosition) jest trochę bez sensu
Global.pCamera.Pos = Global.FreeCameraInit[ i ]; // nowa pozycja dla generowania obiektów Global.pCamera.Pos = Global.FreeCameraInit[ i ]; // nowa pozycja dla generowania obiektów
Camera.Init( Global.FreeCameraInit[ i ], Global.FreeCameraInitAngle[ i ], TCameraType::tp_Free ); // przestawienie Camera.Init( Global.FreeCameraInit[ i ], Global.FreeCameraInitAngle[ i ], nullptr ); // przestawienie
} }
} }
} }
@@ -631,7 +688,8 @@ driver_mode::OnKeyDown(int cKey) {
case GLFW_KEY_F4: { case GLFW_KEY_F4: {
InOutKey( !Global.shiftState ); // distant view with Shift, short distance step out otherwise if( Global.shiftState ) { ExternalView(); } // with Shift, cycle through external views
else { InOutKey(); } // without, step out of the cab or return to it
break; break;
} }
case GLFW_KEY_F5: { case GLFW_KEY_F5: {
@@ -820,52 +878,150 @@ driver_mode::DistantView( bool const Near ) {
+ Math3D::vector3( -10.0 * left.x, 1.6, -10.0 * left.z ); + Math3D::vector3( -10.0 * left.x, 1.6, -10.0 * left.z );
} }
Camera.m_owner = nullptr;
Camera.LookAt = vehicle->GetPosition(); Camera.LookAt = vehicle->GetPosition();
Camera.RaLook(); // jednorazowe przestawienie kamery Camera.RaLook(); // jednorazowe przestawienie kamery
} }
// ustawienie śledzenia pojazdu
void void
driver_mode::FollowView(bool wycisz) { driver_mode::ExternalView() {
Camera.Reset(); // likwidacja obrotów - patrzy horyzontalnie na południe
auto *train { simulation::Train }; auto *train { simulation::Train };
if( train == nullptr ) { return; }
if (train != nullptr ) // jest pojazd do prowadzenia? auto *vehicle { train->Dynamic() };
{
if (FreeFlyModeFlag)
{ // jeżeli poza kabiną, przestawiamy w jej okolicę - OK
// wyłączenie trzęsienia na siłę?
train->Dynamic()->ABuSetModelShake( {} );
DistantView(); // przestawienie kamery // disable detailed cab in external view modes
vehicle->bDisplayCab = false;
if( true == m_externalview ) {
// we're already in some external view mode, so select next one on the list
m_externalviewmode = clamp_circular( ++m_externalviewmode, static_cast<int>( view::count_ ) );
} }
else {
Camera.Pos = train->pMechPosition;
// potentially restore cached camera angles
Camera.Pitch = train->pMechViewAngle.x;
Camera.Yaw = train->pMechViewAngle.y;
Camera.Roll = std::atan(train->pMechShake.x * train->fMechRoll); // hustanie kamery na boki FreeFlyModeFlag = true;
Camera.Pitch -= 0.5 * std::atan(train->vMechVelocity.z * train->fMechPitch); // hustanie kamery przod tyl m_externalview = true;
Camera.Reset();
// configure camera placement for the selected view mode
switch( m_externalviewmode ) {
case view::consistfront: {
// bind camera with the vehicle
auto *owner { vehicle->Mechanik->Vehicle( side::front ) };
Camera.m_owner = owner;
auto const offsetflip {
( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab )
* ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) };
Camera.m_owneroffset = {
1.5 * owner->MoverParameters->Dim.W * offsetflip,
std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ),
- 0.4 * owner->MoverParameters->Dim.L * offsetflip };
Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) );
auto const shakeangles { owner->shake_angles() };
Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl
Camera.Angle.z = shakeangles.first; // hustanie kamery na boki
break;
}
case view::consistrear: {
// bind camera with the vehicle
auto *owner { vehicle->Mechanik->Vehicle( side::rear ) };
Camera.m_owner = owner;
auto const offsetflip {
( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab )
* ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir )
* -1 };
Camera.m_owneroffset = {
1.5 * owner->MoverParameters->Dim.W * offsetflip,
std::max( 5.0, 1.25 * owner->MoverParameters->Dim.H ),
0.2 * owner->MoverParameters->Dim.L * offsetflip };
Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 0.0 : 180.0 ) );
auto const shakeangles { owner->shake_angles() };
Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl
Camera.Angle.z = shakeangles.first; // hustanie kamery na boki
break;
}
case view::bogie: {
auto *owner { vehicle->Mechanik->Vehicle( side::front ) };
Camera.m_owner = owner;
auto const offsetflip {
( vehicle->MoverParameters->ActiveCab == 0 ? 1 : vehicle->MoverParameters->ActiveCab )
* ( vehicle->MoverParameters->ActiveDir == 0 ? 1 : vehicle->MoverParameters->ActiveDir ) };
Camera.m_owneroffset = {
- 0.65 * owner->MoverParameters->Dim.W * offsetflip,
0.90,
0.15 * owner->MoverParameters->Dim.L * offsetflip };
Camera.Angle.y = glm::radians( ( vehicle->MoverParameters->ActiveDir < 0 ? 180.0 : 0.0 ) );
auto const shakeangles { owner->shake_angles() };
Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl
Camera.Angle.z = shakeangles.first; // hustanie kamery na boki
break;
}
case view::driveby: {
DistantView( false );
break;
}
default: {
break;
}
}
}
// ustawienie śledzenia pojazdu
void
driver_mode::CabView() {
// TODO: configure owner and camera placement depending on the view mode
if( true == FreeFlyModeFlag ) { return; }
auto *train { simulation::Train };
if( train == nullptr ) { return; }
m_externalview = false;
// likwidacja obrotów - patrzy horyzontalnie na południe
Camera.Reset();
// bind camera with the vehicle
Camera.m_owner = train->Dynamic();
// potentially restore cached camera setup
Camera.m_owneroffset = train->pMechSittingPosition;
Camera.Angle.x = train->pMechViewAngle.x;
Camera.Angle.y = train->pMechViewAngle.y;
auto const shakeangles { Camera.m_owner->shake_angles() };
Camera.Angle.x -= 0.5 * shakeangles.second; // hustanie kamery przod tyl
Camera.Angle.z = shakeangles.first; // hustanie kamery na boki
if( train->Occupied()->ActiveCab == 0 ) { if( train->Occupied()->ActiveCab == 0 ) {
Camera.LookAt = Camera.LookAt =
train->pMechPosition Camera.m_owner->GetWorldPosition( Camera.m_owneroffset )
+ train->GetDirection() * 5.0; + Camera.m_owner->VectorFront() * 5.0;
} }
else { else {
// patrz w strone wlasciwej kabiny // patrz w strone wlasciwej kabiny
Camera.LookAt = Camera.LookAt =
train->pMechPosition Camera.m_owner->GetWorldPosition( Camera.m_owneroffset )
+ train->GetDirection() * 5.0 * train->Occupied()->ActiveCab; + Camera.m_owner->VectorFront() * 5.0
* Camera.m_owner->MoverParameters->ActiveCab;
} }
train->pMechOffset = train->pMechSittingPosition; train->pMechOffset = Camera.m_owneroffset;
}
}
else
DistantView();
} }
void void
@@ -922,43 +1078,41 @@ driver_mode::ChangeDynamic() {
if( false == FreeFlyModeFlag ) { if( false == FreeFlyModeFlag ) {
vehicle->bDisplayCab = true; vehicle->bDisplayCab = true;
vehicle->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem? vehicle->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem?
train->MechStop(); CabView(); // na pozycję mecha
FollowView(); // na pozycję mecha
} }
Global.changeDynObj = nullptr; Global.changeDynObj = nullptr;
} }
void void
driver_mode::InOutKey( bool const Near ) driver_mode::InOutKey()
{ // przełączenie widoku z kabiny na zewnętrzny i odwrotnie { // przełączenie widoku z kabiny na zewnętrzny i odwrotnie
FreeFlyModeFlag = !FreeFlyModeFlag; // zmiana widoku FreeFlyModeFlag = !FreeFlyModeFlag; // zmiana widoku
auto *train { simulation::Train }; auto *train { simulation::Train };
if( train == nullptr ) {
FreeFlyModeFlag = true; // nadal poza kabiną
return;
}
auto *vehicle { train->Dynamic() };
if (FreeFlyModeFlag) { if (FreeFlyModeFlag) {
// jeżeli poza kabiną, przestawiamy w jej okolicę - OK // jeżeli poza kabiną, przestawiamy w jej okolicę - OK
if (train) {
// cache current cab position so there's no need to set it all over again after each out-in switch // cache current cab position so there's no need to set it all over again after each out-in switch
train->pMechSittingPosition = train->pMechOffset; train->pMechSittingPosition = train->pMechOffset;
train->Dynamic()->bDisplayCab = false; vehicle->bDisplayCab = false;
DistantView( Near ); DistantView( true );
}
DebugCamera = Camera; DebugCamera = Camera;
} }
else else {
{ // jazda w kabinie // jazda w kabinie
if (train)
{
train->Dynamic()->bDisplayCab = true;
// zerowanie przesunięcia przed powrotem? // zerowanie przesunięcia przed powrotem?
train->Dynamic()->ABuSetModelShake( { 0, 0, 0 } ); vehicle->ABuSetModelShake( { 0, 0, 0 } );
train->MechStop(); vehicle->bDisplayCab = true;
FollowView(); // na pozycję mecha CabView(); // na pozycję mecha
train->UpdateMechPosition( m_secondaryupdaterate );
}
else
FreeFlyModeFlag = true; // nadal poza kabiną
} }
// update window title to reflect the situation // update window title to reflect the situation
Application.set_title( Global.AppName + " (" + ( train != nullptr ? train->Occupied()->Name : "" ) + " @ " + Global.SceneryFile + ")" ); Application.set_title( Global.AppName + " (" + ( train != nullptr ? train->Occupied()->Name : "" ) + " @ " + Global.SceneryFile + ")" );

View File

@@ -52,6 +52,14 @@ public:
private: private:
// types // types
enum view {
consistfront,
consistrear,
bogie,
driveby,
count_
};
struct drivermode_input { struct drivermode_input {
gamepad_input gamepad; gamepad_input gamepad;
@@ -73,8 +81,9 @@ private:
// handles vehicle change flag // handles vehicle change flag
void OnKeyDown( int cKey ); void OnKeyDown( int cKey );
void ChangeDynamic(); void ChangeDynamic();
void InOutKey( bool const Near = true ); void InOutKey();
void FollowView( bool wycisz = true ); void CabView();
void ExternalView();
void DistantView( bool const Near = false ); void DistantView( bool const Near = false );
void set_picking( bool const Picking ); void set_picking( bool const Picking );
@@ -83,6 +92,8 @@ private:
std::array<basic_event *, 10> KeyEvents { nullptr }; // eventy wyzwalane z klawiaury std::array<basic_event *, 10> KeyEvents { nullptr }; // eventy wyzwalane z klawiaury
TCamera Camera; TCamera Camera;
TCamera DebugCamera; TCamera DebugCamera;
int m_externalviewmode { view::consistfront }; // selected external view mode
bool m_externalview { false };
TDynamicObject *pDynamicNearest { nullptr }; // vehicle nearest to the active camera. TODO: move to camera TDynamicObject *pDynamicNearest { nullptr }; // vehicle nearest to the active camera. TODO: move to camera
double fTime50Hz { 0.0 }; // bufor czasu dla komunikacji z PoKeys double fTime50Hz { 0.0 }; // bufor czasu dla komunikacji z PoKeys
double const m_primaryupdaterate { 1.0 / 100.0 }; double const m_primaryupdaterate { 1.0 / 100.0 };

View File

@@ -852,10 +852,10 @@ debug_panel::update_section_camera( std::vector<text_line> &Output ) {
textline = textline =
"Azimuth: " "Azimuth: "
+ to_string( 180.0 - glm::degrees( camera.Yaw ), 0 ) // ma być azymut, czyli 0 na północy i rośnie na wschód + to_string( 180.0 - glm::degrees( camera.Angle.y ), 0 ) // ma być azymut, czyli 0 na północy i rośnie na wschód
+ " " + " "
+ std::string( "S SEE NEN NWW SW" ) + std::string( "S SEE NEN NWW SW" )
.substr( 0 + 2 * floor( fmod( 8 + ( camera.Yaw + 0.5 * M_PI_4 ) / M_PI_4, 8 ) ), 2 ); .substr( 0 + 2 * floor( fmod( 8 + ( camera.Angle.y + 0.5 * M_PI_4 ) / M_PI_4, 8 ) ), 2 );
Output.emplace_back( textline, Global.UITextColor ); Output.emplace_back( textline, Global.UITextColor );
} }

View File

@@ -43,7 +43,7 @@ editor_mode::editor_mode() {
bool bool
editor_mode::init() { editor_mode::init() {
Camera.Init( { 0, 15, 0 }, { glm::radians( -30.0 ), glm::radians( 180.0 ), 0 }, TCameraType::tp_Free ); Camera.Init( { 0, 15, 0 }, { glm::radians( -30.0 ), glm::radians( 180.0 ), 0 }, nullptr );
return m_input.init(); return m_input.init();
} }

View File

@@ -1512,7 +1512,10 @@ opengl_renderer::Render( world_environment *Environment ) {
::glLoadIdentity(); // macierz jedynkowa ::glLoadIdentity(); // macierz jedynkowa
::glTranslatef( sunposition.x, sunposition.y, sunposition.z ); // początek układu zostaje bez zmian ::glTranslatef( sunposition.x, sunposition.y, sunposition.z ); // początek układu zostaje bez zmian
float const size = 0.045f; float const size = interpolate( // TODO: expose distance/scale factor from the moon object
0.0325f,
0.0275f,
clamp( Environment->m_sun.getAngle(), 0.f, 90.f ) / 90.f );
::glBegin( GL_TRIANGLE_STRIP ); ::glBegin( GL_TRIANGLE_STRIP );
::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 1.f ); ::glVertex3f( -size, size, 0.f ); ::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 1.f ); ::glVertex3f( -size, size, 0.f );
::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 0.f ); ::glVertex3f( -size, -size, 0.f ); ::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 0.f ); ::glVertex3f( -size, -size, 0.f );
@@ -1542,8 +1545,8 @@ opengl_renderer::Render( world_environment *Environment ) {
::glTranslatef( moonposition.x, moonposition.y, moonposition.z ); ::glTranslatef( moonposition.x, moonposition.y, moonposition.z );
float const size = interpolate( // TODO: expose distance/scale factor from the moon object float const size = interpolate( // TODO: expose distance/scale factor from the moon object
0.0175f, 0.0160f,
0.015f, 0.0135f,
clamp( Environment->m_moon.getAngle(), 0.f, 90.f ) / 90.f ); clamp( Environment->m_moon.getAngle(), 0.f, 90.f ) / 90.f );
// choose the moon appearance variant, based on current moon phase // choose the moon appearance variant, based on current moon phase
// NOTE: implementation specific, 8 variants are laid out in 3x3 arrangement // NOTE: implementation specific, 8 variants are laid out in 3x3 arrangement
@@ -2180,19 +2183,21 @@ opengl_renderer::Render( TDynamicObject *Dynamic ) {
if( Dynamic->mdLowPolyInt ) { if( Dynamic->mdLowPolyInt ) {
// low poly interior // low poly interior
if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) { if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) {
/*
// enable cab light if needed // enable cab light if needed
if( Dynamic->InteriorLightLevel > 0.0f ) { if( Dynamic->InteriorLightLevel > 0.0f ) {
// crude way to light the cabin, until we have something more complete in place // crude way to light the cabin, until we have something more complete in place
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( Dynamic->InteriorLight * Dynamic->InteriorLightLevel ) ); ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( Dynamic->InteriorLight * Dynamic->InteriorLightLevel ) );
} }
*/
Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance ); Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance );
/*
if( Dynamic->InteriorLightLevel > 0.0f ) { if( Dynamic->InteriorLightLevel > 0.0f ) {
// reset the overall ambient // reset the overall ambient
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( m_baseambient ) ); ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( m_baseambient ) );
} }
*/
} }
} }
if( Dynamic->mdModel ) if( Dynamic->mdModel )
@@ -3265,19 +3270,21 @@ opengl_renderer::Render_Alpha( TDynamicObject *Dynamic ) {
if( Dynamic->mdLowPolyInt ) { if( Dynamic->mdLowPolyInt ) {
// low poly interior // low poly interior
if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) { if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) {
/*
// enable cab light if needed // enable cab light if needed
if( Dynamic->InteriorLightLevel > 0.0f ) { if( Dynamic->InteriorLightLevel > 0.0f ) {
// crude way to light the cabin, until we have something more complete in place // crude way to light the cabin, until we have something more complete in place
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( Dynamic->InteriorLight * Dynamic->InteriorLightLevel ) ); ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( Dynamic->InteriorLight * Dynamic->InteriorLightLevel ) );
} }
*/
Render_Alpha( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance ); Render_Alpha( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance );
/*
if( Dynamic->InteriorLightLevel > 0.0f ) { if( Dynamic->InteriorLightLevel > 0.0f ) {
// reset the overall ambient // reset the overall ambient
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( m_baseambient ) ); ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( m_baseambient ) );
} }
*/
} }
} }

View File

@@ -1 +1 @@
#define VERSION_INFO "M7 25.10.2018, based on tmj-b36ed8d" #define VERSION_INFO "M7 10.11.2018, based on tmj-c59b530"