diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index 9a2b7ab5..70e8eb10 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -269,6 +269,11 @@ are no longer loaded. what it doesn't see and lights the rest with the sun (with the shadows of the terrain, models and clouds), the sky and the lamps and headlights, as the surfaces are lit: smoke in the shade is darker, smoke against the sun glows, smoke in the beam of headlights at night is lit by it. +- **Templates of smoke sources** (`data/smokesource_*.txt`, both renderers) may say how heavy the particles are: with + `terminal_velocity: v` (m/s, positive up: light, hot smoke rises; negative down: dust, sand, sparks fall) the velocity + of a particle goes over to `v` (straight up or down, besides the drift with the wind), the faster the larger `drag: d` + (per second, default 1: about 63% of the way in a second). It takes the place of the old rules of the rise (slower in + cold and humid air); a template without it behaves as before. The particles don't stop at the ground. - **Rain and snow** are particles in three boxes around the camera (10, 24 and 60 metres), more of them the heavier the rain or snow (the overcast above 1). The drops and flakes fall, drift with the wind, the flakes flutter, and each is drawn as a streak of its motion against the camera: they slant when the train moves. They are lit like the smoke, so the diff --git a/gl/ubo.h b/gl/ubo.h index 8d95caab..a070b78f 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -526,10 +526,11 @@ namespace gl glm::vec4 sizechange; glm::vec4 opacitychange; glm::vec4 motion; + glm::vec4 settling; glm::vec4 color; glm::vec4 oldcolor; }; - static_assert(sizeof(particle_smoke_source) == 160, "bad size of particle_smoke_source"); + static_assert(sizeof(particle_smoke_source) == 176, "bad size of particle_smoke_source"); // layout std430: GLSL particle_draw_s, read by the draws as attributes of instances struct particle_draw diff --git a/rendering/opengl46/opengl46gpuparticles.cpp b/rendering/opengl46/opengl46gpuparticles.cpp index 0e6e140b..13b9b784 100644 --- a/rendering/opengl46/opengl46gpuparticles.cpp +++ b/rendering/opengl46/opengl46gpuparticles.cpp @@ -249,6 +249,7 @@ void opengl46_gpu_particles::simulate_smoke(particle_manager &Particles, glm::dv record.sizechange = glm::vec4{state.sizechange[0], state.sizechange[1], state.sizechange[2], 0.f}; record.opacitychange = glm::vec4{state.opacitychange[0], state.opacitychange[1], state.opacitychange[2], 0.f}; record.motion = glm::vec4{state.rise, state.damping, state.risemin, 0.f}; + record.settling = glm::vec4{state.settling, state.drag, state.settles ? 1.f : 0.f, 0.f}; record.color = glm::vec4{glm::clamp(state.color, glm::vec3{0.f}, glm::vec3{1.f}), 0.f}; record.oldcolor = glm::vec4{glm::clamp(state.color * oldlight, glm::vec3{0.f}, glm::vec3{1.f}), 0.f}; if (pool.size > 0) diff --git a/rendering/particles.cpp b/rendering/particles.cpp index a945d894..95107665 100644 --- a/rendering/particles.cpp +++ b/rendering/particles.cpp @@ -115,6 +115,13 @@ smoke_source::deserialize_mapping( cParser &Input ) { else if( key == "opacity_change:" ) { m_opacitymodifier.deserialize( Input ); } + else if( key == "terminal_velocity:" ) { + m_settles = true; + m_settling = Input.getToken( true, "\n\r\t ,;[]" ); + } + else if( key == "drag:" ) { + m_drag = std::max( 0.f, Input.getToken( true, "\n\r\t ,;[]" ) ); + } return true; // return value marks a [ key: value ] pair was extracted, nothing about whether it's recognized } @@ -379,9 +386,14 @@ smoke_source::update( smoke_particle &Particle, bounding_box &Boundingbox, doubl // ... otherwise proceed with full update m_sizemodifier.update( Particle.size, Timedelta ); + if( m_settles ) { + // the drag of the air takes the particle towards the velocity it settles at + auto const share { static_cast( 1.0 - std::exp( -m_drag * Timedelta ) ) }; + Particle.velocity += ( glm::vec3{ 0.f, m_settling, 0.f } - Particle.velocity ) * share; + } // crude smoke dispersion simulation // http://www.auburn.edu/academic/forestry_wildlife/fire/smoke_guide/smoke_dispersion.htm - if (m_ownertype == owner_type::vehicle) + else if (m_ownertype == owner_type::vehicle) { Particle.velocity.y += static_cast(( 0.025 * Particle.velocity.y ) * std::min( 0.f, Global.AirTemperature - 90 ) * Timedelta); // decelerate faster in cold weather Particle.velocity.y -= static_cast(( (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 @@ -451,6 +463,9 @@ smoke_source::update_gpu( double const Timedelta, bool const Onlydespawn ) { m_gpu.damping = static_cast( 0.050 * Global.Overcast ); m_gpu.risemin = static_cast( 0.25 * ( 2.f - Global.Overcast ) ); } + m_gpu.settles = m_settles; + m_gpu.settling = m_settling; + m_gpu.drag = m_drag; m_gpu.color = m_emitter.color; // the renderer culls the particles one by one; the area is where they come from, for the distance of the source diff --git a/rendering/particles.h b/rendering/particles.h index 54512675..a8979d85 100644 --- a/rendering/particles.h +++ b/rendering/particles.h @@ -101,6 +101,10 @@ public: float rise { 0.f }; float damping { 0.f }; float risemin { std::numeric_limits::lowest() }; + // with a settling velocity (settles): the velocity goes over to it instead, see smoke_source::m_settling + bool settles { false }; + float settling { 0.f }; + float drag { 1.f }; glm::vec3 color { 0.f }; }; // methods @@ -189,6 +193,12 @@ private: fixedstep_modifier m_sizemodifier; // particle billboard size // fixedstep_modifier m_colormodifier; // particle billboard color and opacity fixedstep_modifier m_opacitymodifier; + // optional velocity the particles settle at in still air, m/s, positive upwards (light, hot smoke rises, heavy particles + // fall), and how fast they get to it: the share of the rest of the way per second is 1 - exp(-drag). when given, it takes + // the place of the crude dispersion of update( Particle ) (the rise in cold air, the damping in humid air) + bool m_settles { false }; + float m_settling { 0.f }; // terminal_velocity: + float m_drag { 1.f }; // drag: // 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 diff --git a/shaders/particle_common.glsl b/shaders/particle_common.glsl index 3e40c018..03fe200c 100644 --- a/shaders/particle_common.glsl +++ b/shaders/particle_common.glsl @@ -22,6 +22,7 @@ struct smoke_source_s vec4 sizechange; // x: per second, y: min, z: max vec4 opacitychange; // x: per second, y: min, z: max vec4 motion; // x: rise, y: damping (per second, of the vertical velocity), z: least vertical velocity + vec4 settling; // x: velocity the particles settle at, m/s upwards, y: drag, per second, z: 1 when they settle (in place of motion) vec4 color; // rgb: colour of the smoke, lit by the lights of the pass vec4 oldcolor; // rgb: colour lit as the cpu does it, without the lights (gfx.smoke.lighting off) }; diff --git a/shaders/particles_smoke_simulate.comp b/shaders/particles_smoke_simulate.comp index 590c3569..936b79b7 100644 --- a/shaders/particles_smoke_simulate.comp +++ b/shaders/particles_smoke_simulate.comp @@ -37,10 +37,18 @@ bool advance(inout smoke_particle_s particle, smoke_source_s source, float secon if (particle.state.x <= 0.0) return false; particle.velocity.w = clamp(particle.velocity.w + source.sizechange.x * seconds, source.sizechange.y, source.sizechange.z); - // crude smoke dispersion - particle.velocity.y += source.motion.x * particle.velocity.y * seconds; - particle.velocity.y -= source.motion.y * particle.velocity.y * seconds; - particle.velocity.y = max(source.motion.z, particle.velocity.y); + if (source.settling.z > 0.5) + { + // the drag of the air takes the particle towards the velocity it settles at + particle.velocity.xyz += (vec3(0.0, source.settling.x, 0.0) - particle.velocity.xyz) * (1.0 - exp(-source.settling.y * seconds)); + } + else + { + // crude smoke dispersion + particle.velocity.y += source.motion.x * particle.velocity.y * seconds; + particle.velocity.y -= source.motion.y * particle.velocity.y * seconds; + particle.velocity.y = max(source.motion.z, particle.velocity.y); + } particle.position.xyz += particle.velocity.xyz * seconds; particle.position.xyz += 0.1 * particle.state.y * wind.xyz * seconds; particle.state.y += seconds;