16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-19 06:29:18 +02:00

build 180923. basic precipitation visualization system, minor bug fixes

This commit is contained in:
tmj-fstate
2018-09-23 19:13:33 +02:00
parent 2448e59dc8
commit e1b20a025a
19 changed files with 495 additions and 79 deletions

View File

@@ -238,9 +238,16 @@ void TSpeedPos::CommandCheck()
case TCommandType::cm_PassengerStopPoint:
// nie ma dostępu do rozkładu
// przystanek, najwyżej AI zignoruje przy analizie tabelki
// if ((iFlags & spPassengerStopPoint) == 0)
fVelNext = 0.0; // TrainParams->IsStop()?0.0:-1.0; //na razie tak
fVelNext = 0.0;
iFlags |= spPassengerStopPoint; // niestety nie da się w tym miejscu współpracować z rozkładem
/*
// NOTE: not used for now as it might be unnecessary
// special case, potentially override any set speed limits if requested
// NOTE: we test it here because for the time being it's only used for passenger stops
if( TestFlag( iFlags, spDontApplySpeedLimit ) ) {
fVelNext = -1;
}
*/
break;
case TCommandType::cm_SetProximityVelocity:
// musi zostać gdyż inaczej nie działają manewry
@@ -879,7 +886,6 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN
// TableClear(); //aby od nowa sprawdziło W4 z inną nazwą już - to nie
// jest dobry pomysł
sSpeedTable[i].iFlags = 0; // nie liczy się już
sSpeedTable[i].fVelNext = -1; // jechać
continue; // nie analizować prędkości
}
} // koniec obsługi przelotu na W4
@@ -1035,9 +1041,12 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN
iDrivigFlags |= moveStopCloser; // do następnego W4 podjechać blisko (z dociąganiem)
sSpeedTable[i].iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity)
sSpeedTable[i].fVelNext = -1; // można jechać za W4
if( ( sSpeedTable[ i ].fDist <= 0.0 ) && ( eSignNext == sSpeedTable[ i ].evEvent ) ) {
// sanity check, if we're held by this stop point, let us go
VelSignalLast = -1;
}
if (go == TCommandType::cm_Unknown) // jeśli nie było komendy wcześniej
go = TCommandType::cm_Ready; // gotów do odjazdu z W4 (semafor może
// zatrzymać)
go = TCommandType::cm_Ready; // gotów do odjazdu z W4 (semafor może zatrzymać)
if( false == tsGuardSignal.empty() ) {
// jeśli mamy głos kierownika, to odegrać
iDrivigFlags |= moveGuardSignal;
@@ -1061,14 +1070,15 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN
// if the consist can change direction through a simple cab change it doesn't need fiddling with recognition of passenger stops
iDrivigFlags &= ~( moveStopPoint );
}
fLastStopExpDist = -1.0f; // nie ma rozkładu, nie ma usuwania stacji
sSpeedTable[i].iFlags = 0; // W4 nie liczy się już (nie wyśle SetVelocity)
sSpeedTable[i].fVelNext = -1; // można jechać za W4
fLastStopExpDist = -1.0f; // nie ma rozkładu, nie ma usuwania stacji
// wykonanie kolejnego rozkazu (Change_direction albo Shunt)
// FIX: don't automatically advance if there's disconnect procedure in progress
if( false == TestFlag( OrderCurrentGet(), Disconnect ) ) {
JumpToNextOrder();
if( ( sSpeedTable[ i ].fDist <= 0.0 ) && ( eSignNext == sSpeedTable[ i ].evEvent ) ) {
// sanity check, if we're held by this stop point, let us go
VelSignalLast = -1;
}
// wykonanie kolejnego rozkazu (Change_direction albo Shunt)
JumpToNextOrder();
// ma się nie ruszać aż do momentu podania sygnału
iDrivigFlags |= moveStopHere | moveStartHorn;
continue; // nie analizować prędkości
@@ -1094,36 +1104,42 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN
}
else if (sSpeedTable[i].iFlags & spEvent) // W4 może się deaktywować
{ // jeżeli event, może być potrzeba wysłania komendy, aby ruszył
// sprawdzanie eventów pasywnych miniętych
if( (sSpeedTable[ i ].fDist < 0.0) && (SemNextIndex == i) )
{
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextIndex ].GetName() );
if( sSpeedTable[ i ].fDist < 0.0 ) {
// sprawdzanie eventów pasywnych miniętych
/*
if( ( eSignNext != nullptr ) && ( sSpeedTable[ i ].evEvent == eSignNext ) ) {
VelSignalLast = sSpeedTable[ i ].fVelNext;
}
SemNextIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód
}
if( (sSpeedTable[ i ].fDist < 0.0) && (SemNextStopIndex == i) )
{
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextStopIndex ].GetName() );
}
SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód
}
if (sSpeedTable[i].fDist > 0.0 &&
sSpeedTable[i].IsProperSemaphor(OrderCurrentGet()))
{
if( SemNextIndex == -1 ) {
// jeśli jest mienięty poprzedni semafor a wcześniej
// byl nowy to go dorzucamy do zmiennej, żeby cały czas widział najbliższy
SemNextIndex = i;
*/
if( SemNextIndex == i ) {
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", next semaphor is " + sSpeedTable[ SemNextIndex ].GetName() );
WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextIndex ].GetName() );
}
SemNextIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód
}
if( ( SemNextStopIndex == -1 )
|| ( ( sSpeedTable[SemNextStopIndex].fVelNext != 0 )
&& ( sSpeedTable[ i ].fVelNext == 0 ) ) ) {
SemNextStopIndex = i;
if( SemNextStopIndex == i ) {
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextStopIndex ].GetName() );
}
SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód
}
}
if( sSpeedTable[ i ].fDist > 0.0 ) {
// check signals ahead
if( sSpeedTable[ i ].IsProperSemaphor( OrderCurrentGet() ) ) {
if( SemNextIndex == -1 ) {
// jeśli jest mienięty poprzedni semafor a wcześniej
// byl nowy to go dorzucamy do zmiennej, żeby cały czas widział najbliższy
SemNextIndex = i;
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", next semaphor is " + sSpeedTable[ SemNextIndex ].GetName() );
}
}
if( ( SemNextStopIndex == -1 )
|| ( ( sSpeedTable[ SemNextStopIndex ].fVelNext != 0 )
&& ( sSpeedTable[ i ].fVelNext == 0 ) ) ) {
SemNextStopIndex = i;
}
}
}
if (sSpeedTable[i].iFlags & spOutsideStation)

