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

Merge branch 'gfx-work' into sim

This commit is contained in:
milek7
2019-08-08 15:56:06 +02:00
29 changed files with 1178 additions and 84 deletions

View File

@@ -260,7 +260,19 @@ bool TAnimModel::Init(std::string const &asName, std::string const &asReplacable
asText = asReplacableTexture.substr( 1, asReplacableTexture.length() - 1 ); // zapamiętanie tekstu
}
else if( asReplacableTexture != "none" ) {
/*
auto const texturepath { substr_path( asReplacableTexture ) };
if( false == texturepath.empty() ) {
Global.asCurrentTexturePath = texturepath;
}
*/
m_materialdata.replacable_skins[ 1 ] = GfxRenderer.Fetch_Material( asReplacableTexture );
/*
if( false == texturepath.empty() ) {
// z powrotem defaultowa sciezka do tekstur
Global.asCurrentTexturePath = std::string( szTexturePath );
}
*/
}
if( ( m_materialdata.replacable_skins[ 1 ] != null_handle )
&& ( GfxRenderer.Material( m_materialdata.replacable_skins[ 1 ] ).get_or_guess_opacity() == 0.0f ) ) {

View File

@@ -125,6 +125,7 @@ set(SOURCES
"precipitation.cpp"
"pythonscreenviewer.cpp"
"dictionary.cpp"
"particles.cpp"
"network/network.cpp"
"network/message.cpp"

View File

@@ -40,6 +40,7 @@ class powergridsource_table;
class instance_table;
class vehicle_table;
struct light_array;
class particle_manager;
struct dictionary_source;
namespace scene {

View File

@@ -3432,30 +3432,35 @@ void TController::SpeedSet()
break;
case TEngineType::DieselEngine:
// Ra 2014-06: "automatyczna" skrzynia biegów...
if (!mvControlling->MotorParam[mvControlling->ScndCtrlPos].AutoSwitch) // gdy biegi ręczne
if( false == mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].AutoSwitch ) {
// gdy biegi ręczne
if( ( mvControlling->ShuntMode ? mvControlling->AnPos : 1.0 ) * mvControlling->Vel >
0.75 * mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mfi )
// if (mvControlling->enrot>0.95*mvControlling->dizel_nMmax) //youBy: jeśli obroty >
// 0,95 nmax, wrzuć wyższy bieg - Ra: to nie działa
{ // jak prędkość większa niż 0.6 maksymalnej na danym biegu, wrzucić wyższy
if( mvControlling->ScndCtrlPos < mvControlling->ScndCtrlPosNo ) {
// ...presuming there is a higher gear
mvControlling->DecMainCtrl( 2 );
if (mvControlling->IncScndCtrl(1))
if (mvControlling->MotorParam[mvControlling->ScndCtrlPos].mIsat ==
0.0) // jeśli bieg jałowy
mvControlling->IncScndCtrl(1); // to kolejny
if( mvControlling->IncScndCtrl( 1 ) ) {
while( ( mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mIsat == 0.0 ) // jeśli bieg jałowy
&& ( mvControlling->IncScndCtrl( 1 ) ) ) { // to kolejny
;
}
}
}
}
else if( ( mvControlling->ShuntMode ? mvControlling->AnPos : 1.0 ) * mvControlling->Vel <
mvControlling->MotorParam[mvControlling->ScndCtrlPos].fi)
{ // jak prędkość mniejsza niż minimalna na danym biegu, wrzucić niższy
mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].fi ) { // jak prędkość mniejsza niż minimalna na danym biegu, wrzucić niższy
mvControlling->DecMainCtrl( 2 );
mvControlling->DecScndCtrl( 1 );
if (mvControlling->MotorParam[mvControlling->ScndCtrlPos].mIsat ==
0.0) // jeśli bieg jałowy
if( mvControlling->MotorParam[ mvControlling->ScndCtrlPos ].mIsat == 0.0 ) // jeśli bieg jałowy
if( mvControlling->ScndCtrlPos ) // a jeszcze zera nie osiągnięto
mvControlling->DecScndCtrl( 1 ); // to kolejny wcześniejszy
else
mvControlling->IncScndCtrl( 1 ); // a jak zeszło na zero, to powrót
}
}
break;
}
};

View File

