mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 19:09:25 +02:00
* refactor(sonar): use override instead of redundant virtual (cpp:S3471) * refactor(sonar): remove empty statements, one declaration per statement (cpp:S1116, cpp:S1659) * refactor(sonar): drop redundant lambda return types (cpp:S3574) * refactor(sonar): prefer using over typedef (cpp:S5416) * refactor(sonar): use =default for trivial special members (cpp:S3490) * refactor(sonar): use nullptr literal (cpp:S4962) * refactor(sonar): remove redundant access specifiers (cpp:S3539) * refactor(sonar): remove redundant casts (cpp:S1905) * refactor(sonar): use std::scoped_lock instead of std::lock_guard (cpp:S5997) * refactor(sonar): construct elements in place with emplace_back (cpp:S6003) * refactor(sonar): make narrowing conversions explicit (cpp:S5276) * refactor(sonar): mark unused parameters (cpp:S1172) Unused names are commented out; parameters used only on some platforms are marked [[maybe_unused]]. * refactor(sonar): declare non-mutating member functions const (cpp:S5817) Virtual functions are left untouched so overrides keep matching. * refactor(sonar): merge collapsible nested if statements (cpp:S1066) Comments that preceded the inner if are kept above the merged statement. * refactor(sonar): scope single-use variables in if init-statements (cpp:S6004) * fix(sonar): give polymorphic base classes virtual destructors (cpp:S1235) * refactor(sonar): use try_emplace for map insertion (cpp:S6030) * refactor(sonar): use auto where the type is spelled out on the right (cpp:S5827) * refactor(sonar): remove redundant parentheses (cpp:S1110) * refactor(sonar): document intentionally empty functions (cpp:S1186) * refactor(sonar): remove dead stores (cpp:S1854) Side effects (e.g. LISTLINE++) are kept. * refactor(sonar): remove unused variables, captures and private fields (cpp:S1481, cpp:S1068) * refactor(sonar): use structured bindings for pair access (cpp:S6005) * refactor(sonar): use range-based loops and ranges algorithms (cpp:S5566) * refactor(sonar): split chained assignments (cpp:S1121) * refactor(sonar): add default cases to switch statements (cpp:S3562) * refactor(sonar): use std::size for array lengths (cpp:S7127) * refactor(sonar): use std::ranges algorithms and std::erase(_if) (cpp:S6197) * refactor(sonar): access static members through the class (cpp:S2209) * refactor(sonar): add braces around nested if-else (cpp:S5261) * refactor(sonar): avoid side effects inside typeid operands (cpp:S5279) * refactor(sonar): drop top-level const from by-value return types (cpp:S5951) * refactor(sonar): declare variables separately from their type definitions (cpp:S3646) * refactor(sonar): declare loop counters in the for statement (cpp:S5955) * refactor(sonar): rely on class template argument deduction (cpp:S6012) * refactor(sonar): inherit base constructors instead of forwarding them (cpp:S5952) * refactor(sonar): rename locals that shadow outer declarations (cpp:S1117) Only local variables and definition parameters are renamed; shadowing struct fields and enumerators in headers are left for a separate change. * refactor(sonar): reduce enum qualification with using enum (cpp:S6177) Applied only where no unqualified name in the block could be captured by the introduced enumerators. * refactor(sonar): mark converting constructors explicit (cpp:S1709) sound_source and the gl::object conversion operators stay implicit; they are relied upon by copy-list-initialisation and GL calls. * refactor(sonar): initialise members in initialiser lists or declarations (cpp:S3230) * refactor(sonar): make read-only local references and pointers const (cpp:S5350) * refactor(sonar): take read-only pointer and reference parameters as const (cpp:S995) TTrain command handlers keep their signature, it is fixed by the handler table. * refactor(sonar): pass expensive-to-copy parameters by const reference (cpp:S1238) Signatures declared in files touched by PR 206 (Driver.h, EU07.cpp) and virtual overrides are left unchanged. * refactor(sonar): use transparent comparators for string-keyed maps (cpp:S6045) std::unordered_map instances and map types exposed in headers used by other modules are left as they are. * fix(sonar): do not use UI text as a printf format string (cpp:S5281) Text lines containing '%' would otherwise be interpreted as format specifiers by ImGui::TextColored. * refactor(sonar): use std::bind_front for callbacks (cpp:S5995) * refactor(sonar): drop const_cast on string literals passed to the Python C API (cpp:S859) * refactor(sonar): take read-only string parameters as std::string_view (cpp:S6009) The Windows messaging helpers keep std::string, they need c_str(). * refactor(sonar): make read-only global tables const (cpp:S5421) Mutable global state (flags, timers, singletons) is left as is. * refactor(sonar): replace single-case switch statements with if (cpp:S1301) * refactor(sonar): format UUIDs and log timestamps with std::format (cpp:S6494, cpp:S5945) * refactor(sonar): use std::array for local fixed-size arrays (cpp:S5945) Arrays that are class members or part of headers are left unchanged. * fix(sonar): test the bearing damage bit, not bit 0 (cpp:S872) 'DamageFlag & dtrain_bearing > 0' parsed as 'DamageFlag & (dtrain_bearing > 0)', i.e. DamageFlag & 1, so bearing friction reacted to the wrong damage flag. * refactor(sonar): assorted small fixes - empty() instead of size() comparisons (cpp:S1155) - member initialisers in declaration order (cpp:S3229) - no redundant c_str() when a std::string is accepted (cpp:S7121) - contains() on associative containers (cpp:S6171) - const reference instead of a copying range-for (cpp:S6032) - starts_with() for prefix checks (cpp:S6178) - no std::move on temporaries (cpp:S5274) - has_value() on std::optional (cpp:S7172) - bool literals for bool members (cpp:S5820) - inline only on the definition (cpp:S3548) - explicit base access specifier (cpp:S5965) - explicit element access instead of dereferencing an array (cpp:S7116) - rely on the C++20 rewritten operator!= (cpp:S6186) * refactor(sonar): more small fixes - std::format instead of iostream manipulators in to_string/to_hex_str (cpp:S6495); output verified identical, negative hex values keep the unsigned form - remove an empty compound statement (cpp:S108) - drop an always-false unsigned < 0 branch (cpp:S1768) - explicit lambda capture (cpp:S3608) - std::make_unique (cpp:S5950) * chore: strip stray carriage returns left in TTrain constructor comments * fix: keep quantize() in its own value type The S5276 change cast the result of the generic quantize<Type_>() to unsigned int, which truncated float/double callers (shadow angle slider, editor rotation snapping, AI speed control steps) and broke negative values. * fix(sonar): use integer loop counters in segment sampling (cpp:S2193) * refactor(sonar): clean up issues reported on PR 209 - remove locals left unused after the dead-store cleanup (cpp:S1481, cpp:S1854) - unnamed unused parameter (cpp:S1172) - single-use declarations moved into if init-statements (cpp:S6004) - auto for casted initialisers (cpp:S5827) - const references and pointers where nothing is modified (cpp:S5350) - rename structured bindings shadowed by inner declarations (cpp:S1117)
479 lines
19 KiB
C++
479 lines
19 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "rendering/particles.h"
|
|
|
|
#include "utilities/Timer.h"
|
|
#include "utilities/Globals.h"
|
|
#include "model/AnimModel.h"
|
|
#include "simulation/simulationenvironment.h"
|
|
#include "utilities/Logs.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 != "}" ) ) {
|
|
|
|
if( key == "color:" ) {
|
|
// special case, vec3 attribute type
|
|
// TODO: variable table, if amount of vector attributes increases
|
|
color = Input.getToken<glm::vec3>( true, "\n\r\t ,;[]" );
|
|
color =
|
|
glm::clamp(
|
|
color / 255.f,
|
|
glm::vec3{ 0.f }, glm::vec3{ 1.f } );
|
|
}
|
|
else {
|
|
// float type attributes
|
|
auto const lookup { variablemap.find( key ) };
|
|
if( lookup != variablemap.end() ) {
|
|
lookup->second = Input.getToken<float>( true, "\n\r\t ,;[]" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
smoke_source::particle_emitter::initialize( smoke_particle &Particle ) const {
|
|
|
|
auto const polarangle { glm::radians( LocalRandom( inclination[ value_limit::min ], inclination[ value_limit::max ] ) ) }; // theta
|
|
auto const azimuthalangle { glm::radians( LocalRandom( -180, 180 ) ) }; // phi
|
|
// convert spherical coordinates to opengl coordinates
|
|
auto const launchvector { glm::vec3(
|
|
std::sin( polarangle ) * std::sin( azimuthalangle ) * -1,
|
|
std::cos( polarangle ),
|
|
std::sin( polarangle ) * std::cos( azimuthalangle ) ) };
|
|
auto const launchvelocity { static_cast<float>( LocalRandom( velocity[ value_limit::min ], velocity[ value_limit::max ] ) ) };
|
|
|
|
Particle.velocity = launchvector * launchvelocity;
|
|
|
|
Particle.rotation = static_cast<float>(glm::radians( LocalRandom( 0, 360 ) ));
|
|
Particle.size = static_cast<float>(LocalRandom( size[ value_limit::min ], size[ value_limit::max ] ));
|
|
Particle.opacity = static_cast<float>(LocalRandom( opacity[ value_limit::min ], opacity[ value_limit::max ] ) / Global.SmokeFidelity);
|
|
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_max_particles =
|
|
// put a cap on number of particles in a single source. TBD, TODO: make it part of he source configuration?
|
|
std::min(
|
|
static_cast<int>( 500 * Global.SmokeFidelity ),
|
|
// 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 = (
|
|
( ( false == Global.Smoke ) || ( true == Onlydespawn ) ) ?
|
|
0.f :
|
|
std::min<float>(
|
|
static_cast<float>(m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity )),
|
|
static_cast<float>(m_max_particles) ) );
|
|
// consider special spawn rate cases
|
|
if( m_ownertype == owner_type::vehicle ) {
|
|
|
|
if (Global.AirTemperature <= 5.f || m_owner.vehicle->MoverParameters->dizel_heat.Ts < 45.f)
|
|
{
|
|
m_emitter.color = glm::vec3{128, 128, 128};
|
|
}
|
|
|
|
if (m_owner.vehicle->MoverParameters->dizel_spinup == true)
|
|
{
|
|
m_spawncount =
|
|
((false == Global.Smoke) || (true == Onlydespawn)) ?
|
|
0.f :
|
|
std::min<float>(
|
|
// m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ),
|
|
static_cast<float>(m_spawncount +(m_spawnrate * Timedelta * Global.SmokeFidelity * (((m_owner.vehicle->MoverParameters->enrot) / 4 ) * 0.01))), static_cast<float>(m_max_particles));
|
|
}
|
|
else
|
|
{
|
|
if (m_owner.vehicle->MoverParameters->DirAbsolute == 0 ||
|
|
m_owner.vehicle->MoverParameters->Im == 0)
|
|
{
|
|
m_spawncount =
|
|
((false == Global.Smoke) || (true == Onlydespawn)) ?
|
|
0.f :
|
|
std::min<float>(
|
|
// m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ),
|
|
static_cast<float>(m_spawncount + (m_spawnrate * Timedelta * Global.SmokeFidelity * ((((m_owner.vehicle->MoverParameters->DElist[m_owner.vehicle->MoverParameters->MainCtrlPosNo].RPM -m_owner.vehicle->MoverParameters->enrot) /60) *0.02) *(m_owner.vehicle->MoverParameters->EnginePower * 0.005)))), static_cast<float>(m_max_particles));
|
|
}
|
|
else
|
|
{
|
|
|
|
m_spawncount =
|
|
((false == Global.Smoke) || (true == Onlydespawn)) ?
|
|
0.f :
|
|
std::min<float>(
|
|
// m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ),
|
|
static_cast<float>(m_spawncount + (m_spawnrate * Timedelta * Global.SmokeFidelity * ((((m_owner.vehicle->MoverParameters->DElist[m_owner.vehicle->MoverParameters->MainCtrlPosNo].RPM -m_owner.vehicle->MoverParameters->enrot) /60) * (sqrt(m_owner.vehicle->MoverParameters->Im) * 0.01) * 0.02) *(m_owner.vehicle->MoverParameters->EnginePower * 0.005)))), static_cast<float>(m_max_particles));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_max_particles ) ) {
|
|
|
|
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::ranges::sort(
|
|
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 = glm::mix(boundingbox[value_limit::min], boundingbox[value_limit::max], 0.5);
|
|
m_area.radius = static_cast<float>(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 += m_owner.vehicle->GetPosition();
|
|
break;
|
|
}
|
|
case owner_type::node: {
|
|
auto const rotationx { glm::angleAxis( glm::radians( m_owner.node->Angles().x ), glm::vec3{ 1.f, 0.f, 0.f } ) };
|
|
auto const rotationy { glm::angleAxis( glm::radians( m_owner.node->Angles().y ), glm::vec3{ 0.f, 1.f, 0.f } ) };
|
|
auto const rotationz { glm::angleAxis( glm::radians( m_owner.node->Angles().z ), glm::vec3{ 0.f, 0.f, 1.f } ) };
|
|
location = rotationy * rotationx * rotationz * 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 *= static_cast<float>(m_owner.vehicle->MoverParameters->dizel_fill);
|
|
if (m_owner.vehicle->MoverParameters->EngineType == TEngineType::DieselElectric)
|
|
{
|
|
if (m_owner.vehicle->MoverParameters->dizel_spinup == true)
|
|
{
|
|
Particle.velocity *= 0.38*(((m_owner.vehicle->MoverParameters->enrot)/2)*0.5); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
Particle.velocity *= ((((m_owner.vehicle->MoverParameters->enrot)/60)*0.6) * (m_owner.vehicle->MoverParameters->EnginePower *0.03)); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
|
//Particle.velocity *= m_owner.vehicle->GetVelocity(); // / m_owner.vehicle->MoverParameters->dizel_fill *0.01)) ;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
if (m_ownertype == owner_type::vehicle)
|
|
{
|
|
Particle.velocity.y += static_cast<float>(( 0.025 * Particle.velocity.y ) * std::min( 0.f, Global.AirTemperature - 90 ) * Timedelta); // decelerate faster in cold weather
|
|
Particle.velocity.y -= static_cast<float>(( (0.05 * (pow(m_owner.vehicle->GetVelocity()*1,0.4))) * Particle.velocity.y ) * Global.Overcast * Timedelta); // decelerate faster with high air humidity and/or precipitation
|
|
}
|
|
else
|
|
{
|
|
Particle.velocity.y += static_cast<float>(( 0.005 * Particle.velocity.y ) * std::min( 0.f, Global.AirTemperature - 10 ) * Timedelta); // decelerate faster in cold weather
|
|
Particle.velocity.y -= static_cast<float>(( 0.050 * Particle.velocity.y ) * Global.Overcast * Timedelta); // decelerate faster with high air humidity and/or precipitation
|
|
Particle.velocity.y = std::max<float>( static_cast<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 += static_cast<float>(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...
|
|
if( auto const lookup { m_sourcetemplates.find( templatename ) }; 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 (cParser parser(templatepath + templatename + ".txt", cParser::buffer_FILE); source.deserialize(parser))
|
|
{
|
|
// if deserialization didn't fail finish source setup...
|
|
source.m_opacitymodifier.bind( &Global.SmokeFidelity );
|
|
// ...then cache the source as template for future instances
|
|
m_sourcetemplates.try_emplace( templatename, source );
|
|
// should be 'safe enough' to return lookup result directly afterwards
|
|
return &( m_sourcetemplates.find( templatename )->second );
|
|
}
|
|
else {
|
|
ErrorLog( "Bad file: failed to locate particle source configuration file \"" + std::string( templatepath + templatename + ".txt" ) + "\"", logtype::file );
|
|
}
|
|
// if fetching data from the file fails too, give up
|
|
return nullptr;
|
|
}
|