View File

@@ -119,8 +119,8 @@ enum TSpeedPosFlag
spSemaphor = 0x4000, // semafor pociągowy
spRoadVel = 0x8000, // zadanie prędkości drogowej
spSectionVel = 0x20000, // odcinek z ograniczeniem
spProximityVelocity = 0x40000, // odcinek z ograniczeniem i podaną jego długościa
spEndOfTable = 0x10000 // zatkanie tabelki
spProximityVelocity = 0x40000 // odcinek z ograniczeniem i podaną jego długościa
// spDontApplySpeedLimit = 0x10000 // this point won't apply its speed limit. potentially set by the scanning vehicle
};
class TSpeedPos

View File

@@ -2014,7 +2014,7 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424"
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 );
MoverParameters->TruckHunting = ( Random( 0, 100 ) < huntingchance );
ActPar.erase( 0, indexend );
break;
}
@@ -2396,12 +2396,12 @@ void TDynamicObject::Move(double fDistance)
// normalizacja potrzebna z powodu pochylenia (vFront)
vUp = CrossProduct(vFront, vLeft); // wektor w górę, będzie jednostkowy
modelRot.z = atan2(-vFront.x, vFront.z); // kąt obrotu pojazdu [rad]; z ABuBogies()
double a = ((Axle1.GetRoll() + Axle0.GetRoll())); // suma przechyłek
if (a != 0.0)
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.Identity(); // ta macierz jest potrzebna głównie do wyświetlania
mMatrix.Rotation(a * 0.5, vFront); // obrót wzdłuż osi o przechyłkę
mMatrix.Rotation(roll * 0.5, vFront); // obrót wzdłuż osi o przechyłkę
vUp = mMatrix * vUp; // wektor w górę pojazdu (przekręcenie na przechyłce)
// vLeft=mMatrix*DynamicObject->vLeft;
// vUp=CrossProduct(vFront,vLeft); //wektor w górę
@@ -2838,7 +2838,7 @@ bool TDynamicObject::Update(double dt, double dt1)
// TTrackParam tp;
tp.Width = MyTrack->fTrackWidth;
// McZapkie-250202
tp.friction = MyTrack->fFriction * Global.fFriction;
tp.friction = MyTrack->fFriction * Global.fFriction * Global.FrictionWeatherFactor;
tp.CategoryFlag = MyTrack->iCategoryFlag & 15;
tp.DamageFlag = MyTrack->iDamageFlag;
tp.QualityFlag = MyTrack->iQualityFlag;

View File

