mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-21 22:59:19 +02:00
basic particle system implementation
This commit is contained in:
@@ -422,7 +422,19 @@ bool TAnimModel::Init(std::string const &asName, std::string const &asReplacable
|
|||||||
asText = asReplacableTexture.substr( 1, asReplacableTexture.length() - 1 ); // zapamiętanie tekstu
|
asText = asReplacableTexture.substr( 1, asReplacableTexture.length() - 1 ); // zapamiętanie tekstu
|
||||||
}
|
}
|
||||||
else if( asReplacableTexture != "none" ) {
|
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 );
|
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 )
|
if( ( m_materialdata.replacable_skins[ 1 ] != null_handle )
|
||||||
&& ( GfxRenderer.Material( m_materialdata.replacable_skins[ 1 ] ).has_alpha ) ) {
|
&& ( GfxRenderer.Material( m_materialdata.replacable_skins[ 1 ] ).has_alpha ) ) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class powergridsource_table;
|
|||||||
class instance_table;
|
class instance_table;
|
||||||
class vehicle_table;
|
class vehicle_table;
|
||||||
struct light_array;
|
struct light_array;
|
||||||
|
class particle_manager;
|
||||||
struct dictionary_source;
|
struct dictionary_source;
|
||||||
|
|
||||||
namespace scene {
|
namespace scene {
|
||||||
|
|||||||
@@ -4553,9 +4553,12 @@ double TMoverParameters::TractionForce( double dt ) {
|
|||||||
EngineHeatingRPM )
|
EngineHeatingRPM )
|
||||||
/ 60.0 );
|
/ 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 {
|
else {
|
||||||
tmp = 0.0;
|
tmp = 0.0;
|
||||||
|
dizel_fill = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( enrot != tmp ) {
|
if( enrot != tmp ) {
|
||||||
|
|||||||
53
Model3d.cpp
53
Model3d.cpp
@@ -703,7 +703,8 @@ void TSubModel::InitialRotate(bool doit)
|
|||||||
}
|
}
|
||||||
else if (Global.iConvertModels & 2) {
|
else if (Global.iConvertModels & 2) {
|
||||||
// optymalizacja jest opcjonalna
|
// 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
|
{ // jak nie ma potomnych, można wymnożyć przez transform i wyjedynkować go
|
||||||
float4x4 *mat = GetMatrix(); // transform submodelu
|
float4x4 *mat = GetMatrix(); // transform submodelu
|
||||||
if( false == Vertices.empty() ) {
|
if( false == Vertices.empty() ) {
|
||||||
@@ -810,6 +811,26 @@ TSubModel::find_replacable4() {
|
|||||||
return std::make_tuple( nullptr, false );
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int TSubModel::FlagsCheck()
|
int TSubModel::FlagsCheck()
|
||||||
{ // analiza koniecznych zmian pomiędzy submodelami
|
{ // analiza koniecznych zmian pomiędzy submodelami
|
||||||
// samo pomijanie glBindTexture() nie poprawi wydajności
|
// samo pomijanie glBindTexture() nie poprawi wydajności
|
||||||
@@ -1047,7 +1068,7 @@ void TSubModel::RaAnimation(TAnimType a)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
void TSubModel::serialize_geometry( std::ostream &Output ) const {
|
void TSubModel::serialize_geometry( std::ostream &Output ) const {
|
||||||
|
|
||||||
@@ -1132,6 +1153,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
|
// pobranie transformacji względem wstawienia modelu
|
||||||
void TSubModel::ParentMatrix( float4x4 *m ) const {
|
void TSubModel::ParentMatrix( float4x4 *m ) const {
|
||||||
|
|
||||||
@@ -1230,8 +1259,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;
|
TSubModel *sm = Name ? GetFromName(Name) : nullptr;
|
||||||
if( ( sm == nullptr )
|
if( ( sm == nullptr )
|
||||||
&& ( Name != nullptr ) && ( std::strcmp( Name, "none" ) != 0 ) ) {
|
&& ( Name != nullptr ) && ( std::strcmp( Name, "none" ) != 0 ) ) {
|
||||||
@@ -1243,6 +1273,7 @@ TSubModel *TModel3d::AddToNamed(const char *Name, TSubModel *SubModel)
|
|||||||
|
|
||||||
// jedyny poprawny sposób dodawania submodeli, inaczej mogą zginąć przy zapisie E3D
|
// jedyny poprawny sposób dodawania submodeli, inaczej mogą zginąć przy zapisie E3D
|
||||||
void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel) {
|
void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel) {
|
||||||
|
|
||||||
if (tmp) {
|
if (tmp) {
|
||||||
// jeśli znaleziony, podłączamy mu jako potomny
|
// jeśli znaleziony, podłączamy mu jako potomny
|
||||||
tmp->ChildAdd(SubModel);
|
tmp->ChildAdd(SubModel);
|
||||||
@@ -1269,6 +1300,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
|
// returns offset vector from root
|
||||||
glm::vec3
|
glm::vec3
|
||||||
TSubModel::offset( float const Geometrytestoffsetthreshold ) const {
|
TSubModel::offset( float const Geometrytestoffsetthreshold ) const {
|
||||||
@@ -1888,6 +1931,8 @@ void TModel3d::Init()
|
|||||||
asBinary = ""; // zablokowanie powtórnego zapisu
|
asBinary = ""; // zablokowanie powtórnego zapisu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check if the model contains particle emitters
|
||||||
|
find_smoke_sources();
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|||||||
10
Model3d.h
10
Model3d.h
@@ -51,6 +51,8 @@ namespace scene {
|
|||||||
class shape_node;
|
class shape_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using nameoffset_sequence = std::vector<std::pair<std::string, glm::vec3>>;
|
||||||
|
|
||||||
class TSubModel
|
class TSubModel
|
||||||
{ // klasa submodelu - pojedyncza siatka, punkt świetlny albo grupa punktów
|
{ // klasa submodelu - pojedyncza siatka, punkt świetlny albo grupa punktów
|
||||||
//m7todo: zrobić normalną serializację
|
//m7todo: zrobić normalną serializację
|
||||||
@@ -149,6 +151,8 @@ public: // chwilowo
|
|||||||
private:
|
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 );
|
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(TAnimType a);
|
||||||
|
// returns true if the submodel is a smoke emitter attachment point, false otherwise
|
||||||
|
bool is_emitter() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static size_t iInstance; // identyfikator egzemplarza, który aktualnie renderuje model
|
static size_t iInstance; // identyfikator egzemplarza, który aktualnie renderuje model
|
||||||
@@ -168,6 +172,8 @@ public:
|
|||||||
int count_children();
|
int count_children();
|
||||||
// locates submodel mapped with replacable -4
|
// locates submodel mapped with replacable -4
|
||||||
std::tuple<TSubModel *, bool> find_replacable4();
|
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);
|
int TriangleAdd(TModel3d *m, material_handle tex, int tri);
|
||||||
void SetRotate(float3 vNewRotateAxis, float fNewAngle);
|
void SetRotate(float3 vNewRotateAxis, float fNewAngle);
|
||||||
void SetRotateXYZ( Math3D::vector3 vNewAngles);
|
void SetRotateXYZ( Math3D::vector3 vNewAngles);
|
||||||
@@ -240,6 +246,7 @@ private:
|
|||||||
int iSubModelsCount; // Ra: używane do tworzenia binarnych
|
int iSubModelsCount; // Ra: używane do tworzenia binarnych
|
||||||
std::string asBinary; // nazwa pod którą zapisać model binarny
|
std::string asBinary; // nazwa pod którą zapisać model binarny
|
||||||
std::string m_filename;
|
std::string m_filename;
|
||||||
|
nameoffset_sequence m_smokesources; // list of particle sources defined in the model
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TModel3d();
|
TModel3d();
|
||||||
@@ -252,6 +259,7 @@ public:
|
|||||||
inline TSubModel * GetSMRoot() { return (Root); };
|
inline TSubModel * GetSMRoot() { return (Root); };
|
||||||
TSubModel * GetFromName(std::string const &Name) const;
|
TSubModel * GetFromName(std::string const &Name) const;
|
||||||
TSubModel * AddToNamed(const char *Name, TSubModel *SubModel);
|
TSubModel * AddToNamed(const char *Name, TSubModel *SubModel);
|
||||||
|
nameoffset_sequence const & find_smoke_sources();
|
||||||
void AddTo(TSubModel *tmp, TSubModel *SubModel);
|
void AddTo(TSubModel *tmp, TSubModel *SubModel);
|
||||||
void LoadFromTextFile(std::string const &FileName, bool dynamic);
|
void LoadFromTextFile(std::string const &FileName, bool dynamic);
|
||||||
void LoadFromBinFile(std::string const &FileName, bool dynamic);
|
void LoadFromBinFile(std::string const &FileName, bool dynamic);
|
||||||
@@ -260,6 +268,8 @@ public:
|
|||||||
int Flags() const { return iFlags; };
|
int Flags() const { return iFlags; };
|
||||||
void Init();
|
void Init();
|
||||||
std::string NameGet() const { return m_filename; };
|
std::string NameGet() const { return m_filename; };
|
||||||
|
nameoffset_sequence const & smoke_sources() const {
|
||||||
|
return m_smokesources; }
|
||||||
int TerrainCount() const;
|
int TerrainCount() const;
|
||||||
TSubModel * TerrainSquare(int n);
|
TSubModel * TerrainSquare(int n);
|
||||||
void deserialize(std::istream &s, size_t size, bool dynamic);
|
void deserialize(std::istream &s, size_t size, bool dynamic);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "simulationtime.h"
|
#include "simulationtime.h"
|
||||||
#include "simulationenvironment.h"
|
#include "simulationenvironment.h"
|
||||||
#include "lightarray.h"
|
#include "lightarray.h"
|
||||||
|
#include "particles.h"
|
||||||
#include "Train.h"
|
#include "Train.h"
|
||||||
#include "Driver.h"
|
#include "Driver.h"
|
||||||
#include "DynObj.h"
|
#include "DynObj.h"
|
||||||
@@ -235,6 +236,8 @@ driver_mode::update() {
|
|||||||
simulation::Region->update_sounds();
|
simulation::Region->update_sounds();
|
||||||
audio::renderer.update( deltarealtime );
|
audio::renderer.update( 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 );
|
GfxRenderer.Update( deltarealtime );
|
||||||
|
|
||||||
simulation::is_ready = true;
|
simulation::is_ready = true;
|
||||||
|
|||||||
13
parser.cpp
13
parser.cpp
@@ -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<>
|
template<>
|
||||||
cParser&
|
cParser&
|
||||||
cParser::operator>>( std::string &Right ) {
|
cParser::operator>>( std::string &Right ) {
|
||||||
|
|||||||
8
parser.h
8
parser.h
@@ -37,7 +37,7 @@ class cParser //: public std::stringstream
|
|||||||
operator>>( Type_ &Right );
|
operator>>( Type_ &Right );
|
||||||
template <typename Output_>
|
template <typename Output_>
|
||||||
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 );
|
getTokens( 1, ToLower, Break );
|
||||||
Output_ output;
|
Output_ output;
|
||||||
*this >> output;
|
*this >> output;
|
||||||
@@ -108,6 +108,12 @@ class cParser //: public std::stringstream
|
|||||||
std::deque<std::string> tokens;
|
std::deque<std::string> tokens;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <>
|
||||||
|
glm::vec3
|
||||||
|
cParser::getToken( bool const ToLower, const char *Break );
|
||||||
|
|
||||||
|
|
||||||
template<typename Type_>
|
template<typename Type_>
|
||||||
cParser&
|
cParser&
|
||||||
cParser::operator>>( Type_ &Right ) {
|
cParser::operator>>( Type_ &Right ) {
|
||||||
|
|||||||
432
particles.cpp
Normal file
432
particles.cpp
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
/*
|
||||||
|
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(
|
||||||
|
500,
|
||||||
|
// 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() ) ) ) );
|
||||||
|
/*
|
||||||
|
m_particlehead =
|
||||||
|
m_particletail =
|
||||||
|
std::begin( m_particles );
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
smoke_source::bind( TDynamicObject const *Vehicle ) {
|
||||||
|
|
||||||
|
m_owner.vehicle = Vehicle;
|
||||||
|
m_ownertype = (
|
||||||
|
m_owner.vehicle != nullptr ?
|
||||||
|
owner_type::vehicle :
|
||||||
|
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
|
||||||
|
/*
|
||||||
|
while( m_particlehead != m_particletail ) {
|
||||||
|
|
||||||
|
auto &particle { *m_particlehead };
|
||||||
|
bool particleisalive;
|
||||||
|
|
||||||
|
while( ( false == ( particleisalive = update( particle, 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 {
|
||||||
|
--m_particletail;
|
||||||
|
if( m_particlehead == m_particletail ) { break; }
|
||||||
|
particle = *m_particletail;
|
||||||
|
} while( false == ( particleisalive = update( particle, Timedelta ) ) );
|
||||||
|
}
|
||||||
|
if( true == particleisalive ) {
|
||||||
|
// ensure at the end of the pass the head iterator is placed after the last alive particle
|
||||||
|
++m_particlehead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
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_particlehead != std::end( m_particles ) ) ) {
|
||||||
|
|
||||||
|
m_spawncount -= 1.f;
|
||||||
|
auto &particle { *m_particlehead };
|
||||||
|
initialize( particle );
|
||||||
|
if( true == update( particle, Timedelta ) ) {
|
||||||
|
++m_particlehead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
// after the pass the head iterator is left last alive particle
|
||||||
|
m_particletail = m_particlehead;
|
||||||
|
m_particlehead = std::begin( m_particles );
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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
|
||||||
|
location = 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 = std::max<float>( 0.25 * ( 1.f - Global.Overcast ), Particle.velocity.y - 0.25 * Global.Overcast * Timedelta );
|
||||||
|
|
||||||
|
Particle.position += Particle.velocity * static_cast<float>( Timedelta );
|
||||||
|
Particle.position += 0.35f * 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
if( source.deserialize( cParser( templatepath + templatename + ".txt", cParser::buffer_FILE ) ) ) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
229
particles.h
Normal file
229
particles.h
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
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 );
|
||||||
|
// 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 );
|
||||||
|
// 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 ,;[]" );
|
||||||
|
}
|
||||||
|
}
|
||||||
169
renderer.cpp
169
renderer.cpp
@@ -106,6 +106,134 @@ opengl_camera::draw( glm::vec3 const &Offset ) const {
|
|||||||
::glEnd();
|
::glEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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, 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[ 0 ] =
|
||||||
|
vertex.color[ 1 ] =
|
||||||
|
vertex.color[ 2 ] = static_cast<std::uint8_t>( Global.fLuminance * 32 );
|
||||||
|
vertex.color[ 3 ] = clamp<std::uint8_t>( particle.opacity * 255, 0, 255 );
|
||||||
|
|
||||||
|
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:
|
||||||
|
// setup...
|
||||||
|
::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT );
|
||||||
|
// ...make sure we have enough room...
|
||||||
|
if( m_buffercapacity < m_particlevertices.size() ) {
|
||||||
|
// allocate gpu side buffer big enough to hold the data
|
||||||
|
m_buffercapacity = 0;
|
||||||
|
if( m_buffer != -1 ) {
|
||||||
|
// get rid of the old buffer
|
||||||
|
::glDeleteBuffers( 1, &m_buffer );
|
||||||
|
}
|
||||||
|
::glGenBuffers( 1, &m_buffer );
|
||||||
|
::glBindBuffer( GL_ARRAY_BUFFER, m_buffer );
|
||||||
|
if( m_buffer > 0 ) {
|
||||||
|
// if we didn't get a buffer we'll try again during the next draw call
|
||||||
|
// NOTE: we match capacity instead of current size to reduce number of re-allocations
|
||||||
|
auto const particlecount { m_particlevertices.capacity() };
|
||||||
|
::glBufferData(
|
||||||
|
GL_ARRAY_BUFFER,
|
||||||
|
particlecount * sizeof( particle_vertex ),
|
||||||
|
nullptr,
|
||||||
|
GL_DYNAMIC_DRAW );
|
||||||
|
if( ::glGetError() == GL_OUT_OF_MEMORY ) {
|
||||||
|
// TBD: throw a bad_alloc?
|
||||||
|
ErrorLog( "openGL error: out of memory; failed to create a geometry buffer" );
|
||||||
|
::glDeleteBuffers( 1, &m_buffer );
|
||||||
|
m_buffer = -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_buffercapacity = particlecount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ...send the data...
|
||||||
|
if( m_buffer > 0 ) {
|
||||||
|
// if the buffer exists at this point it's guaranteed to be big enough to hold our data
|
||||||
|
::glBindBuffer( GL_ARRAY_BUFFER, m_buffer );
|
||||||
|
::glBufferSubData(
|
||||||
|
GL_ARRAY_BUFFER,
|
||||||
|
0,
|
||||||
|
m_particlevertices.size() * sizeof( particle_vertex ),
|
||||||
|
m_particlevertices.data() );
|
||||||
|
}
|
||||||
|
// ...and cleanup
|
||||||
|
::glPopClientAttrib();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
opengl_particles::render( int const Textureunit ) {
|
||||||
|
|
||||||
|
if( m_buffercapacity == 0 ) { return; }
|
||||||
|
if( m_particlevertices.empty() ) { return; }
|
||||||
|
|
||||||
|
// setup...
|
||||||
|
::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT );
|
||||||
|
::glBindBuffer( GL_ARRAY_BUFFER, m_buffer );
|
||||||
|
::glVertexPointer( 3, GL_FLOAT, sizeof( particle_vertex ), static_cast<char *>( nullptr ) );
|
||||||
|
::glEnableClientState( GL_VERTEX_ARRAY );
|
||||||
|
::glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( particle_vertex ), static_cast<char *>( nullptr ) + sizeof( float ) * 3 );
|
||||||
|
::glEnableClientState( GL_COLOR_ARRAY );
|
||||||
|
::glClientActiveTexture( Textureunit );
|
||||||
|
::glTexCoordPointer( 2, GL_FLOAT, sizeof( particle_vertex ), static_cast<char *>( nullptr ) + sizeof( float ) * 3 + sizeof( std::uint8_t ) * 4 );
|
||||||
|
::glEnableClientState( GL_TEXTURE_COORD_ARRAY );
|
||||||
|
// ...draw...
|
||||||
|
::glDrawArrays( GL_QUADS, 0, m_particlevertices.size() );
|
||||||
|
// ...and cleanup
|
||||||
|
::glPopClientAttrib();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
opengl_renderer::Init( GLFWwindow *Window ) {
|
opengl_renderer::Init( GLFWwindow *Window ) {
|
||||||
|
|
||||||
@@ -194,6 +322,7 @@ opengl_renderer::Init( GLFWwindow *Window ) {
|
|||||||
if( m_helpertextureunit >= 0 ) {
|
if( m_helpertextureunit >= 0 ) {
|
||||||
m_reflectiontexture = Fetch_Texture( "fx/reflections" );
|
m_reflectiontexture = Fetch_Texture( "fx/reflections" );
|
||||||
}
|
}
|
||||||
|
m_smoketexture = Fetch_Texture( "fx/smoke" );
|
||||||
WriteLog( "...gfx data pre-loading done" );
|
WriteLog( "...gfx data pre-loading done" );
|
||||||
|
|
||||||
#ifdef EU07_USE_PICKING_FRAMEBUFFER
|
#ifdef EU07_USE_PICKING_FRAMEBUFFER
|
||||||
@@ -576,6 +705,8 @@ opengl_renderer::Render_pass( rendermode const Mode ) {
|
|||||||
// ...translucent parts
|
// ...translucent parts
|
||||||
setup_drawing( true );
|
setup_drawing( true );
|
||||||
Render_Alpha( simulation::Region );
|
Render_Alpha( simulation::Region );
|
||||||
|
// particles
|
||||||
|
Render_particles();
|
||||||
// precipitation; done at the end, only before cab render
|
// precipitation; done at the end, only before cab render
|
||||||
Render_precipitation();
|
Render_precipitation();
|
||||||
// cab render
|
// cab render
|
||||||
@@ -1489,7 +1620,7 @@ opengl_renderer::Render( world_environment *Environment ) {
|
|||||||
|
|
||||||
auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW );
|
auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW );
|
||||||
|
|
||||||
auto const fogfactor { clamp<float>( Global.fFogEnd / 2000.f, 0.f, 1.f ) }; // stronger fog reduces opacity of the celestial bodies
|
auto const fogfactor { clamp<float>( Global.fFogEnd / 2000.f, 0.f, 1.f ) }; // closer/denser fog reduces opacity of the celestial bodies
|
||||||
float const duskfactor = 1.0f - clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f;
|
float const duskfactor = 1.0f - clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f;
|
||||||
glm::vec3 suncolor = interpolate(
|
glm::vec3 suncolor = interpolate(
|
||||||
glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ),
|
glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ),
|
||||||
@@ -3008,6 +3139,25 @@ opengl_renderer::Render( TMemCell *Memcell ) {
|
|||||||
::glPopMatrix();
|
::glPopMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
opengl_renderer::Render_particles() {
|
||||||
|
|
||||||
|
switch_units( true, false, false );
|
||||||
|
|
||||||
|
Bind_Material( null_handle ); // TODO: bind smoke texture
|
||||||
|
|
||||||
|
// 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 );
|
||||||
|
|
||||||
|
Bind_Texture( m_smoketexture );
|
||||||
|
m_particlerenderer.render( m_diffusetextureunit );
|
||||||
|
|
||||||
|
::glDepthMask( GL_TRUE );
|
||||||
|
::glEnable( GL_LIGHTING );
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
opengl_renderer::Render_precipitation() {
|
opengl_renderer::Render_precipitation() {
|
||||||
|
|
||||||
@@ -3023,8 +3173,11 @@ opengl_renderer::Render_precipitation() {
|
|||||||
colors::white,
|
colors::white,
|
||||||
0.5f * clamp<float>( Global.fLuminance, 0.f, 1.f ) ) ) );
|
0.5f * clamp<float>( Global.fLuminance, 0.f, 1.f ) ) ) );
|
||||||
::glPushMatrix();
|
::glPushMatrix();
|
||||||
// tilt the precipitation cone against the velocity vector for crude motion blur
|
// tilt the precipitation cone against the camera movement 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 ) {
|
if( glm::length2( velocity ) > 0.0 ) {
|
||||||
auto const forward{ glm::normalize( velocity ) };
|
auto const forward{ glm::normalize( velocity ) };
|
||||||
auto left { glm::cross( forward, {0.0,1.0,0.0} ) };
|
auto left { glm::cross( forward, {0.0,1.0,0.0} ) };
|
||||||
@@ -3801,6 +3954,16 @@ opengl_renderer::Update_Mouse_Position() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
opengl_renderer::Update( double const Deltatime ) {
|
opengl_renderer::Update( double const Deltatime ) {
|
||||||
|
|
||||||
|
// per frame updates
|
||||||
|
if( simulation::is_ready ) {
|
||||||
|
// update particle subsystem
|
||||||
|
renderpass_config renderpass;
|
||||||
|
setup_pass( renderpass, rendermode::color );
|
||||||
|
m_particlerenderer.update( renderpass.camera );
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixed step updates
|
||||||
/*
|
/*
|
||||||
m_pickupdateaccumulator += Deltatime;
|
m_pickupdateaccumulator += Deltatime;
|
||||||
|
|
||||||
|
|||||||
50
renderer.h
50
renderer.h
@@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "frustum.h"
|
#include "frustum.h"
|
||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "simulationenvironment.h"
|
#include "simulationenvironment.h"
|
||||||
|
#include "particles.h"
|
||||||
#include "MemCell.h"
|
#include "MemCell.h"
|
||||||
|
|
||||||
#define EU07_USE_PICKING_FRAMEBUFFER
|
#define EU07_USE_PICKING_FRAMEBUFFER
|
||||||
@@ -43,6 +44,8 @@ struct opengl_light : public basic_light {
|
|||||||
return *this; }
|
return *this; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// encapsulates basic rendering setup.
|
// encapsulates basic rendering setup.
|
||||||
// for modern opengl this translates to a specific collection of glsl shaders,
|
// for modern opengl this translates to a specific collection of glsl shaders,
|
||||||
// for legacy opengl this is combination of blending modes, active texture units etc
|
// for legacy opengl this is combination of blending modes, active texture units etc
|
||||||
@@ -50,6 +53,8 @@ struct opengl_technique {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// simple camera object. paired with 'virtual camera' in the scene
|
// simple camera object. paired with 'virtual camera' in the scene
|
||||||
class opengl_camera {
|
class opengl_camera {
|
||||||
|
|
||||||
@@ -112,6 +117,46 @@ private:
|
|||||||
glm::mat4 m_inversetransformation; // cached transformation to world space
|
glm::mat4 m_inversetransformation; // cached transformation to world space
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// particle data visualizer
|
||||||
|
class opengl_particles {
|
||||||
|
public:
|
||||||
|
// constructors
|
||||||
|
opengl_particles() = default;
|
||||||
|
// destructor
|
||||||
|
~opengl_particles() {
|
||||||
|
if( m_buffer != 0 ) {
|
||||||
|
::glDeleteBuffers( 1, &m_buffer ); } }
|
||||||
|
// methods
|
||||||
|
void
|
||||||
|
update( opengl_camera const &Camera );
|
||||||
|
void
|
||||||
|
render( int const Textureunit );
|
||||||
|
private:
|
||||||
|
// types
|
||||||
|
struct particle_vertex {
|
||||||
|
glm::vec3 position; // 3d space
|
||||||
|
std::uint8_t color[ 4 ]; // rgba, unsigned byte format
|
||||||
|
glm::vec2 texture; // uv space
|
||||||
|
float padding[ 2 ]; // experimental, some gfx hardware allegedly works better with 32-bit aligned data blocks
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
GLuint m_buffer{ (GLuint)-1 }; // id of the buffer holding geometry data on the opengl end
|
||||||
|
std::size_t m_buffercapacity{ 0 }; // total capacity of the last established buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// bare-bones render controller, in lack of anything better yet
|
// bare-bones render controller, in lack of anything better yet
|
||||||
class opengl_renderer {
|
class opengl_renderer {
|
||||||
|
|
||||||
@@ -296,6 +341,8 @@ private:
|
|||||||
Render_cab( TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false );
|
Render_cab( TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false );
|
||||||
void
|
void
|
||||||
Render( TMemCell *Memcell );
|
Render( TMemCell *Memcell );
|
||||||
|
void
|
||||||
|
Render_particles();
|
||||||
void
|
void
|
||||||
Render_precipitation();
|
Render_precipitation();
|
||||||
void
|
void
|
||||||
@@ -338,6 +385,7 @@ private:
|
|||||||
texture_handle m_suntexture { -1 };
|
texture_handle m_suntexture { -1 };
|
||||||
texture_handle m_moontexture { -1 };
|
texture_handle m_moontexture { -1 };
|
||||||
texture_handle m_reflectiontexture { -1 };
|
texture_handle m_reflectiontexture { -1 };
|
||||||
|
texture_handle m_smoketexture { -1 };
|
||||||
GLUquadricObj *m_quadric { nullptr }; // helper object for drawing debug mode scene elements
|
GLUquadricObj *m_quadric { nullptr }; // helper object for drawing debug mode scene elements
|
||||||
// TODO: refactor framebuffer stuff into an object
|
// TODO: refactor framebuffer stuff into an object
|
||||||
bool m_framebuffersupport { false };
|
bool m_framebuffersupport { false };
|
||||||
@@ -369,6 +417,8 @@ private:
|
|||||||
int m_environmentcubetextureface { 0 }; // helper, currently processed cube map face
|
int m_environmentcubetextureface { 0 }; // helper, currently processed cube map face
|
||||||
int m_environmentupdatetime { 0 }; // time of the most recent environment map update
|
int m_environmentupdatetime { 0 }; // time of the most recent environment map update
|
||||||
glm::dvec3 m_environmentupdatelocation; // coordinates of most recent environment map update
|
glm::dvec3 m_environmentupdatelocation; // coordinates of most recent environment map update
|
||||||
|
// particle visualization subsystem
|
||||||
|
opengl_particles m_particlerenderer;
|
||||||
|
|
||||||
int m_helpertextureunit { GL_TEXTURE0 };
|
int m_helpertextureunit { GL_TEXTURE0 };
|
||||||
int m_shadowtextureunit { GL_TEXTURE1 };
|
int m_shadowtextureunit { GL_TEXTURE1 };
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "AnimModel.h"
|
#include "AnimModel.h"
|
||||||
#include "DynObj.h"
|
#include "DynObj.h"
|
||||||
#include "lightarray.h"
|
#include "lightarray.h"
|
||||||
|
#include "particles.h"
|
||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "Train.h"
|
#include "Train.h"
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ sound_table Sounds;
|
|||||||
instance_table Instances;
|
instance_table Instances;
|
||||||
vehicle_table Vehicles;
|
vehicle_table Vehicles;
|
||||||
light_array Lights;
|
light_array Lights;
|
||||||
|
particle_manager Particles;
|
||||||
|
|
||||||
scene::basic_region *Region { nullptr };
|
scene::basic_region *Region { nullptr };
|
||||||
TTrain *Train { nullptr };
|
TTrain *Train { nullptr };
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ extern sound_table Sounds;
|
|||||||
extern instance_table Instances;
|
extern instance_table Instances;
|
||||||
extern vehicle_table Vehicles;
|
extern vehicle_table Vehicles;
|
||||||
extern light_array Lights;
|
extern light_array Lights;
|
||||||
|
extern particle_manager Particles;
|
||||||
|
|
||||||
extern scene::basic_region *Region;
|
extern scene::basic_region *Region;
|
||||||
extern TTrain *Train;
|
extern TTrain *Train;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "simulationenvironment.h"
|
#include "simulationenvironment.h"
|
||||||
|
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
|
#include "Timer.h"
|
||||||
|
|
||||||
namespace simulation {
|
namespace simulation {
|
||||||
|
|
||||||
@@ -77,8 +78,14 @@ world_environment::init() {
|
|||||||
m_stars.init();
|
m_stars.init();
|
||||||
m_clouds.Init();
|
m_clouds.Init();
|
||||||
m_precipitation.init();
|
m_precipitation.init();
|
||||||
|
|
||||||
m_precipitationsound.deserialize( "rain-sound-loop", sound_type::single );
|
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
|
void
|
||||||
@@ -170,6 +177,8 @@ world_environment::update() {
|
|||||||
// reduce friction due to snow
|
// reduce friction due to snow
|
||||||
Global.FrictionWeatherFactor = 0.75f;
|
Global.FrictionWeatherFactor = 0.75f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_wind();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -178,6 +187,33 @@ world_environment::update_precipitation() {
|
|||||||
m_precipitation.update();
|
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.azimuth_change = Random( -5, 5 );
|
||||||
|
m_wind.velocity_change = Random( -1, 1 );
|
||||||
|
}
|
||||||
|
// 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
|
void
|
||||||
world_environment::time( int const Hour, int const Minute, int const Second ) {
|
world_environment::time( int const Hour, int const Minute, int const Second ) {
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,25 @@ public:
|
|||||||
void compute_season( int const Yearday ) const;
|
void compute_season( int const Yearday ) const;
|
||||||
// calculates current weather
|
// calculates current weather
|
||||||
void compute_weather() const;
|
void compute_weather() const;
|
||||||
|
// data access
|
||||||
|
glm::vec3 const &
|
||||||
|
wind() const {
|
||||||
|
return m_wind.vector; }
|
||||||
|
|
||||||
private:
|
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
|
// members
|
||||||
CSkyDome m_skydome;
|
CSkyDome m_skydome;
|
||||||
cStars m_stars;
|
cStars m_stars;
|
||||||
@@ -45,8 +62,8 @@ private:
|
|||||||
cMoon m_moon;
|
cMoon m_moon;
|
||||||
TSky m_clouds;
|
TSky m_clouds;
|
||||||
basic_precipitation m_precipitation;
|
basic_precipitation m_precipitation;
|
||||||
|
|
||||||
sound_source m_precipitationsound { sound_placement::external, -1 };
|
sound_source m_precipitationsound { sound_placement::external, -1 };
|
||||||
|
basic_wind m_wind;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace simulation {
|
namespace simulation {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "simulation.h"
|
#include "simulation.h"
|
||||||
#include "simulationtime.h"
|
#include "simulationtime.h"
|
||||||
#include "scenenodegroups.h"
|
#include "scenenodegroups.h"
|
||||||
|
#include "particles.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "Driver.h"
|
#include "Driver.h"
|
||||||
#include "DynObj.h"
|
#include "DynObj.h"
|
||||||
@@ -55,6 +56,7 @@ state_serializer::deserialize( std::string const &Scenariofile ) {
|
|||||||
// as long as the scenario file wasn't rainsted-created base file override
|
// as long as the scenario file wasn't rainsted-created base file override
|
||||||
Region->serialize( Scenariofile );
|
Region->serialize( Scenariofile );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,6 +356,16 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|
|||||||
// vehicle import can potentially fail
|
// vehicle import can potentially fail
|
||||||
if( vehicle == nullptr ) { return; }
|
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 ) ) {
|
if( false == simulation::Vehicles.insert( vehicle ) ) {
|
||||||
|
|
||||||
ErrorLog( "Bad scenario: duplicate vehicle name \"" + vehicle->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
|
ErrorLog( "Bad scenario: duplicate vehicle name \"" + vehicle->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
|
||||||
|
|||||||
Reference in New Issue
Block a user