diff --git a/CMakeLists.txt b/CMakeLists.txt index dfe58b66..9d19e844 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -463,6 +463,7 @@ set(CORE_SOURCES "scripting/ladderlogic.cpp" "vehicle/handover.cpp" "vehicle/horn.cpp" +"vehicle/smoke.cpp" ) add_library(eu07_core STATIC ${CORE_SOURCES}) target_link_libraries(eu07_core PUBLIC eu07_settings) diff --git a/rendering/particles.cpp b/rendering/particles.cpp index 6719eb6d..8ccb728d 100644 --- a/rendering/particles.cpp +++ b/rendering/particles.cpp @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. #include "model/AnimModel.h" #include "simulation/simulationenvironment.h" #include "utilities/Logs.h" +#include "vehicle/smoke.h" void @@ -162,60 +163,32 @@ smoke_source::update( double const Timedelta, bool const Onlydespawn ) { glm::dvec3{ std::numeric_limits::max() }, glm::dvec3{ std::numeric_limits::lowest() } }; - // a vehicle taken off the tracks (gone through a portal, or waiting in the pool of the road traffic) puffs no more, - // the particles it already let out fade away as usual - auto const nospawn { - false == Global.Smoke - || true == Onlydespawn - || ( m_ownertype == owner_type::vehicle && false == m_owner.vehicle->bEnabled ) }; + auto const spawning { ( true == Global.Smoke ) && ( false == Onlydespawn ) }; - m_spawncount = ( - ( true == nospawn ) ? - 0.f : - std::min( - static_cast(m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity )), - static_cast(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 = - (true == nospawn) ? - 0.f : - std::min( - // m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ), - static_cast(m_spawncount +(m_spawnrate * Timedelta * Global.SmokeFidelity * (((m_owner.vehicle->MoverParameters->enrot) / 4 ) * 0.01))), static_cast(m_max_particles)); - } - else - { - if (m_owner.vehicle->MoverParameters->DirAbsolute == 0 || - m_owner.vehicle->MoverParameters->Im == 0) - { - m_spawncount = - (true == nospawn) ? - 0.f : - std::min( - // m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ), - static_cast(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(m_max_particles)); - } - else - { - - m_spawncount = - (true == nospawn) ? - 0.f : - std::min( - // m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ), - static_cast(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(m_max_particles)); - } - } - } + auto const &mover { *m_owner.vehicle->MoverParameters }; + if( Global.AirTemperature <= 5.f || mover.dizel_heat.Ts < 45.f ) { + m_emitter.color = glm::vec3{ 128, 128, 128 }; + } + // a vehicle taken off the tracks (gone through a portal, or waiting in the pool of the road traffic) puffs no more, + // the particles it already let out fade away as usual + m_spawncount = ( + ( true == spawning ) ? + smoke::vehicle_spawn_count( + mover, m_owner.vehicle->bEnabled, + { m_spawncount, m_spawnrate, Global.SmokeFidelity, static_cast( m_max_particles ) }, + Timedelta ) : + 0.f ); + } + else { + m_spawncount = ( + ( true == spawning ) ? + std::min( + static_cast(m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity )), + static_cast(m_max_particles) ) : + 0.f ); + } // update spawned particles for( auto particleiterator { std::begin( m_particles ) }; particleiterator != std::end( m_particles ); ++particleiterator ) { diff --git a/tests/unit/test_smoke.cpp b/tests/unit/test_smoke.cpp new file mode 100644 index 00000000..05fb8ae8 --- /dev/null +++ b/tests/unit/test_smoke.cpp @@ -0,0 +1,85 @@ +#include "stdafx.h" +#include "doctest.h" + +#include "vehicle.h" + +#include "vehicle/smoke.h" + +// the smoke of a vehicle's engine: how many particles the smoke source of the vehicle's model spawns in an update + +namespace { + +// a source of 20 particles per second, with room for 100 +smoke::spawn_rate const source{0.f, 20.f, 1.f, 100.f}; +double const update{0.1}; // s + +// a diesel at work, its engine in the state of one of the cases below +test::vehicle diesel() { + test::vehicle vehicle{test::definition::load("wagon")}; + vehicle->MainCtrlPosNo = 1; + vehicle->DElist[1].RPM = 1500.0; + vehicle->enrot = 300.0; + vehicle->EnginePower = 400.0; + return vehicle; +} + +} // namespace + +TEST_CASE("smoke: a vehicle taken off the tracks puffs no more (#90)") { + auto vehicle{diesel()}; + SUBCASE("engine starting") { vehicle->dizel_spinup = true; } + SUBCASE("engine idling") { vehicle->DirAbsolute = 0; } + SUBCASE("engine under load") { + vehicle->DirAbsolute = 1; + vehicle->Im = 400.0; + } + // on the tracks the engine puffs + REQUIRE(smoke::vehicle_spawn_count(vehicle.mover(), true, source, update) >= 1.f); + + // gone through a portal, the pending particles are dropped too + auto pending{source}; + pending.pending = 5.f; + CHECK(smoke::vehicle_spawn_count(vehicle.mover(), false, pending, update) == 0.f); +} + +TEST_CASE("smoke: a road vehicle back on the road from the pool puffs again") { + auto vehicle{diesel()}; + // waiting in the pool, the count stays at zero update after update + auto rate{source}; + for (auto i{0}; i < 10; ++i) { + rate.pending = smoke::vehicle_spawn_count(vehicle.mover(), false, rate, update); + } + REQUIRE(rate.pending == 0.f); + + CHECK(smoke::vehicle_spawn_count(vehicle.mover(), true, rate, update) >= 1.f); +} + +TEST_CASE("smoke: the engine adds to the steady rate of the source") { + auto vehicle{diesel()}; + vehicle->DirAbsolute = 0; + auto const steady{source.rate * update}; // the rate of a source of the scenery + + vehicle->EnginePower = 0.0; + CHECK(smoke::vehicle_spawn_count(vehicle.mover(), true, source, update) == doctest::Approx(steady)); + + vehicle->EnginePower = 400.0; + auto const working{smoke::vehicle_spawn_count(vehicle.mover(), true, source, update)}; + CHECK(working > steady); + + // the faster the starting engine turns, the more it puffs + vehicle->dizel_spinup = true; + vehicle->enrot = 100.0; + auto const slow{smoke::vehicle_spawn_count(vehicle.mover(), true, source, update)}; + vehicle->enrot = 400.0; + CHECK(smoke::vehicle_spawn_count(vehicle.mover(), true, source, update) > slow); +} + +TEST_CASE("smoke: the pending particles never outgrow the room of the source") { + auto vehicle{diesel()}; + vehicle->dizel_spinup = true; + vehicle->enrot = 100000.0; + auto rate{source}; + rate.pending = 99.f; + + CHECK(smoke::vehicle_spawn_count(vehicle.mover(), true, rate, 10.0) == rate.limit); +} diff --git a/vehicle/smoke.cpp b/vehicle/smoke.cpp new file mode 100644 index 00000000..b6e92322 --- /dev/null +++ b/vehicle/smoke.cpp @@ -0,0 +1,46 @@ +/* +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 "vehicle/smoke.h" + +#include "McZapkie/MOVER.h" + +namespace smoke { + +namespace { + +// how hard the engine works, scaling the spawn rate of the source +double engine_load(TMoverParameters const &Mover) { + if (Mover.dizel_spinup) { + // starting up, it puffs harder the faster it turns + return ((Mover.enrot / 4) * 0.01); + } + auto const rpmrise{(Mover.DElist[Mover.MainCtrlPosNo].RPM - Mover.enrot) / 60}; + if (Mover.DirAbsolute == 0 || Mover.Im == 0) { + // idling: speeding up to the revolutions of the master controller + return ((rpmrise * 0.02) * (Mover.EnginePower * 0.005)); + } + // under load: the more current the traction motors draw, the more smoke + return ((rpmrise * (std::sqrt(Mover.Im) * 0.01) * 0.02) * (Mover.EnginePower * 0.005)); +} + +} // namespace + +float vehicle_spawn_count(TMoverParameters const &Mover, bool const Ontracks, spawn_rate const &Rate, double const Timedelta) { + if (false == Ontracks) { + return 0.f; + } + // the steady rate of the source, the way a source of the scenery spawns... + auto const steady{std::min(static_cast(Rate.pending + (Rate.rate * Timedelta * Rate.fidelity)), Rate.limit)}; + // ...and on top of it the share of the engine + return std::min(static_cast(steady + (Rate.rate * Timedelta * Rate.fidelity * engine_load(Mover))), Rate.limit); +} + +} // namespace smoke diff --git a/vehicle/smoke.h b/vehicle/smoke.h new file mode 100644 index 00000000..e9dac262 --- /dev/null +++ b/vehicle/smoke.h @@ -0,0 +1,30 @@ +/* +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 + +class TMoverParameters; + +// the smoke of a vehicle's engine, let out by the smoke sources of its model (rendering/particles.h) +namespace smoke { + +// what a smoke source spawns, before the engine of its vehicle is taken into account +struct spawn_rate { + float pending{0.f}; // particles left to spawn from the previous updates + float rate{0.f}; // particles per second + float fidelity{1.f}; // multiplier of the smoke settings + float limit{0.f}; // most particles the source can have +}; + +// number of particles the smoke source of a vehicle has to spawn after Timedelta seconds: the steady rate of the +// source, plus a share which grows with the work of its engine. a vehicle taken off the tracks (Ontracks false: gone through a portal, or waiting in the pool of the road +// traffic) puffs no more, the particles it already let out fade away as usual +float vehicle_spawn_count(TMoverParameters const &Mover, bool Ontracks, spawn_rate const &Rate, double Timedelta); + +} // namespace smoke