@@ -546,6 +546,8 @@ private:
return iAxleFirst ?
Axle1.pPosition :
Axle0.pPosition; };
inline double Roll() {
return ( ( Axle1.GetRoll() + Axle0.GetRoll() ) ); }
/*
// TODO: check if scanning takes into account direction when selecting axle
// if it does, replace the version above

View File

@@ -65,6 +65,7 @@ struct global_settings {
bool RealisticControlMode{ false }; // controls ability to steer the vehicle from outside views
bool bEnableTraction{ true };
float fFriction{ 1.f }; // mnożnik tarcia - KURS90
float FrictionWeatherFactor { 1.f };
bool bLiveTraction{ true };
float Overcast{ 0.1f }; // NOTE: all this weather stuff should be moved elsewhere
glm::vec3 FogColor = { 0.6f, 0.7f, 0.8f };

View File

@@ -527,9 +527,9 @@ PyObject *TTrain::GetTrainState() {
PyDict_SetItemString( dict, "unit_no", PyGetInt( iUnitNo ) );
for( int i = 0; i < 20; i++ ) {
PyDict_SetItemString( dict, ( "doors_" + std::to_string( i + 1 ) ).c_str(), PyGetFloatS( bDoors[ i ][ 0 ] ) );
PyDict_SetItemString( dict, ( "doors_r_" + std::to_string( i + 1 ) ).c_str(), PyGetFloatS( bDoors[ i ][ 1 ] ) );
PyDict_SetItemString( dict, ( "doors_l_" + std::to_string( i + 1 ) ).c_str(), PyGetFloatS( bDoors[ i ][ 2 ] ) );
PyDict_SetItemString( dict, ( "doors_" + std::to_string( i + 1 ) ).c_str(), PyGetBool( bDoors[ i ][ 0 ] ) );
PyDict_SetItemString( dict, ( "doors_r_" + std::to_string( i + 1 ) ).c_str(), PyGetBool( bDoors[ i ][ 1 ] ) );
PyDict_SetItemString( dict, ( "doors_l_" + std::to_string( i + 1 ) ).c_str(), PyGetBool( bDoors[ i ][ 2 ] ) );
PyDict_SetItemString( dict, ( "doors_no_" + std::to_string( i + 1 ) ).c_str(), PyGetInt( iDoorNo[ i ] ) );
PyDict_SetItemString( dict, ( "code_" + std::to_string( i + 1 ) ).c_str(), PyGetString( ( std::to_string( iUnits[ i ] ) + cCode[ i ] ).c_str() ) );
PyDict_SetItemString( dict, ( "car_name" + std::to_string( i + 1 ) ).c_str(), PyGetString( asCarName[ i ].c_str() ) );
@@ -4529,31 +4529,14 @@ void TTrain::UpdateMechPosition(double dt)
2 :
DynamicObject->MoverParameters->ActiveCab );
if( !DebugModeFlag ) { // sprawdzaj więzy //Ra: nie tu!
if( pMechPosition.x < Cabine[ iCabn ].CabPos1.x )
pMechPosition.x = Cabine[ iCabn ].CabPos1.x;
if( pMechPosition.x > Cabine[ iCabn ].CabPos2.x )
pMechPosition.x = Cabine[ iCabn ].CabPos2.x;
if( pMechPosition.z < Cabine[ iCabn ].CabPos1.z )
pMechPosition.z = Cabine[ iCabn ].CabPos1.z;
if( pMechPosition.z > Cabine[ iCabn ].CabPos2.z )
pMechPosition.z = Cabine[ iCabn ].CabPos2.z;
if( pMechPosition.y > Cabine[ iCabn ].CabPos1.y + 1.8 )
pMechPosition.y = Cabine[ iCabn ].CabPos1.y + 1.8;
if( pMechPosition.y < Cabine[ iCabn ].CabPos1.y + 0.5 )
pMechPosition.y = Cabine[ iCabn ].CabPos2.y + 0.5;
if( pMechOffset.x < Cabine[ iCabn ].CabPos1.x )
pMechOffset.x = Cabine[ iCabn ].CabPos1.x;
if( pMechOffset.x > Cabine[ iCabn ].CabPos2.x )
pMechOffset.x = Cabine[ iCabn ].CabPos2.x;
if( pMechOffset.z < Cabine[ iCabn ].CabPos1.z )
pMechOffset.z = Cabine[ iCabn ].CabPos1.z;
if( pMechOffset.z > Cabine[ iCabn ].CabPos2.z )
pMechOffset.z = Cabine[ iCabn ].CabPos2.z;
if( pMechOffset.y > Cabine[ iCabn ].CabPos1.y + 1.8 )
pMechOffset.y = Cabine[ iCabn ].CabPos1.y + 1.8;
if( pMechOffset.y < Cabine[ iCabn ].CabPos1.y + 0.5 )
pMechOffset.y = Cabine[ iCabn ].CabPos2.y + 0.5;
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 );
}
};
@@ -5863,7 +5846,7 @@ TTrain::update_sounds( double const Deltatime ) {
dsbSlipAlarm.stop();
}
}
/*
// szum w czasie jazdy
if( ( false == FreeFlyModeFlag )
&& ( false == Global.CabWindowOpen )
@@ -5875,6 +5858,7 @@ TTrain::update_sounds( double const Deltatime ) {
// don't play the optional ending sound if the listener switches views
rsRunningNoise.stop( true == FreeFlyModeFlag );
}
*/
// hunting oscillation noise
if( ( false == FreeFlyModeFlag )
&& ( false == Global.CabWindowOpen )
@@ -5882,6 +5866,15 @@ TTrain::update_sounds( double const Deltatime ) {
&& ( IsHunting ) ) {
update_sounds_runningnoise( rsHuntingNoise );
// modify calculated sound volume by hunting amount
auto const huntingamount =
interpolate(
0.0, 1.0,
clamp(
( mvOccupied->Vel - HuntingShake.fadein_begin ) / ( HuntingShake.fadein_end - HuntingShake.fadein_begin ),
0.0, 1.0 ) );
rsHuntingNoise.gain( rsHuntingNoise.gain() * huntingamount );
}
else {
// don't play the optional ending sound if the listener switches views

View File

@@ -224,6 +224,8 @@ driver_mode::update() {
update_camera( deltarealtime );
simulation::Environment.update_precipitation(); // has to be launched after camera step to work properly
Timer::subsystem.sim_total.stop();
simulation::Region->update_sounds();
@@ -951,6 +953,7 @@ driver_mode::InOutKey( bool const Near )
train->Dynamic()->ABuSetModelShake( { 0, 0, 0 } );
train->MechStop();
FollowView(); // na pozycję mecha
train->UpdateMechPosition( m_secondaryupdaterate );
}
else
FreeFlyModeFlag = true; // nadal poza kabiną

View File

@@ -330,6 +330,9 @@
<ClCompile Include="ref\imgui\imgui_widgets.cpp">
<Filter>Source Files\imgui</Filter>
</ClCompile>
<ClCompile Include="precipitation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Globals.h">
@@ -608,6 +611,9 @@
<ClInclude Include="editoruipanels.h">
<Filter>Header Files\application\mode_editor</Filter>
</ClInclude>
<ClInclude Include="precipitation.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="maszyna.rc">

View File

@@ -43,12 +43,16 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c
if( key == Global.Weather ) {
// weather textures override generic (pri 0) and seasonal (pri 1) textures
// seasonal weather textures (pri 1+2=3) override generic weather (pri 2) textures
// skip the opening bracket
auto const value { Input.getToken<std::string>( true, "\n\r\t ;" ) };
while( true == deserialize_mapping( Input, Priority + 2, Loadnow ) ) {
; // all work is done in the header
}
}
else if( key == Global.Season ) {
// seasonal textures override generic textures
// skip the opening bracket
auto const value { Input.getToken<std::string>( true, "\n\r\t ;" ) };
while( true == deserialize_mapping( Input, 1, Loadnow ) ) {
; // all work is done in the header
}

197
precipitation.cpp Normal file
View File

@@ -0,0 +1,197 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "precipitation.h"
#include "globals.h"
#include "openglmatrixstack.h"
#include "renderer.h"
#include "timer.h"
#include "simulation.h"
#include "train.h"
basic_precipitation::~basic_precipitation() {
// TODO: release allocated resources
}
void
basic_precipitation::create( int const Tesselation ) {
auto const heightfactor { 10.f }; // height-to-radius factor
m_moverate *= heightfactor;
auto const verticaltexturestretchfactor { 1.5f }; // crude motion blur
// create geometry chunk
auto const latitudes { 3 }; // just a cylinder with end cones
auto const longitudes { Tesselation };
auto const longitudehalfstep { 0.5f * static_cast<float>( 2.0 * M_PI * 1.f / longitudes ) }; // for crude uv correction
std::uint16_t index = 0;
// auto const radius { 25.f }; // cylinder radius
std::vector<float> radii { 25.f, 10.f, 5.f, 1.f };
for( auto radius : radii ) {
for( int i = 0; i <= latitudes; ++i ) {
auto const latitude{ static_cast<float>( M_PI * ( -0.5f + (float)( i ) / latitudes ) ) };
auto const z{ std::sin( latitude ) };
auto const zr{ std::cos( latitude ) };
for( int j = 0; j <= longitudes; ++j ) {
// NOTE: for the first and last row half of the points we create end up unused but, eh
auto const longitude{ static_cast<float>( 2.0 * M_PI * (float)( j ) / longitudes ) };
auto const x{ std::cos( longitude ) };
auto const y{ std::sin( longitude ) };
// NOTE: cartesian to opengl swap would be: -x, -z, -y
m_vertices.emplace_back( glm::vec3( -x * zr, -z * heightfactor, -y * zr ) * radius );
// uvs
// NOTE: first and last row receives modified u values to deal with limitation of mapping onto triangles
auto u = (
i == 0 ? longitude + longitudehalfstep :
i == latitudes ? longitude - longitudehalfstep :
longitude );
m_uvs.emplace_back(
u / ( 2.0 * M_PI ) * radius,
1.f - (float)( i ) / latitudes * radius * heightfactor * 0.5f / verticaltexturestretchfactor );
if( ( i == 0 ) || ( j == 0 ) ) {
// initial edge of the dome, don't start indices yet
++index;
}
else {
// the end cones are built from one triangle of each quad, the middle rows use both
if( i < latitudes ) {
m_indices.emplace_back( index - 1 - ( longitudes + 1 ) );
m_indices.emplace_back( index - 1 );
m_indices.emplace_back( index );
}
if( i > 1 ) {
m_indices.emplace_back( index );
m_indices.emplace_back( index - ( longitudes + 1 ) );
m_indices.emplace_back( index - 1 - ( longitudes + 1 ) );
}
++index;
}
} // longitude
} // latitude
} // radius
}
bool
basic_precipitation::init() {
create( 18 );
// TODO: select texture based on current overcast level
if( Global.Weather == "rain:" ) {
m_texture = GfxRenderer.Fetch_Texture( "fx/rain_medium" );
m_moverateweathertypefactor = 2.f;
}
else if( Global.Weather == "snow:" ) {
m_texture = GfxRenderer.Fetch_Texture( "fx/snow_medium" );
m_moverateweathertypefactor = 1.25f;
}
return true;
}
void
basic_precipitation::update() {
auto const timedelta { static_cast<float>( ( DebugModeFlag ? Timer::GetDeltaTime() : Timer::GetDeltaTime() ) ) };
if( timedelta == 0.0 ) { return; }
m_textureoffset += m_moverate * m_moverateweathertypefactor * timedelta;
m_textureoffset = clamp_circular( m_textureoffset, 1.f );
auto cameramove { glm::dvec3{ Global.pCamera.Pos - m_camerapos} };
cameramove.y = 0.0; // vertical movement messes up vector calculation
m_camerapos = Global.pCamera.Pos;
// intercept sudden user-induced camera jumps
if( m_freeflymode != FreeFlyModeFlag ) {
m_freeflymode = FreeFlyModeFlag;
if( true == m_freeflymode ) {
// cache last precipitation vector in the cab
m_cabcameramove = m_cameramove;
// don't carry previous precipitation vector to a new unrelated location
m_cameramove = glm::dvec3{ 0.0 };
}
else {
// restore last cached precipitation vector
m_cameramove = m_cabcameramove;
}
cameramove = glm::dvec3{ 0.0 };
}
if( m_windowopen != Global.CabWindowOpen ) {
m_windowopen = Global.CabWindowOpen;
cameramove = glm::dvec3{ 0.0 };
}
if( ( simulation::Train != nullptr ) && ( simulation::Train->iCabn != m_activecab ) ) {
m_activecab = simulation::Train->iCabn;
cameramove = glm::dvec3{ 0.0 };
}
if( glm::length( cameramove ) > 100.0 ) {
cameramove = glm::dvec3{ 0.0 };
}
m_cameramove = m_cameramove * std::max( 0.0, 1.0 - 5.0 * timedelta ) + cameramove * ( 30.0 * timedelta );
if( std::abs( m_cameramove.x ) < 0.001 ) { m_cameramove.x = 0.0; }
if( std::abs( m_cameramove.y ) < 0.001 ) { m_cameramove.y = 0.0; }
if( std::abs( m_cameramove.z ) < 0.001 ) { m_cameramove.z = 0.0; }
}
void
basic_precipitation::render() {
GfxRenderer.Bind_Texture( m_texture );
// cache entry state
::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT );
if( m_vertexbuffer == -1 ) {
// build the buffers
::glGenBuffers( 1, &m_vertexbuffer );
::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer );
::glBufferData( GL_ARRAY_BUFFER, m_vertices.size() * sizeof( glm::vec3 ), m_vertices.data(), GL_STATIC_DRAW );
::glGenBuffers( 1, &m_uvbuffer );
::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer );
::glBufferData( GL_ARRAY_BUFFER, m_uvs.size() * sizeof( glm::vec2 ), m_uvs.data(), GL_STATIC_DRAW );
::glGenBuffers( 1, &m_indexbuffer );
::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer );
::glBufferData( GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof( unsigned short ), m_indices.data(), GL_STATIC_DRAW );
// NOTE: vertex and index source data is superfluous past this point, but, eh
}
// positions
::glBindBuffer( GL_ARRAY_BUFFER, m_vertexbuffer );
::glVertexPointer( 3, GL_FLOAT, sizeof( glm::vec3 ), reinterpret_cast<void const*>( 0 ) );
::glEnableClientState( GL_VERTEX_ARRAY );
// uvs
::glBindBuffer( GL_ARRAY_BUFFER, m_uvbuffer );
::glClientActiveTexture( m_textureunit );
::glTexCoordPointer( 2, GL_FLOAT, sizeof( glm::vec2 ), reinterpret_cast<void const*>( 0 ) );
::glEnableClientState( GL_TEXTURE_COORD_ARRAY );
// uv transformation matrix
::glMatrixMode( GL_TEXTURE );
::glLoadIdentity();
::glTranslatef( 0.f, m_textureoffset, 0.f );
// indices
::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indexbuffer );
::glDrawElements( GL_TRIANGLES, static_cast<GLsizei>( m_indices.size() ), GL_UNSIGNED_SHORT, reinterpret_cast<void const*>( 0 ) );
// cleanup
::glLoadIdentity();
::glMatrixMode( GL_MODELVIEW );
::glPopClientAttrib();
}

