17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-11 22:29:03 +01:00

Merge pull request #257 from MaSzyna-EU07/fix/vehicle-removal-smoke

fix(gfx): a vehicle taken off the tracks stops smoking (#90)
This commit is contained in:
2026-11-07 13:28:31 +00:00
committed by GitHub
6 changed files with 188 additions and 46 deletions

View File

@@ -464,6 +464,7 @@ set(CORE_SOURCES
"scripting/ladderlogic.cpp" "scripting/ladderlogic.cpp"
"vehicle/handover.cpp" "vehicle/handover.cpp"
"vehicle/horn.cpp" "vehicle/horn.cpp"
"vehicle/smoke.cpp"
) )
add_library(eu07_core STATIC ${CORE_SOURCES}) add_library(eu07_core STATIC ${CORE_SOURCES})
target_link_libraries(eu07_core PUBLIC eu07_settings) target_link_libraries(eu07_core PUBLIC eu07_settings)

View File

@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
#include "model/AnimModel.h" #include "model/AnimModel.h"
#include "simulation/simulationenvironment.h" #include "simulation/simulationenvironment.h"
#include "utilities/Logs.h" #include "utilities/Logs.h"
#include "vehicle/smoke.h"
void void
@@ -162,53 +163,32 @@ smoke_source::update( double const Timedelta, bool const Onlydespawn ) {
glm::dvec3{ std::numeric_limits<double>::max() }, glm::dvec3{ std::numeric_limits<double>::max() },
glm::dvec3{ std::numeric_limits<double>::lowest() } }; glm::dvec3{ std::numeric_limits<double>::lowest() } };
m_spawncount = ( auto const spawning { ( true == Global.Smoke ) && ( false == Onlydespawn ) };
( ( 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( m_ownertype == owner_type::vehicle ) {
if (Global.AirTemperature <= 5.f || m_owner.vehicle->MoverParameters->dizel_heat.Ts < 45.f) 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}; 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,
if (m_owner.vehicle->MoverParameters->dizel_spinup == true) // the particles it already let out fade away as usual
{ m_spawncount = (
m_spawncount = ( true == spawning ) ?
((false == Global.Smoke) || (true == Onlydespawn)) ? smoke::vehicle_spawn_count(
0.f : mover, m_owner.vehicle->bEnabled,
std::min<float>( { m_spawncount, m_spawnrate, Global.SmokeFidelity, static_cast<float>( m_max_particles ) },
// m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ), Timedelta ) :
static_cast<float>(m_spawncount +(m_spawnrate * Timedelta * Global.SmokeFidelity * (((m_owner.vehicle->MoverParameters->enrot) / 4 ) * 0.01))), static_cast<float>(m_max_particles)); 0.f );
} }
else else {
{ m_spawncount = (
if (m_owner.vehicle->MoverParameters->DirAbsolute == 0 || ( true == spawning ) ?
m_owner.vehicle->MoverParameters->Im == 0) std::min<float>(
{ static_cast<float>(m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity )),
m_spawncount = static_cast<float>(m_max_particles) ) :
((false == Global.Smoke) || (true == Onlydespawn)) ? 0.f );
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 // update spawned particles
for( auto particleiterator { std::begin( m_particles ) }; particleiterator != std::end( m_particles ); ++particleiterator ) { for( auto particleiterator { std::begin( m_particles ) }; particleiterator != std::end( m_particles ); ++particleiterator ) {

85
tests/unit/test_smoke.cpp Normal file
View File

@@ -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);
}

View File

@@ -4852,7 +4852,7 @@ void TDynamicObject::RenderSounds() {
#endif #endif
&& ( // compound test whether the vehicle belongs to user-driven consist (as these don't emit outer noise in cab view) && ( // compound test whether the vehicle belongs to user-driven consist (as these don't emit outer noise in cab view)
FreeFlyModeFlag ? true : // in external view all vehicles emit outer noise FreeFlyModeFlag ? true : // in external view all vehicles emit outer noise
// Global.pWorld->train() == nullptr ? true : // (can skip this check, with no player train the external view is a given) simulation::Train == nullptr ? true : // no player train, e.g. it went through a portal and the view isn't switched yet
ctOwner == nullptr ? true : // standalone vehicle, can't be part of user-driven train ctOwner == nullptr ? true : // standalone vehicle, can't be part of user-driven train
ctOwner != simulation::Train->Dynamic()->ctOwner ? true : // confirmed isn't a part of the user-driven train ctOwner != simulation::Train->Dynamic()->ctOwner ? true : // confirmed isn't a part of the user-driven train
Global.CabWindowOpen ? true : // sticking head out we get to hear outer noise Global.CabWindowOpen ? true : // sticking head out we get to hear outer noise

46
vehicle/smoke.cpp Normal file
View File

@@ -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<float>(static_cast<float>(Rate.pending + (Rate.rate * Timedelta * Rate.fidelity)), Rate.limit)};
// ...and on top of it the share of the engine
return std::min<float>(static_cast<float>(steady + (Rate.rate * Timedelta * Rate.fidelity * engine_load(Mover))), Rate.limit);
}
} // namespace smoke

30
vehicle/smoke.h Normal file
View File

@@ -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