@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
#include "Console.h"
#include "PyInt.h"
#include "Timer.h"
#include "vao.h"
global_settings Global;
@@ -887,6 +888,10 @@ global_settings::ConfigParse(cParser &Parser) {
Parser >> conf.transform[i / 4][i % 4];
extra_viewports.push_back(conf);
if (gl::vao::use_vao) {
gl::vao::use_vao = false;
WriteLog("using multiple viewports, disabling vao!");
}
}
else if (token == "map.highlightdistance") {
Parser.getTokens(1);

View File

@@ -1683,6 +1683,8 @@ void TMoverParameters::HeatingCheck( double const Timestep ) {
auto const absrevolutions { std::abs( generator.revolutions ) };
generator.voltage = (
false == HeatingAllow ? 0.0 :
// TODO: add support for desired voltage selector
absrevolutions < generator.revolutions_min ? generator.voltage_min * absrevolutions / generator.revolutions_min :
// absrevolutions > generator.revolutions_max ? generator.voltage_max * absrevolutions / generator.revolutions_max :
interpolate(
@@ -4543,9 +4545,12 @@ double TMoverParameters::TractionForce( double dt ) {
EngineHeatingRPM )
/ 60.0 );
}
// NOTE: fake dizel_fill calculation for the sake of smoke emitter which uses this parameter to determine smoke opacity
dizel_fill = clamp( 0.2 + 0.35 * ( tmp - enrot ), 0.0, 1.0 );
}
else {
tmp = 0.0;
dizel_fill = 0.0;
}
if( enrot != tmp ) {
@@ -6150,7 +6155,7 @@ void TMoverParameters::CheckEIMIC(double dt)
{
if (eimic > 0.001)
eimic = std::max(0.002, eimic * (double)MainCtrlPosNo / ((double)MainCtrlPosNo - 1.0) - 1.0 / ((double)MainCtrlPosNo - 1.0));
if (eimic < -0.001)
if ((eimic < -0.001) && (BrakeHandle != TBrakeHandle::MHZ_EN57))
eimic = std::min(-0.002, eimic * (double)LocalBrakePosNo / ((double)LocalBrakePosNo - 1.0) + 1.0 / ((double)LocalBrakePosNo - 1.0));
}
break;

View File

@@ -740,7 +740,8 @@ void TSubModel::InitialRotate(bool doit)
}
else if (Global.iConvertModels & 2) {
// optymalizacja jest opcjonalna
if ((iFlags & 0xC000) == 0x8000) // o ile nie ma animacji
if ( ((iFlags & 0xC000) == 0x8000) // o ile nie ma animacji
&& ( false == is_emitter() ) ) // don't optimize smoke emitter attachment points
{ // jak nie ma potomnych, można wymnożyć przez transform i wyjedynkować go
float4x4 *mat = GetMatrix(); // transform submodelu
if( false == Vertices.empty() ) {
@@ -847,6 +848,26 @@ TSubModel::find_replacable4() {
return std::make_tuple( nullptr, false );
}
// locates particle emitter submodels and adds them to provided list
void
TSubModel::find_smoke_sources( nameoffset_sequence &Sourcelist ) const {
auto const name { ToLower( pName ) };
if( ( eType == TP_ROTATOR )
&& ( pName.find( "smokesource_" ) == 0 ) ) {
Sourcelist.emplace_back( pName, offset() );
}
if( Next != nullptr ) {
Next->find_smoke_sources( Sourcelist );
}
if( Child != nullptr ) {
Child->find_smoke_sources( Sourcelist );
}
}
uint32_t TSubModel::FlagsCheck()
{ // analiza koniecznych zmian pomiędzy submodelami
// samo pomijanie glBindTexture() nie poprawi wydajności
@@ -1171,6 +1192,14 @@ void TSubModel::ColorsSet( glm::vec3 const &Ambient, glm::vec3 const &Diffuse, g
*/
};
bool
TSubModel::is_emitter() const {
return (
( eType == TP_ROTATOR )
&& ( ToLower( pName ).find( "smokesource_" ) == 0 ) );
}
// pobranie transformacji względem wstawienia modelu
void TSubModel::ParentMatrix( float4x4 *m ) const {
@@ -1269,8 +1298,9 @@ TModel3d::~TModel3d() {
}
};
TSubModel *TModel3d::AddToNamed(const char *Name, TSubModel *SubModel)
{
TSubModel *
TModel3d::AddToNamed(const char *Name, TSubModel *SubModel) {
TSubModel *sm = Name ? GetFromName(Name) : nullptr;
if( ( sm == nullptr )
&& ( Name != nullptr ) && ( std::strcmp( Name, "none" ) != 0 ) ) {
@@ -1282,6 +1312,7 @@ TSubModel *TModel3d::AddToNamed(const char *Name, TSubModel *SubModel)
// jedyny poprawny sposób dodawania submodeli, inaczej mogą zginąć przy zapisie E3D
void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel) {
if (tmp) {
// jeśli znaleziony, podłączamy mu jako potomny
tmp->ChildAdd(SubModel);
@@ -1308,6 +1339,18 @@ TSubModel *TModel3d::GetFromName(std::string const &Name) const
}
};
// locates particle source submodels and stores them on internal list
nameoffset_sequence const &
TModel3d::find_smoke_sources() {
m_smokesources.clear();
if( Root != nullptr ) {
Root->find_smoke_sources( m_smokesources );
}
return smoke_sources();
}
// returns offset vector from root
glm::vec3
TSubModel::offset( float const Geometrytestoffsetthreshold ) const {
@@ -1954,6 +1997,8 @@ void TModel3d::Init()
asBinary = ""; // zablokowanie powtórnego zapisu
}
}
// check if the model contains particle emitters
find_smoke_sources();
};
//-----------------------------------------------------------------------------

View File

@@ -48,6 +48,7 @@ class shape_node;
}
class TModel3d;
using nameoffset_sequence = std::vector<std::pair<std::string, glm::vec3>>;
class TSubModel
{ // klasa submodelu - pojedyncza siatka, punkt świetlny albo grupa punktów
@@ -148,6 +149,8 @@ private:
int SeekFaceNormal( std::vector<unsigned int> const &Masks, int const Startface, unsigned int const Mask, glm::vec3 const &Position, gfx::vertex_array const &Vertices );
void RaAnimation(TAnimType a);
void RaAnimation(glm::mat4 &m, TAnimType a);
// returns true if the submodel is a smoke emitter attachment point, false otherwise
bool is_emitter() const;
public:
static size_t iInstance; // identyfikator egzemplarza, który aktualnie renderuje model
@@ -167,6 +170,8 @@ public:
int count_children();
// locates submodel mapped with replacable -4
std::tuple<TSubModel *, bool> find_replacable4();
// locates particle emitter submodels and adds them to provided list
void find_smoke_sources( nameoffset_sequence &Sourcelist ) const;
int TriangleAdd(TModel3d *m, material_handle tex, int tri);
void SetRotate(float3 vNewRotateAxis, float fNewAngle);
void SetRotateXYZ( Math3D::vector3 vNewAngles);
@@ -242,6 +247,7 @@ private:
int iSubModelsCount; // Ra: używane do tworzenia binarnych
std::string asBinary; // nazwa pod którą zapisać model binarny
std::string m_filename;
nameoffset_sequence m_smokesources; // list of particle sources defined in the model
public:
TModel3d();
@@ -254,6 +260,7 @@ public:
inline TSubModel * GetSMRoot() { return (Root); };
TSubModel * GetFromName(std::string const &Name) const;
TSubModel * AddToNamed(const char *Name, TSubModel *SubModel);
nameoffset_sequence const & find_smoke_sources();
void AddTo(TSubModel *tmp, TSubModel *SubModel);
void LoadFromTextFile(std::string const &FileName, bool dynamic);
void LoadFromBinFile(std::string const &FileName, bool dynamic);
@@ -262,6 +269,8 @@ public:
uint32_t Flags() const { return iFlags; };
void Init();
std::string NameGet() const { return m_filename; };
nameoffset_sequence const & smoke_sources() const {
return m_smokesources; }
int TerrainCount() const;
TSubModel * TerrainSquare(int n);
void deserialize(std::istream &s, size_t size, bool dynamic);

View File

@@ -5043,12 +5043,22 @@ void TTrain::OnCommand_radiocall3send( TTrain *Train, command_data const &Comman
void TTrain::OnCommand_cabchangeforward( TTrain *Train, command_data const &Command ) {
if( Command.action == GLFW_PRESS ) {
if( false == Train->CabChange( 1 ) ) {
if( TestFlag( Train->DynamicObject->MoverParameters->Couplers[ end::front ].CouplingFlag, coupling::gangway ) ) {
auto const movedirection {
1 * ( Train->DynamicObject->ctOwner->Vehicle( end::front )->DirectionGet() == Train->DynamicObject->DirectionGet() ?
1 :
-1 ) };
if( false == Train->CabChange( movedirection ) ) {
auto const exitdirection { (
movedirection > 0 ?
end::front :
end::rear ) };
if( TestFlag( Train->DynamicObject->MoverParameters->Couplers[ exitdirection ].CouplingFlag, coupling::gangway ) ) {
// przejscie do nastepnego pojazdu
TDynamicObject *dynobj = Train->DynamicObject->PrevConnected();
TDynamicObject *dynobj = ( exitdirection == end::front ?
Train->DynamicObject->PrevConnected() :
Train->DynamicObject->NextConnected() );
dynobj->MoverParameters->ActiveCab = (
Train->DynamicObject->MoverParameters->Neighbours[end::front].vehicle_end ?
Train->DynamicObject->MoverParameters->Neighbours[exitdirection].vehicle_end ?
-1 :
1 );
Train->MoveToVehicle(dynobj);
@@ -5064,12 +5074,22 @@ void TTrain::OnCommand_cabchangeforward( TTrain *Train, command_data const &Comm
void TTrain::OnCommand_cabchangebackward( TTrain *Train, command_data const &Command ) {
if( Command.action == GLFW_PRESS ) {
if( false == Train->CabChange( -1 ) ) {
if( TestFlag( Train->DynamicObject->MoverParameters->Couplers[ end::rear ].CouplingFlag, coupling::gangway ) ) {
auto const movedirection {
-1 * ( Train->DynamicObject->ctOwner->Vehicle( end::front )->DirectionGet() == Train->DynamicObject->DirectionGet() ?
1 :
-1 ) };
if( false == Train->CabChange( movedirection ) ) {
auto const exitdirection { (
movedirection > 0 ?
end::front :
end::rear ) };
if( TestFlag( Train->DynamicObject->MoverParameters->Couplers[ exitdirection ].CouplingFlag, coupling::gangway ) ) {
// przejscie do nastepnego pojazdu
TDynamicObject *dynobj = Train->DynamicObject->NextConnected();
TDynamicObject *dynobj = ( exitdirection == end::front ?
Train->DynamicObject->PrevConnected() :
Train->DynamicObject->NextConnected() );
dynobj->MoverParameters->ActiveCab = (
Train->DynamicObject->MoverParameters->Neighbours[end::rear].vehicle_end ?
Train->DynamicObject->MoverParameters->Neighbours[exitdirection].vehicle_end ?
-1 :
1 );
Train->MoveToVehicle(dynobj);
@@ -8517,6 +8537,18 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con
gauge.AssignDouble(&mvControlled->AnPos);
m_controlmapper.insert( gauge, "shuntmodepower:" );
}
else if( Label == "heatingvoltage:" ) {
if( mvControlled->HeatingPowerSource.SourceType == TPowerSource::Generator ) {
auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
gauge.Load( Parser, DynamicObject );
gauge.AssignDouble( &(mvControlled->HeatingPowerSource.EngineGenerator.voltage) );
}
}
else if( Label == "heatingcurrent:" ) {
auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
gauge.Load( Parser, DynamicObject );
gauge.AssignDouble( &( mvControlled->TotalCurrent ) );
}
else
{
// failed to match the label

View File

@@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/.
#include "simulationtime.h"
#include "simulationenvironment.h"
#include "lightarray.h"
#include "particles.h"
#include "Train.h"
#include "Driver.h"
#include "DynObj.h"
@@ -256,6 +257,8 @@ driver_mode::update() {
simulation::Region->update_sounds();
audio::renderer.update( Global.iPause ? 0.0 : deltarealtime );
// NOTE: particle system runs on simulation time, but needs actual camera position to determine how to update each particle source
simulation::Particles.update();
GfxRenderer.Update( deltarealtime );
simulation::is_ready = true;

View File

@@ -751,15 +751,12 @@ debug_panel::update_vehicle_brake() const {
void
debug_panel::update_section_engine( std::vector<text_line> &Output ) {
if( m_input.train == nullptr ) { return; }
if( m_input.vehicle == nullptr ) { return; }
if( m_input.mover == nullptr ) { return; }
auto const &train { *m_input.train };
auto const &vehicle{ *m_input.vehicle };
auto const &mover{ *m_input.mover };
// engine data
// induction motor data
if( mover.EngineType == TEngineType::ElectricInductionMotor ) {
@@ -772,7 +769,10 @@ debug_panel::update_section_engine( std::vector<text_line> &Output ) {
+ mover.eimv_labels[ i ] + to_string( mover.eimv[ i ], 2, 9 );
if( i < 10 ) {
parameters += " | " + train.fPress_labels[ i ] + to_string( train.fPress[ i ][ 0 ], 2, 9 );
parameters +=
( ( m_input.train != nullptr ) && ( m_input.train->Dynamic() == m_input.vehicle ) ?
" | " + TTrain::fPress_labels[ i ] + to_string( m_input.train->fPress[ i ][ 0 ], 2, 9 ) :
"" );
}
else if( i == 12 ) {
parameters += " med:";

View File

@@ -1,49 +1,49 @@
#include "stdafx.h"
#include "vao.h"
/*
bool gl::vao::use_vao = true;
gl::vao::vao()
{
if (!use_vao)
return;
glGenVertexArrays(1, *this);
}
gl::vao::~vao()
{
if (!use_vao)
return;
glDeleteVertexArrays(1, *this);
}
void gl::vao::setup_attrib(gl::buffer &buffer, int attrib, int size, int type, int stride, int offset)
{
if (use_vao) {
bind();
buffer.bind(gl::buffer::ARRAY_BUFFER);
buffer.bind(buffer::ARRAY_BUFFER);
glVertexAttribPointer(attrib, size, type, GL_FALSE, stride, reinterpret_cast<void*>(offset));
glEnableVertexAttribArray(attrib);
}
void gl::vao::setup_ebo(buffer &ebo)
{
bind();
ebo.bind(gl::buffer::ELEMENT_ARRAY_BUFFER);
}
void gl::vao::bind(GLuint i)
{
glBindVertexArray(i);
}
*/
void gl::vao::setup_attrib(gl::buffer &buffer, int attrib, int size, int type, int stride, int offset)
{
else {
params.push_back({buffer, attrib, size, type, stride, offset});
active = nullptr;
}
}
void gl::vao::setup_ebo(buffer &e)
{
if (use_vao) {
bind();
e.bind(buffer::ELEMENT_ARRAY_BUFFER);
}
else {
ebo = &e;
active = nullptr;
}
}
void gl::vao::bind()
{
@@ -51,6 +51,10 @@ void gl::vao::bind()
return;
active = this;
if (use_vao) {
glBindVertexArray(*this);
}
else {
for (attrib_params &param : params) {
param.buffer.bind(gl::buffer::ARRAY_BUFFER);
glVertexAttribPointer(param.attrib, param.size, param.type, GL_FALSE, param.stride, reinterpret_cast<void*>(param.offset));
@@ -65,6 +69,7 @@ void gl::vao::bind()
else
gl::buffer::unbind(gl::buffer::ELEMENT_ARRAY_BUFFER);
}
}
void gl::vao::unbind()
{

View File

@@ -6,7 +6,7 @@
namespace gl
{
class vao : public bindable<vao>
class vao : object, public bindable<vao>
{
struct attrib_params {
// TBD: should be shared_ptr? (when buffer is destroyed by owner VAO could still potentially exist)
@@ -23,15 +23,15 @@ namespace gl
std::vector<attrib_params> params;
public:
//vao();
//~vao();
vao();
~vao();
void setup_attrib(buffer &buffer, int attrib, int size, int type, int stride, int offset);
void setup_ebo(buffer &ebo);
//using bindable::bind;
void bind();
static void unbind();
//static void bind(GLuint i);
static bool use_vao;
};
}

View File

@@ -79,6 +79,19 @@ cParser::~cParser() {
}
}
template <>
glm::vec3
cParser::getToken( bool const ToLower, char const *Break ) {
// NOTE: this specialization ignores default arguments
getTokens( 3, false, "\n\r\t ,;[]" );
glm::vec3 output;
*this
>> output.x
>> output.y
>> output.z;
return output;
};
template<>
cParser&
cParser::operator>>( std::string &Right ) {

View File

@@ -37,7 +37,7 @@ class cParser //: public std::stringstream
operator>>( Type_ &Right );
template <typename Output_>
Output_
getToken( bool const ToLower = true, const char *Break = "\n\r\t ;" ) {
getToken( bool const ToLower = true, char const *Break = "\n\r\t ;" ) {
getTokens( 1, ToLower, Break );
Output_ output;
*this >> output;
@@ -108,6 +108,12 @@ class cParser //: public std::stringstream
std::deque<std::string> tokens;
};
template <>
glm::vec3
cParser::getToken( bool const ToLower, const char *Break );
template<typename Type_>
cParser&
cParser::operator>>( Type_ &Right ) {

409
particles.cpp Normal file
View File

@@ -0,0 +1,409 @@
/*
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 "particles.h"
#include "Timer.h"
#include "Globals.h"
#include "AnimModel.h"
#include "simulationenvironment.h"
void
smoke_source::particle_emitter::deserialize( cParser &Input ) {
if( Input.getToken<std::string>() != "{" ) { return; }
std::unordered_map<std::string, float &> const variablemap{
{ "min_inclination:", inclination[ value_limit::min ] },
{ "max_inclination:", inclination[ value_limit::max ] },
{ "min_velocity:", velocity[ value_limit::min ] },
{ "max_velocity:", velocity[ value_limit::max ] },
{ "min_size:", size[ value_limit::min ] },
{ "max_size:", size[ value_limit::max ] },
{ "min_opacity:", opacity[ value_limit::min ] },
{ "max_opacity:", opacity[ value_limit::max ] } };
std::string key;
while( ( false == ( ( key = Input.getToken<std::string>( true, "\n\r\t ,;[]" ) ).empty() ) )
&& ( key != "}" ) ) {
auto const lookup { variablemap.find( key ) };
if( lookup == variablemap.end() ) { continue; }
lookup->second = Input.getToken<float>( true, "\n\r\t ,;[]" );
}
}
void
smoke_source::particle_emitter::initialize( smoke_particle &Particle ) {
auto const polarangle { glm::radians( Random( inclination[ value_limit::min ], inclination[ value_limit::max ] ) ) }; // theta
auto const azimuthalangle { glm::radians( Random( -180, 180 ) ) }; // phi
// convert spherical coordinates to opengl coordinates
auto const launchvector { glm::vec3(
std::sin( polarangle ) * std::sin( azimuthalangle ),
std::cos( polarangle ),
std::sin( polarangle ) * std::cos( azimuthalangle ) * -1 ) };
auto const launchvelocity { static_cast<float>( Random( velocity[ value_limit::min ], velocity[ value_limit::max ] ) ) };
Particle.velocity = launchvector * launchvelocity;
Particle.rotation = glm::radians( Random( 0, 360 ) );
Particle.size = Random( size[ value_limit::min ], size[ value_limit::max ] );
Particle.opacity = Random( opacity[ value_limit::min ], opacity[ value_limit::max ] );
Particle.age = 0;
}
bool
smoke_source::deserialize( cParser &Input ) {
if( false == Input.ok() ) { return false; }
while( true == deserialize_mapping( Input ) ) {
; // all work done by while()
}
return true;
}
// imports member data pair from the config file
bool
smoke_source::deserialize_mapping( cParser &Input ) {
// token can be a key or block end
std::string const key { Input.getToken<std::string>( true, "\n\r\t ,;[]" ) };
if( ( true == key.empty() ) || ( key == "}" ) ) { return false; }
// if not block end then the key is followed by assigned value or sub-block
if( key == "spawn_rate:" ) {
Input.getTokens();
Input >> m_spawnrate;
}
else if( key == "initializer:" ) {
m_emitter.deserialize( Input );
}
/*
else if( key == "velocity_change:" ) {
m_velocitymodifier.deserialize( Input );
}
*/
else if( key == "size_change:" ) {
m_sizemodifier.deserialize( Input );
}
else if( key == "opacity_change:" ) {
m_opacitymodifier.deserialize( Input );
}
return true; // return value marks a [ key: value ] pair was extracted, nothing about whether it's recognized
}
void
smoke_source::initialize() {
m_particles.reserve(
// put a cap on number of particles in a single source. TBD, TODO: make it part of he source configuration?
std::min(
2000,
// NOTE: given nature of the smoke we're presuming opacity decreases over time and the particle is killed when it reaches 0
// this gives us estimate of longest potential lifespan of single particle, and how many particles total can there be at any given time
// TBD, TODO: explicit lifespan variable as part of the source configuration?
static_cast<int>( m_spawnrate / std::abs( m_opacitymodifier.value_change() ) ) ) );
}
void
smoke_source::bind( TDynamicObject const *Vehicle ) {
m_owner.vehicle = Vehicle;
m_ownertype = (
m_owner.vehicle != nullptr ?
owner_type::vehicle :
owner_type::none );
}
void
smoke_source::bind( TAnimModel const *Node ) {
m_owner.node = Node;
m_ownertype = (
m_owner.node != nullptr ?
owner_type::node :
owner_type::none );
}
// updates state of owned particles
void
smoke_source::update( double const Timedelta, bool const Onlydespawn ) {
// prepare bounding box for new pass
// TODO: include bounding box in the bounding_area class
bounding_box boundingbox {
glm::dvec3{ std::numeric_limits<double>::max() },
glm::dvec3{ std::numeric_limits<double>::lowest() } };
m_spawncount = (
Onlydespawn ?
0.f :
std::min<float>(
m_spawncount + ( m_spawnrate * Timedelta ),
m_particles.capacity() ) );
// update spawned particles
for( auto particleiterator { std::begin( m_particles ) }; particleiterator != std::end( m_particles ); ++particleiterator ) {
auto &particle { *particleiterator };
bool particleisalive;
while( ( false == ( particleisalive = update( particle, boundingbox, Timedelta ) ) )
&& ( m_spawncount >= 1.f ) ) {
// replace dead particle with a new one
m_spawncount -= 1.f;
initialize( particle );
}
if( false == particleisalive ) {
// we have a dead particle and no pending spawn requests, (try to) move the last particle here
do {
if( std::next( particleiterator ) == std::end( m_particles ) ) { break; } // already at last particle
particle = m_particles.back();
m_particles.pop_back();
} while( false == ( particleisalive = update( particle, boundingbox, Timedelta ) ) );
}
if( false == particleisalive ) {
// NOTE: if we're here it means the iterator is at last container slot which holds a dead particle about to be eliminated...
m_particles.pop_back();
// ...since this effectively makes the iterator now point at end() and the advancement at the end of the loop will move it past end()
// we have to break the loop manually (could use < comparison but with both ways being ugly, this is
break;
}
}
// spawn pending particles in remaining container slots
while( ( m_spawncount >= 1.f )
&& ( m_particles.size() < m_particles.capacity() ) ) {
m_spawncount -= 1.f;
// work with a temporary copy in case initial update renders the particle dead
smoke_particle newparticle;
initialize( newparticle );
if( true == update( newparticle, boundingbox, Timedelta ) ) {
// if the new particle didn't die immediately place it in the container...
m_particles.emplace_back( newparticle );
}
}
// if we still have pending requests after filling entire container replace older particles
if( m_spawncount >= 1.f ) {
// sort all particles from most to least transparent, oldest to youngest if it's a tie
std::sort(
std::begin( m_particles ),
std::end( m_particles ),
[]( smoke_particle const &Left, smoke_particle const &Right ) {
return ( Left.opacity != Right.opacity ?
Left.opacity < Right.opacity :
Left.age > Right.age ); } );
// replace old particles with new ones until we run out of either requests or room
for( auto &particle : m_particles ) {
while( m_spawncount >= 1.f ) {
m_spawncount -= 1.f;
// work with a temporary copy so we don't wind up with replacing a good particle with a dead on arrival one
smoke_particle newparticle;
initialize( newparticle );
if( true == update( newparticle, boundingbox, Timedelta ) ) {
// if the new particle didn't die immediately place it in the container...
particle = newparticle;
// ...and move on to the next slot
break;
}
}
}
// discard pending spawn requests our container couldn't fit
m_spawncount -= std::floor( m_spawncount );
}
// determine bounding area from calculated bounding box
if( false == m_particles.empty() ) {
m_area.center = interpolate( boundingbox[ value_limit::min ], boundingbox[ value_limit::max ], 0.5 );
m_area.radius = 0.5 * ( glm::length( boundingbox[ value_limit::max ] - boundingbox[ value_limit::min ] ) );
}
else {
m_area.center = location();
m_area.radius = 0;
}
}
glm::dvec3
smoke_source::location() const {
glm::dvec3 location;
switch( m_ownertype ) {
case owner_type::vehicle: {
location = glm::dvec3 {
m_offset.x * m_owner.vehicle->VectorLeft()
+ m_offset.y * m_owner.vehicle->VectorUp()
+ m_offset.z * m_owner.vehicle->VectorFront() };
location += glm::dvec3{ m_owner.vehicle->GetPosition() };
break;
}
case owner_type::node: {
// TODO: take into account node rotation
auto const rotation { glm::angleAxis( glm::radians( m_owner.node->Angles().y ), glm::vec3{ 0.f, 1.f, 0.f } ) };
location = rotation * glm::vec3{ m_offset };
location += m_owner.node->location();
break;
}
default: {
location = m_offset;
break;
}
}
return location;
}
// sets particle state to fresh values
void
smoke_source::initialize( smoke_particle &Particle ) {
m_emitter.initialize( Particle );
Particle.position = location();
if( m_ownertype == owner_type::vehicle ) {
Particle.opacity *= m_owner.vehicle->MoverParameters->dizel_fill;
switch( m_owner.vehicle->MoverParameters->EngineType ) {
case TEngineType::DieselElectric: {
Particle.velocity *= 1.0 + m_owner.vehicle->MoverParameters->enrot / ( m_owner.vehicle->MoverParameters->DElist[ m_owner.vehicle->MoverParameters->MainCtrlPosNo ].RPM / 60.0 );
break;
}
case TEngineType::DieselEngine: {
Particle.velocity *= 1.0 + m_owner.vehicle->MoverParameters->enrot / m_owner.vehicle->MoverParameters->nmax;
break;
}
default: {
break;
}
}
}
}
// updates state of provided particle and bounding box. returns: true if particle is still alive afterwards, false otherwise
bool
smoke_source::update( smoke_particle &Particle, bounding_box &Boundingbox, double const Timedelta ) {
m_opacitymodifier.update( Particle.opacity, Timedelta );
// if the particle is dead we can bail out early...
if( Particle.opacity <= 0.f ) { return false; }
// ... otherwise proceed with full update
m_sizemodifier.update( Particle.size, Timedelta );
// crude smoke dispersion simulation
// http://www.auburn.edu/academic/forestry_wildlife/fire/smoke_guide/smoke_dispersion.htm
Particle.velocity.y += ( 0.005 * Particle.velocity.y ) * std::min( 0.f, Global.AirTemperature - 10 ) * Timedelta; // decelerate faster in cold weather
Particle.velocity.y -= ( 0.010 * Particle.velocity.y ) * Global.Overcast * Timedelta; // decelerate faster with high air humidity and/or precipitation
Particle.velocity.y = std::max<float>( 0.25 * ( 2.f - Global.Overcast ), Particle.velocity.y ); // put a cap on deceleration
Particle.position += Particle.velocity * static_cast<float>( Timedelta );
Particle.position += 0.1f * Particle.age * simulation::Environment.wind() * static_cast<float>( Timedelta );
// m_velocitymodifier.update( Particle.velocity, Timedelta );
Particle.age += Timedelta;
// update bounding box
Boundingbox[ value_limit::min ] = glm::min( Boundingbox[ value_limit::min ], Particle.position - glm::dvec3{ Particle.size } );
Boundingbox[ value_limit::max ] = glm::max( Boundingbox[ value_limit::max ], Particle.position + glm::dvec3{ Particle.size } );
return true;
}
// adds a new particle source of specified type, placing it in specified world location
// returns: true on success, false if the specified type definition couldn't be located
bool
particle_manager::insert( std::string const &Sourcetemplate, glm::dvec3 const Location ) {
auto const *sourcetemplate { find( Sourcetemplate ) };
if( sourcetemplate == nullptr ) { return false; }
// ...if template lookup didn't fail put template clone on the source list and initialize it
m_sources.emplace_back( *sourcetemplate );
auto &source { m_sources.back() };
source.initialize();
source.m_offset = Location;
return true;
}
bool
particle_manager::insert( std::string const &Sourcetemplate, TDynamicObject const *Vehicle, glm::dvec3 const Location ) {
if( false == insert( Sourcetemplate, Location ) ) { return false; }
// attach the source to specified vehicle
auto &source { m_sources.back() };
source.bind( Vehicle );
return true;
}
bool
particle_manager::insert( std::string const &Sourcetemplate, TAnimModel const *Node, glm::dvec3 const Location ) {
if( false == insert( Sourcetemplate, Location ) ) { return false; }
// attach the source to specified node
auto &source { m_sources.back() };
source.bind( Node );
return true;
}
// updates state of all owned emitters
void
particle_manager::update() {
auto const timedelta { Timer::GetDeltaTime() };
if( timedelta == 0.0 ) { return; }
auto const distancethreshold { 2 * Global.BaseDrawRange * Global.fDistanceFactor }; // to reduce workload distant enough sources won't spawn new particles
for( auto &source : m_sources ) {
auto const viewerdistance { glm::length( source.area().center - glm::dvec3{ Global.pCamera.Pos } ) - source.area().radius };
source.update( timedelta, viewerdistance > distancethreshold );
}
}
smoke_source *
particle_manager::find( std::string const &Template ) {
auto const templatepath { "data/" };
auto const templatename { ToLower( Template ) };
// try to locate specified rail profile...
auto const lookup { m_sourcetemplates.find( templatename ) };
if( lookup != m_sourcetemplates.end() ) {
// ...if it works, we're done...
return &(lookup->second);
}
// ... and if it fails try to add the template to the database from a data file
smoke_source source;
cParser parser( templatepath + templatename + ".txt", cParser::buffer_FILE );
if( source.deserialize( parser ) ) {
// if deserialization didn't fail cache the source as template for future instances
m_sourcetemplates.emplace( templatename, source );
// should be 'safe enough' to return lookup result directly afterwards
return &( m_sourcetemplates.find( templatename )->second );
}
// if fetching data from the file fails too, give up
return nullptr;
}

233
particles.h Normal file
View File

@@ -0,0 +1,233 @@
/*
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 "Classes.h"
#include "scene.h"
// particle specialized for drawing smoke
// given smoke features we can take certain shortcuts
// -- there's no need to sort the particles, can be drawn in any order with depth write turned off
// -- the colour remains consistent throughout, only opacity changes
// -- randomized particle rotation
// -- initial velocity reduced over time to slow drift upwards (drift speed depends on particle and air temperature difference)
// -- size increased over time
struct smoke_particle {
glm::dvec3 position; // meters, 3d space;
float rotation; // radians; local z axis angle
glm::vec3 velocity; // meters per second, 3d space; current velocity
float size; // multiplier, billboard size
// glm::vec4 color; // 0-1 range, rgba; geometry color and opacity
float opacity; // 0-1 range
// glm::vec2 uv_offset; // 0-1 range, uv space; for texture animation
float age; // seconds; time elapsed since creation
// double distance; // meters; distance between particle and camera
};
enum value_limit {
min = 0,
max = 1
};
// helper, adjusts provided variable by fixed amount, keeping resulting value between limits
template <typename Type_>
class fixedstep_modifier {
public:
// methods
void
deserialize( cParser &Input );
// updates state of provided variable
void
update( Type_ &Variable, double const Timedelta ) const;
Type_ const &
value_change() const {
return m_valuechange; }
private:
//types
// methods
// members
// Type_ m_intialvalue { Type_( 0 ) }; // meters per second; velocity applied to freshly spawned particles
Type_ m_valuechange { Type_( 0 ) }; // meters per second; change applied to initial velocity
Type_ m_valuelimits[ 2 ] { Type_( std::numeric_limits<Type_>::lowest() ), Type_( std::numeric_limits<Type_>::max() ) };
};
// particle emitter
class smoke_source {
// located in scenery
// new particles emitted if distance of source < double view range
// existing particles are updated until dead no matter the range (presumed to have certain lifespan)
// during update pass dead particle slots are filled with new instances, if there's no particles queued the slot is swapped with the last particle in the list
// bounding box/sphere calculated based on position of all owned particles, used by the renderer to include/discard data in a draw pass
friend class particle_manager;
public:
// types
using particle_sequence = std::vector<smoke_particle>;
// methods
bool
deserialize( cParser &Input );
void
initialize();
void
bind( TDynamicObject const *Vehicle );
void
bind( TAnimModel const *Node );
// updates state of owned particles
void
update( double const Timedelta, bool const Onlydespawn );
glm::dvec3
location() const;
// provides access to bounding area data
scene::bounding_area const &
area() const {
return m_area; }
particle_sequence const &
sequence() const {
return m_particles; }
private:
// types
enum class owner_type {
none = 0,
vehicle,
node
};
struct particle_emitter {
float inclination[ 2 ] { 0.f, 0.f };
float velocity[ 2 ] { 1.f, 1.f };
float size[ 2 ] { 1.f, 1.f };
float opacity[ 2 ] { 1.f, 1.f };
void deserialize( cParser &Input );
void initialize( smoke_particle &Particle );
};
using bounding_box = glm::dvec3[ 2 ]; // bounding box of owned particles
// methods
// imports member data pair from the config file
bool
deserialize_mapping( cParser &Input );
void
initialize( smoke_particle &Particle );
// updates state of provided particle and bounding box. returns: true if particle is still alive afterwards, false otherwise
bool
update( smoke_particle &Particle, bounding_box &Boundingbox, double const Timedelta );
// members
// config/inputs
// TBD: union and indicator, or just plain owner variables?
owner_type m_ownertype { owner_type::none };
union {
TDynamicObject const * vehicle;
TAnimModel const * node;
} m_owner { nullptr }; // optional, scene item carrying this source
glm::dvec3 m_offset; // meters, 3d space; relative position of the source, either from the owner or the region centre
float m_spawnrate { 0.f }; // number of particles to spawn per second
particle_emitter m_emitter;
// bool m_inheritvelocity { false }; // whether spawned particle should receive velocity of its owner
// TODO: replace modifiers with configurable interpolator item allowing keyframe-based changes over time
// fixedstep_modifier<glm::vec3> m_velocitymodifier; // particle velocity
fixedstep_modifier<float> m_sizemodifier; // particle billboard size
// fixedstep_modifier<glm::vec4> m_colormodifier; // particle billboard color and opacity
fixedstep_modifier<float> m_opacitymodifier;
// texture_handle m_texture { -1 }; // texture assigned to particle billboards
// current state
float m_spawncount { 0.f }; // number of particles to spawn during next update
particle_sequence m_particles; // collection of spawned particles
/*
smoke_sequence::iterator // helpers, iterators marking currently used part of the particle container
m_particlehead,
m_particletail;
*/
scene::bounding_area m_area; // bounding sphere of owned particles
};
// holds all particle emitters defined in the scene and updates their state
class particle_manager {
friend opengl_renderer;
public:
// types
using source_sequence = std::vector<smoke_source>;
// constructors
particle_manager() = default;
// destructor
// ~particle_manager();
// methods
// adds a new particle source of specified type, placing it in specified world location. returns: true on success, false if the specified type definition couldn't be located
bool
insert( std::string const &Sourcetemplate, glm::dvec3 const Location );
bool
insert( std::string const &Sourcetemplate, TDynamicObject const *Vehicle, glm::dvec3 const Location );
bool
insert( std::string const &Sourcetemplate, TAnimModel const *Node, glm::dvec3 const Location );
// updates state of all owned emitters
void
update();
// data access
source_sequence &
sequence() {
return m_sources; }
// members
private:
// types
using source_map = std::unordered_map<std::string, smoke_source>;
// methods
smoke_source *
find( std::string const &Template );
// members
source_map m_sourcetemplates; // cached particle emitter configurations
source_sequence m_sources; // all owned particle emitters
};
template <typename Type_>
void
fixedstep_modifier<Type_>::update( Type_ &Variable, double const Timedelta ) const {
// HACK: float cast to avoid vec3 and double mismatch
// TBD, TODO: replace with vector types specialization
Variable += ( m_valuechange * static_cast<float>( Timedelta ) );
// clamp down to allowed value range
Variable = glm::max( Variable, m_valuelimits[ value_limit::min ] );
Variable = glm::min( Variable, m_valuelimits[ value_limit::max ] );
}
template <typename Type_>
void
fixedstep_modifier<Type_>::deserialize( cParser &Input ) {
if( Input.getToken<std::string>() != "{" ) { return; }
std::unordered_map<std::string, Type_ &> const variablemap {
{ "step:", m_valuechange },
{ "min:", m_valuelimits[ value_limit::min ] },
{ "max:", m_valuelimits[ value_limit::max ] } };
std::string key;
while( ( false == ( ( key = Input.getToken<std::string>( true, "\n\r\t ,;[]" ) ).empty() ) )
&& ( key != "}" ) ) {
auto const lookup { variablemap.find( key ) };
if( lookup == variablemap.end() ) { continue; }
lookup->second = Input.getToken<Type_>( true, "\n\r\t ,;[]" );
}
}

View File

@@ -66,6 +66,117 @@ void opengl_camera::draw(glm::vec3 const &Offset) const
// m7t port to core gl
}
std::vector<std::pair<glm::vec3, glm::vec2>> const billboard_vertices {
{ { -0.5f, -0.5f, 0.f }, { 0.f, 0.f } },
{ { 0.5f, -0.5f, 0.f }, { 1.f, 0.f } },
{ { 0.5f, 0.5f, 0.f }, { 1.f, 1.f } },
{ { -0.5f, -0.5f, 0.f }, { 0.f, 0.f } },
{ { -0.5f, 0.5f, 0.f }, { 0.f, 1.f } },
{ { 0.5f, 0.5f, 0.f }, { 1.f, 1.f } }
};
void
opengl_particles::update( opengl_camera const &Camera ) {
m_particlevertices.clear();
// build a list of visible smoke sources
// NOTE: arranged by distance to camera, if we ever need sorting and/or total amount cap-based culling
std::multimap<float, smoke_source const &> sources;
for( auto const &source : simulation::Particles.sequence() ) {
if( false == Camera.visible( source.area() ) ) { continue; }
// NOTE: the distance is negative when the camera is inside the source's bounding area
sources.emplace(
static_cast<float>( glm::length( Camera.position() - source.area().center ) - source.area().radius ),
source );
}
if( true == sources.empty() ) { return; }
// build billboard data for particles from visible sources
auto const camerarotation { glm::mat3( Camera.modelview() ) };
particle_vertex vertex;
for( auto const &source : sources ) {
auto const &particles { source.second.sequence() };
// TODO: put sanity cap on the overall amount of particles that can be drawn
auto const sizestep { 256.0 * billboard_vertices.size() };
m_particlevertices.reserve(
sizestep * std::ceil( m_particlevertices.size() + ( particles.size() * billboard_vertices.size() ) / sizestep ) );
for( auto const &particle : particles ) {
// TODO: particle color support
vertex.color.r =
vertex.color.g =
vertex.color.b = Global.fLuminance * 0.125f;
vertex.color.a = std::clamp(particle.opacity, 0.0f, 1.0f);
auto const offset { glm::vec3{ particle.position - Camera.position() } };
auto const rotation { glm::angleAxis( particle.rotation, glm::vec3{ 0.f, 0.f, 1.f } ) };
for( auto const &billboardvertex : billboard_vertices ) {
vertex.position = offset + ( rotation * billboardvertex.first * particle.size ) * camerarotation;
vertex.texture = billboardvertex.second;
m_particlevertices.emplace_back( vertex );
}
}
}
// ship the billboard data to the gpu:
// make sure we have enough room...
if( m_buffercapacity < m_particlevertices.size() ) {
m_buffercapacity = m_particlevertices.size();
if (!m_buffer)
m_buffer.emplace();
m_buffer->allocate(gl::buffer::ARRAY_BUFFER,
m_buffercapacity * sizeof(particle_vertex), GL_STREAM_DRAW);
}
if (m_buffer) {
// ...send the data...
m_buffer->upload(gl::buffer::ARRAY_BUFFER,
m_particlevertices.data(), 0, m_particlevertices.size() * sizeof(particle_vertex));
}
}
void
opengl_particles::render() {
if( m_buffercapacity == 0 ) { return; }
if( m_particlevertices.empty() ) { return; }
if (!m_vao) {
m_vao.emplace();
m_vao->setup_attrib(*m_buffer, 0, 3, GL_FLOAT, sizeof(particle_vertex), 0);
m_vao->setup_attrib(*m_buffer, 1, 4, GL_FLOAT, sizeof(particle_vertex), 12);
m_vao->setup_attrib(*m_buffer, 2, 2, GL_FLOAT, sizeof(particle_vertex), 28);
m_buffer->unbind(gl::buffer::ARRAY_BUFFER);
m_vao->unbind();
}
if (!m_shader) {
gl::shader vert("smoke.vert");
gl::shader frag("smoke.frag");
gl::program *prog = new gl::program({vert, frag});
m_shader = std::unique_ptr<gl::program>(prog);
}
m_buffer->bind(gl::buffer::ARRAY_BUFFER);
m_shader->bind();
m_vao->bind();
glDrawArrays(GL_TRIANGLES, 0, m_particlevertices.size());
m_shader->unbind();
m_vao->unbind();
m_buffer->unbind(gl::buffer::ARRAY_BUFFER);
}
bool opengl_renderer::Init(GLFWwindow *Window)
{
if (!Init_caps())
@@ -105,6 +216,7 @@ bool opengl_renderer::Init(GLFWwindow *Window)
m_glaretexture = Fetch_Texture("fx/lightglare");
m_suntexture = Fetch_Texture("fx/sun");
m_moontexture = Fetch_Texture("fx/moon");
m_smoketexture = Fetch_Texture("fx/smoke");
WriteLog("...gfx data pre-loading done");
// prepare basic geometry chunks
@@ -342,6 +454,7 @@ bool opengl_renderer::init_viewport(viewport_config &vp)
if (!Global.gfx_usegles)
glEnable(GL_PROGRAM_POINT_SIZE);
if (!gl::vao::use_vao)
{
GLuint v;
glGenVertexArrays(1, &v);
@@ -668,6 +781,9 @@ void opengl_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
setup_drawing(true);
Render_Alpha(simulation::Region);
// particles
Render_particles();
// precipitation; done at end, only before cab render
Render_precipitation();
@@ -2929,9 +3045,23 @@ void opengl_renderer::Render(TMemCell *Memcell)
::glPopMatrix();
}
void opengl_renderer::Render_particles()
{
// momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside'
glDepthMask(GL_FALSE);
model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW));
model_ubo->update(model_ubs);
Bind_Texture(0, m_smoketexture);
m_particlerenderer.update(m_renderpass.pass_camera);
m_particlerenderer.render();
glDepthMask(GL_TRUE);
}
void opengl_renderer::Render_precipitation()
{
if (Global.Overcast <= 1.f)
{
return;
@@ -2939,7 +3069,10 @@ void opengl_renderer::Render_precipitation()
::glPushMatrix();
// tilt the precipitation cone against the velocity vector for crude motion blur
auto const velocity{simulation::Environment.m_precipitation.m_cameramove * -1.0};
// include current wind vector while at it
auto const velocity {
simulation::Environment.m_precipitation.m_cameramove * -1.0
+ glm::dvec3{ simulation::Environment.wind() } * 0.5 };
if (glm::length2(velocity) > 0.0)
{
auto const forward{glm::normalize(velocity)};

View File

@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
#include "MemCell.h"
#include "scene.h"
#include "light.h"
#include "particles.h"
#include "gl/ubo.h"
#include "gl/framebuffer.h"
#include "gl/renderbuffer.h"
@@ -116,6 +117,42 @@ class opengl_camera
glm::mat4 m_inversetransformation; // cached transformation to world space
};
// particle data visualizer
class opengl_particles {
public:
// constructors
opengl_particles() = default;
// methods
void
update( opengl_camera const &Camera );
void
render( );
private:
// types
struct particle_vertex {
glm::vec3 position; // 3d space
glm::vec4 color; // rgba, unsigned byte format
glm::vec2 texture; // uv space
};
/*
using sourcedistance_pair = std::pair<smoke_source *, float>;
using source_sequence = std::vector<sourcedistance_pair>;
*/
using particlevertex_sequence = std::vector<particle_vertex>;
// methods
// members
/*
source_sequence m_sources; // list of particle sources visible in current render pass, with their respective distances to the camera
*/
particlevertex_sequence m_particlevertices; // geometry data of visible particles, generated on the cpu end
std::optional<gl::buffer> m_buffer;
std::optional<gl::vao> m_vao;
std::unique_ptr<gl::program> m_shader;
std::size_t m_buffercapacity{ 0 }; // total capacity of the last established buffer
};
// bare-bones render controller, in lack of anything better yet
class opengl_renderer
{
@@ -291,6 +328,7 @@ class opengl_renderer
void Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last);
bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false);
void Render(TMemCell *Memcell);
void Render_particles();
void Render_precipitation();
void Render_Alpha(scene::basic_region *Region);
void Render_Alpha(cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last);
@@ -326,6 +364,7 @@ class opengl_renderer
texture_handle m_glaretexture{-1};
texture_handle m_suntexture{-1};
texture_handle m_moontexture{-1};
texture_handle m_smoketexture{-1};
// main shadowmap resources
int m_shadowbuffersize{2048};
@@ -335,6 +374,7 @@ class opengl_renderer
int m_environmentcubetextureface{0}; // helper, currently processed cube map face
int m_environmentupdatetime{0}; // time of the most recent environment map update
glm::dvec3 m_environmentupdatelocation; // coordinates of most recent environment map update
opengl_particles m_particlerenderer; // particle visualization subsystem
unsigned int m_framestamp; // id of currently rendered gfx frame
float m_framerate;

View File

@@ -1025,6 +1025,9 @@ basic_region::on_click( TAnimModel const *Instance ) {
// legacy method, polls event launchers around camera
void
basic_region::update_events() {
if( false == simulation::is_ready ) { return; }
// render events and sounds from sectors near enough to the viewer
auto const range = EU07_SECTIONSIZE; // arbitrary range
auto const &sectionlist = sections( Global.pCamera.Pos, range );

View File

@@ -1,4 +1,3 @@
in vec3 f_normal;
in vec2 f_coord;
#texture (tex1, 0, sRGB_A)

34
shaders/smoke.frag Normal file
View File

@@ -0,0 +1,34 @@
in vec4 f_color;
in vec2 f_coord;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
#texture (tex1, 0, sRGB_A)
uniform sampler2D tex1;
#include <common>
#include <tonemapping.glsl>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 tex_color = texture(tex1, f_coord);
#if POSTFX_ENABLED
out_color = tex_color * f_color;
#else
out_color = tonemap(tex_color * f_color);
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
#endif
}

21
shaders/smoke.vert Normal file
View File

@@ -0,0 +1,21 @@
layout(location = 0) in vec3 v_vert;
layout(location = 1) in vec4 v_color;
layout(location = 2) in vec2 v_coord;
out vec4 f_color;
out vec2 f_coord;
out vec4 f_clip_pos;
out vec4 f_clip_future_pos;
#include <common>
void main()
{
f_clip_pos = (projection * modelview) * vec4(v_vert, 1.0f);
f_clip_future_pos = (projection * future * modelview) * vec4(v_vert, 1.0f);
gl_Position = f_clip_pos;
f_coord = v_coord;
f_color = v_color;
}

View File

@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
#include "AnimModel.h"
#include "DynObj.h"
#include "lightarray.h"
#include "particles.h"
#include "scene.h"
#include "Train.h"
#include "application.h"
@@ -40,6 +41,7 @@ train_table Trains;
light_array Lights;
sound_table Sounds;
lua Lua;
particle_manager Particles;
scene::basic_region *Region { nullptr };
TTrain *Train { nullptr };

View File

@@ -71,6 +71,7 @@ extern train_table Trains;
extern light_array Lights;
extern sound_table Sounds;
extern lua Lua;
extern particle_manager Particles;
extern scene::basic_region *Region;
extern TTrain *Train;

View File

@@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/.
#include "simulationenvironment.h"
#include "Globals.h"
#include "Timer.h"
namespace simulation {
@@ -79,8 +80,14 @@ world_environment::init() {
m_stars.init();
m_clouds.Init();
m_precipitation.init();
m_precipitationsound.deserialize( "rain-sound-loop", sound_type::single );
m_wind = basic_wind{
static_cast<float>( Random( 0, 360 ) ),
static_cast<float>( Random( -5, 5 ) ),
static_cast<float>( Random( -2, 4 ) ),
static_cast<float>( Random( -1, 1 ) ),
static_cast<float>( Random( 5, 20 ) ),
{} };
}
void
@@ -176,6 +183,8 @@ world_environment::update() {
else {
m_precipitationsound.stop();
}
update_wind();
}
void
@@ -184,6 +193,37 @@ world_environment::update_precipitation() {
m_precipitation.update();
}
void
world_environment::update_wind() {
auto const timedelta{ static_cast<float>( Timer::GetDeltaTime() ) };
m_wind.change_time -= timedelta;
if( m_wind.change_time < 0 ) {
m_wind.change_time = Random( 5, 30 );
m_wind.velocity_change = Random( -1, 1 );
if( Random() < 0.2 ) {
// changes in wind direction should be less frequent than changes in wind speed
// TBD, TODO: configuration-driven direction change frequency
m_wind.azimuth_change = Random( -5, 5 );
}
}
// TBD, TODO: wind configuration
m_wind.azimuth = clamp_circular( m_wind.azimuth + m_wind.azimuth_change * timedelta );
// HACK: negative part of range allows for some quiet periods, without active wind
m_wind.velocity = clamp<float>( m_wind.velocity + m_wind.velocity_change * timedelta, -2, 4 );
// convert to force vector
auto const polarangle { glm::radians( 90.f ) }; // theta
auto const azimuthalangle{ glm::radians( m_wind.azimuth ) }; // phi
// convert spherical coordinates to opengl coordinates
m_wind.vector =
std::max( 0.f, m_wind.velocity )
* glm::vec3(
std::sin( polarangle ) * std::sin( azimuthalangle ),
std::cos( polarangle ),
std::sin( polarangle ) * std::cos( azimuthalangle ) * -1 );
}
void
world_environment::time( int const Hour, int const Minute, int const Second ) {

View File

@@ -36,8 +36,25 @@ public:
void compute_season( int const Yearday );
// calculates current weather
void compute_weather();
// data access
glm::vec3 const &
wind() const {
return m_wind.vector; }
private:
// types
struct basic_wind {
// internal state data
float azimuth;
float azimuth_change;
float velocity;
float velocity_change;
float change_time;
// output
glm::vec3 vector;
};
// methods
void update_wind();
// members
CSkyDome m_skydome;
cStars m_stars;
@@ -45,8 +62,8 @@ private:
cMoon m_moon;
TSky m_clouds;
basic_precipitation m_precipitation;
sound_source m_precipitationsound { sound_placement::external, -1 };
basic_wind m_wind;
};
namespace simulation {

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include "simulation.h"
#include "simulationtime.h"
#include "scenenodegroups.h"
#include "particles.h"
#include "Event.h"
#include "Driver.h"
#include "DynObj.h"
@@ -369,6 +370,16 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
// vehicle import can potentially fail
if( vehicle == nullptr ) { return; }
//
if( vehicle->mdModel != nullptr ) {
for( auto const &smokesource : vehicle->mdModel->smoke_sources() ) {
Particles.insert(
smokesource.first,
vehicle,
smokesource.second );
}
}
if( false == simulation::Vehicles.insert( vehicle ) ) {
ErrorLog( "Bad scenario: duplicate vehicle name \"" + vehicle->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
@@ -461,6 +472,15 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
// model import can potentially fail
if( instance == nullptr ) { return; }
if( instance->Model() != nullptr ) {
for( auto const &smokesource : instance->Model()->smoke_sources() ) {
Particles.insert(
smokesource.first,
instance,
smokesource.second );
}
}
if( false == simulation::Instances.insert( instance ) ) {
ErrorLog( "Bad scenario: duplicate 3d model instance name \"" + instance->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
}

View File

@@ -1 +1 @@
#define VERSION_INFO "M7 (sim) 13.07.2019"
#define VERSION_INFO "M7 (sim) 8.08.2019"