59
precipitation.h Normal file
View File

@@ -0,0 +1,59 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "texture.h"
// based on "Rendering Falling Rain and Snow"
// by Niniane Wang, Bretton Wade
class basic_precipitation {
public:
// constructors
basic_precipitation() = default;
// destructor
~basic_precipitation();
// methods
inline
void
set_unit( GLint const Textureunit ) {
m_textureunit = Textureunit; }
bool
init();
void
update();
void
render();
glm::dvec3 m_cameramove{ 0.0 };
private:
// methods
void create( int const Tesselation );
// members
std::vector<glm::vec3> m_vertices;
std::vector<glm::vec2> m_uvs;
std::vector<std::uint16_t> m_indices;
GLuint m_vertexbuffer { (GLuint)-1 };
GLuint m_uvbuffer { (GLuint)-1 };
GLuint m_indexbuffer { (GLuint)-1 };
GLint m_textureunit { 0 };
texture_handle m_texture { -1 };
float m_textureoffset { 0.f };
float m_moverate { 30 * 0.001f };
float m_moverateweathertypefactor { 1.f }; // medium-dependent; 1.0 for snow, faster for rain
glm::dvec3 m_camerapos { 0.0 };
bool m_freeflymode { true };
bool m_windowopen { true };
int m_activecab{ 0 };
glm::dvec3 m_cabcameramove{ 0.0 };
};

View File

@@ -129,6 +129,7 @@ opengl_renderer::Init( GLFWwindow *Window ) {
std::vector<GLint>{ m_normaltextureunit, m_diffusetextureunit } );
m_textures.assign_units( m_helpertextureunit, m_shadowtextureunit, m_normaltextureunit, m_diffusetextureunit ); // TODO: add reflections unit
ui_layer::set_unit( m_diffusetextureunit );
simulation::Environment.m_precipitation.set_unit( m_diffusetextureunit );
select_unit( m_diffusetextureunit );
::glDepthFunc( GL_LEQUAL );
@@ -550,7 +551,9 @@ opengl_renderer::Render_pass( rendermode const Mode ) {
::glEnable( GL_TEXTURE_2D );
}
#endif
if( false == FreeFlyModeFlag ) {
// without rain/snow we can render the cab early to limit the overdraw
if( ( false == FreeFlyModeFlag )
&& ( Global.Overcast <= 1.f ) ) { // precipitation happens when overcast is in 1-2 range
switch_units( true, true, false );
setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix );
// cache shadow colour in case we need to account for cab light
@@ -570,8 +573,10 @@ opengl_renderer::Render_pass( rendermode const Mode ) {
// ...translucent parts
setup_drawing( true );
Render_Alpha( simulation::Region );
// precipitation; done at the end, only before cab render
Render_precipitation();
// cab render
if( false == FreeFlyModeFlag ) {
// cab render is performed without shadows, due to low resolution and number of models without windows :|
switch_units( true, true, false );
setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix );
// cache shadow colour in case we need to account for cab light
@@ -580,6 +585,12 @@ opengl_renderer::Render_pass( rendermode const Mode ) {
if( vehicle->InteriorLightLevel > 0.f ) {
setup_shadow_color( glm::min( colors::white, shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
}
if( Global.Overcast > 1.f ) {
// with active precipitation draw the opaque cab parts here to mask rain/snow placed 'inside' the cab
setup_drawing( false );
Render_cab( vehicle, false );
setup_drawing( true );
}
Render_cab( vehicle, true );
if( vehicle->InteriorLightLevel > 0.f ) {
setup_shadow_color( shadowcolor );
@@ -590,7 +601,8 @@ opengl_renderer::Render_pass( rendermode const Mode ) {
// restore default texture matrix for reflections cube map
select_unit( m_helpertextureunit );
::glMatrixMode( GL_TEXTURE );
::glPopMatrix();
// ::glPopMatrix();
::glLoadIdentity();
select_unit( m_diffusetextureunit );
::glMatrixMode( GL_MODELVIEW );
}
@@ -1011,7 +1023,8 @@ opengl_renderer::setup_matrices() {
// special case, for colour render pass setup texture matrix for reflections cube map
select_unit( m_helpertextureunit );
::glMatrixMode( GL_TEXTURE );
::glPushMatrix();
// ::glPushMatrix();
::glLoadIdentity();
::glMultMatrixf( glm::value_ptr( glm::inverse( glm::mat4{ glm::mat3{ m_renderpass.camera.modelview() } } ) ) );
select_unit( m_diffusetextureunit );
}
@@ -1459,7 +1472,7 @@ opengl_renderer::Render( world_environment *Environment ) {
// sun
{
Bind_Texture( m_suntexture );
::glColor4f( suncolor.x, suncolor.y, suncolor.z, 1.0f );
::glColor4f( suncolor.x, suncolor.y, suncolor.z, clamp( 1.5f - Global.Overcast, 0.f, 1.f ) );
auto const sunvector = Environment->m_sun.getDirection();
auto const sunposition = modelview * glm::vec4( sunvector.x, sunvector.y, sunvector.z, 1.0f );
@@ -2807,6 +2820,54 @@ opengl_renderer::Render( TMemCell *Memcell ) {
::glPopMatrix();
}
void
opengl_renderer::Render_precipitation() {
if( Global.Overcast <= 1.f ) { return; }
switch_units( true, false, false );
// ::glColor4fv( glm::value_ptr( glm::vec4( glm::min( glm::vec3( Global.fLuminance ), glm::vec3( 1 ) ), 1 ) ) );
::glColor4fv(
glm::value_ptr(
interpolate(
0.5f * ( Global.DayLight.diffuse + Global.DayLight.ambient ),
colors::white,
0.5f * clamp<float>( Global.fLuminance, 0.f, 1.f ) ) ) );
::glPushMatrix();
auto const velocity { simulation::Environment.m_precipitation.m_cameramove * -1.0 };
if( glm::length2( velocity ) > 0.0 ) {
auto const forward{ glm::normalize( velocity ) };
if( false == FreeFlyModeFlag ) {
// counter potential vehicle roll
auto const roll { simulation::Train->Dynamic()->Roll() };
if( roll != 0.0 ) {
::glRotated( roll, forward.x, 0.0, forward.z );
}
}
auto left { glm::cross( forward, {0.0,1.0,0.0} ) };
auto const rotationangle {
std::min(
45.0,
( FreeFlyModeFlag ?
5 * glm::length( velocity ) :
simulation::Train->Dynamic()->GetVelocity() * 0.2 ) ) };
::glRotated( rotationangle, left.x, 0.0, left.z );
}
// TBD: leave lighting on to allow vehicle lights to affect it?
::glDisable( GL_LIGHTING );
// momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside'
::glDepthMask( GL_FALSE );
simulation::Environment.m_precipitation.render();
::glDepthMask( GL_TRUE );
::glEnable( GL_LIGHTING );
::glPopMatrix();
}
void
opengl_renderer::Render_Alpha( scene::basic_region *Region ) {

View File

@@ -294,6 +294,8 @@ private:
Render_cab( TDynamicObject const *Dynamic, bool const Alpha = false );
void
Render( TMemCell *Memcell );
void
Render_precipitation();
void
Render_Alpha( scene::basic_region *Region );
void

View File

@@ -986,6 +986,37 @@ basic_region::update_traction( TDynamicObject *Vehicle, int const Pantographinde
}
}
// checks whether specified file is a valid region data file
bool
basic_region::is_scene( std::string const &Scenariofile ) const {
auto filename { Scenariofile };
while( filename[ 0 ] == '$' ) {
// trim leading $ char rainsted utility may add to the base name for modified .scn files
filename.erase( 0, 1 );
}
erase_extension( filename );
filename = Global.asCurrentSceneryPath + filename;
filename += EU07_FILEEXTENSION_REGION;
if( false == FileExists( filename ) ) {
return false;
}
// file type and version check
std::ifstream input( filename, std::ios::binary );
uint32_t headermain{ sn_utils::ld_uint32( input ) };
uint32_t headertype{ sn_utils::ld_uint32( input ) };
if( ( headermain != EU07_FILEHEADER
|| ( headertype != EU07_FILEVERSION_REGION ) ) ) {
// wrong file type
return false;
}
return true;
}
// stores content of the class in file with specified name
void
basic_region::serialize( std::string const &Scenariofile ) const {
@@ -1062,7 +1093,9 @@ basic_region::deserialize( std::string const &Scenariofile ) {
// section index, followed by section data size, followed by section data
auto *&section { m_sections[ sn_utils::ld_uint32( input ) ] };
auto const sectionsize { sn_utils::ld_uint32( input ) };
section = new basic_section();
if( section == nullptr ) {
section = new basic_section();
}
section->deserialize( input );
}

View File

@@ -53,6 +53,7 @@ struct scratch_data {
bool is_open { false };
} trainset;
std::string name;
bool initialized { false };
};
@@ -321,6 +322,9 @@ public:
// legacy method, updates sounds around camera
void
update_sounds();
// checks whether specified file is a valid region data file
bool
is_scene( std::string const &Scenariofile ) const;
// stores content of the class in file with specified name
void
serialize( std::string const &Scenariofile ) const;

View File

@@ -76,6 +76,9 @@ world_environment::init() {
m_moon.init();
m_stars.init();
m_clouds.Init();
m_precipitation.init();
m_precipitationsound.deserialize( "rain-sound-loop", sound_type::single );
}
void
@@ -147,6 +150,24 @@ world_environment::update() {
// but quite effective way to make the distant items blend with background better
// NOTE: base brightness calculation provides scaled up value, so we bring it back to 'real' one here
Global.FogColor = m_skydome.GetAverageHorizonColor();
// weather-related simulation factors
// TODO: dynamic change of air temperature and overcast levels
if( Global.Weather == "rain:" ) {
// reduce friction in rain
Global.FrictionWeatherFactor = 0.85f;
m_precipitationsound.play( sound_flags::exclusive | sound_flags::looping );
}
else if( Global.Weather == "snow:" ) {
// reduce friction due to snow
Global.FrictionWeatherFactor = 0.75f;
}
}
void
world_environment::update_precipitation() {
m_precipitation.update();
}
void

View File

@@ -14,6 +14,8 @@ http://mozilla.org/MPL/2.0/.
#include "moon.h"
#include "stars.h"
#include "skydome.h"
#include "precipitation.h"
#include "sound.h"
class opengl_renderer;
@@ -26,6 +28,7 @@ public:
// methods
void init();
void update();
void update_precipitation();
void time( int const Hour = -1, int const Minute = -1, int const Second = -1 );
// switches between static and dynamic daylight calculation
void toggle_daylight();
@@ -41,6 +44,9 @@ private:
cSun m_sun;
cMoon m_moon;
TSky m_clouds;
basic_precipitation m_precipitation;
sound_source m_precipitationsound { sound_placement::external, -1 };
};
namespace simulation {

View File

@@ -37,9 +37,11 @@ state_serializer::deserialize( std::string const &Scenariofile ) {
// TODO: check first for presence of serialized binary files
// if this fails, fall back on the legacy text format
scene::scratch_data importscratchpad;
importscratchpad.name = Scenariofile;
if( Scenariofile != "$.scn" ) {
// compilation to binary file isn't supported for rainsted-created overrides
importscratchpad.binary.terrain = Region->deserialize( Scenariofile );
// NOTE: we postpone actual loading of the scene until we process time, season and weather data
importscratchpad.binary.terrain = Region->is_scene( Scenariofile ) ;
}
// NOTE: for the time being import from text format is a given, since we don't have full binary serialization
cParser scenarioparser( Scenariofile, cParser::buffer_FILE, Global.asCurrentSceneryPath, Global.bLoadTraction );
@@ -266,6 +268,12 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc
if( true == Scratchpad.initialized ) { return; }
if( true == Scratchpad.binary.terrain ) {
// at this stage it should be safe to import terrain from the binary scene file
// TBD: postpone loading furter and only load required blocks during the simulation?
Region->deserialize( Scratchpad.name );
}
simulation::Paths.InitTracks();
simulation::Traction.InitTraction();
simulation::Events.InitEvents();

View File

@@ -1,5 +1,5 @@
#pragma once
#define VERSION_MAJOR 18
#define VERSION_MINOR 919
#define VERSION_MINOR 923
#define VERSION_REVISION 0