Skip to content

Commit

Permalink
Improve snow particle customizability and code style (SuperTux#2756)
Browse files Browse the repository at this point in the history
Exposes more variables to the reader to allow for more customizable snow particles.
  • Loading branch information
bruhmoent authored Feb 1, 2024
1 parent 4d102d3 commit 10fe530
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 73 deletions.
136 changes: 78 additions & 58 deletions src/object/snow_particle_system.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SuperTux
// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
// Copyright (C) 2024 bruhmoent
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand All @@ -21,140 +22,159 @@

#include "math/random.hpp"
#include "supertux/sector.hpp"
#include "util/reader_mapping.hpp"
#include "video/surface.hpp"
#include "video/video_system.hpp"
#include "video/viewport.hpp"

// TODO: tweak values
namespace SNOW {
static const float SPIN_SPEED = 60.0f;
static const float WIND_SPEED = 30.0f; // max speed of wind will be randf(WIND_SPEED) * randf(STATE_LENGTH)
static const float STATE_LENGTH = 5.0f;
static const float DECAY_RATIO = 0.2f; // ratio of attack speed to decay speed
static const float EPSILON = 0.5f; //velocity changes by up to this much each tick
static const float WOBBLE_DECAY = 0.99f; //wobble decays exponentially by this much each tick
static const float WOBBLE_FACTOR = 4 * .005f; //wobble approaches drift_speed by this much each tick
}

static const float DECAY_RATIO = 0.2f; // Ratio of attack speed to decay speed.
static const float WOBBLE_DECAY = 0.99f; // Wobble decays exponentially by this much each tick.
static const float WOBBLE_FACTOR = 4 * .005f; // Wobble approaches drift_speed by this much each tick.

SnowParticleSystem::SnowParticleSystem() :
state(RELEASING),
timer(),
gust_onset(0),
gust_current_velocity(0)
m_state(RELEASING),
m_timer(),
m_gust_onset(0),
m_gust_current_velocity(0)
{
init();
}

SnowParticleSystem::SnowParticleSystem(const ReaderMapping& reader) :
ParticleSystem(reader),
state(RELEASING),
timer(),
gust_onset(0),
gust_current_velocity(0)
m_state(RELEASING),
m_timer(),
m_gust_onset(0),
m_gust_current_velocity(0)
{
reader.get("state_length", m_state_length, 5.0f);
reader.get("wind_speed", m_wind_speed, 30.0f);
reader.get("spin_speed", m_spin_speed, 60.0f);
reader.get("epsilon", m_epsilon, 0.5f);
init();
}

SnowParticleSystem::~SnowParticleSystem()
{
}

void SnowParticleSystem::init()
void
SnowParticleSystem::init()
{
snowimages[0] = Surface::from_file("images/particles/snow2.png");
snowimages[1] = Surface::from_file("images/particles/snow1.png");
snowimages[2] = Surface::from_file("images/particles/snow0.png");
m_snowimages[0] = Surface::from_file("images/particles/snow2.png");
m_snowimages[1] = Surface::from_file("images/particles/snow1.png");
m_snowimages[2] = Surface::from_file("images/particles/snow0.png");

virtual_width = static_cast<float>(SCREEN_WIDTH) * 2.0f;

timer.start(.01f);
m_timer.start(.01f);

// create some random snowflakes
// Create random snowflakes.
int snowflakecount = static_cast<int>(virtual_width / 10.0f);
for (int i = 0; i < snowflakecount; ++i) {
for (int i = 0; i < snowflakecount; ++i)
{
auto particle = std::make_unique<SnowParticle>();
int snowsize = graphicsRandom.rand(3);

particle->pos.x = graphicsRandom.randf(virtual_width);
particle->pos.y = graphicsRandom.randf(static_cast<float>(SCREEN_HEIGHT));
particle->anchorx = particle->pos.x + (graphicsRandom.randf(-0.5, 0.5) * 16);
// drift will change with wind gusts
// Drift will change with wind gusts.
particle->drift_speed = graphicsRandom.randf(-0.5f, 0.5f) * 0.3f;
particle->wobble = 0.0;

particle->texture = snowimages[snowsize];
particle->flake_size = static_cast<int>(powf(static_cast<float>(snowsize) + 3.0f, 4.0f)); // since it ranges from 0 to 2
particle->texture = m_snowimages[snowsize];
particle->flake_size = static_cast<int>(powf(static_cast<float>(snowsize) + 3.0f, 4.0f)); // Since it ranges from 0 to 2.

particle->speed = 6.32f * (1.0f + (2.0f - static_cast<float>(snowsize)) / 2.0f + graphicsRandom.randf(1.8f));

// Spinning
// Spinning.
particle->angle = graphicsRandom.randf(360.0);
particle->spin_speed = graphicsRandom.randf(-SNOW::SPIN_SPEED,SNOW::SPIN_SPEED);
particle->spin_speed = graphicsRandom.randf(-m_spin_speed, m_spin_speed);

particles.push_back(std::move(particle));
}
}

void SnowParticleSystem::update(float dt_sec)
ObjectSettings
SnowParticleSystem::get_settings()
{
ObjectSettings result = ParticleSystem::get_settings();

result.add_float(_("Epsilon"), &m_epsilon, "epsilon", 0.5f);
result.add_float(_("Spin Speed"), &m_spin_speed, "spin_speed", 60.0f);
result.add_float(_("State Length"), &m_state_length, "state_length", 5.0f);
result.add_float(_("Wind Speed"), &m_wind_speed, "wind_speed", 30.0f);

result.reorder({ "epsilon", "spin_speed", "state_length", "wind_speed", "enabled", "name" });

return result;
}

void
SnowParticleSystem::update(float dt_sec)
{
if (!enabled)
return;

// Simple ADSR wind gusts.

// Simple ADSR wind gusts

if (timer.check()) {
if (m_timer.check())
{
// Change state
state = static_cast<State>((state + 1) % MAX_STATE);

if (state == RESTING) {
// stop wind
gust_current_velocity = 0;
// new wind strength
gust_onset = graphicsRandom.randf(-SNOW::WIND_SPEED, SNOW::WIND_SPEED);
m_state = static_cast<State>((m_state + 1) % MAX_STATE);

if (m_state == RESTING)
{
// Stop wind.
m_gust_current_velocity = 0;
// New wind strength.
m_gust_onset = -m_wind_speed;
}
timer.start(graphicsRandom.randf(SNOW::STATE_LENGTH));
m_timer.start(graphicsRandom.randf(m_state_length));
}

// Update velocities
switch (state) {
// Update velocities.
switch (m_state)
{
case ATTACKING:
gust_current_velocity += gust_onset * dt_sec;
m_gust_current_velocity += m_gust_onset * dt_sec;
break;
case DECAYING:
gust_current_velocity -= gust_onset * dt_sec * SNOW::DECAY_RATIO;
m_gust_current_velocity -= m_gust_onset * dt_sec * DECAY_RATIO;
break;
case RELEASING:
// uses current time/velocity instead of constants
gust_current_velocity -= gust_current_velocity * dt_sec / timer.get_timeleft();
// Uses current time/velocity instead of constants.
m_gust_current_velocity -= m_gust_current_velocity * dt_sec / m_timer.get_timeleft();
break;
case SUSTAINING:
case RESTING:
//do nothing
break;
default:
assert(false);
}

float sq_g = sqrtf(Sector::get().get_gravity());

for (auto& part : particles) {
for (auto& part : particles)
{
auto particle = dynamic_cast<SnowParticle*>(part.get());
if (!particle)
continue;

float anchor_delta;

// Falling
// Falling.
particle->pos.y += particle->speed * dt_sec * sq_g;
// Drifting (speed approaches wind at a rate dependent on flake size)
particle->drift_speed += (gust_current_velocity - particle->drift_speed) / static_cast<float>(particle->flake_size) + graphicsRandom.randf(-SNOW::EPSILON, SNOW::EPSILON);
// Drifting (speed approaches wind at a rate dependent on flake size).
particle->drift_speed += (m_gust_current_velocity - particle->drift_speed) / static_cast<float>(particle->flake_size) + graphicsRandom.randf(-m_epsilon, m_epsilon);
particle->anchorx += particle->drift_speed * dt_sec;
// Wobbling (particle approaches anchorx)
// Wobbling (particle approaches anchorx).
particle->pos.x += particle->wobble * dt_sec * sq_g;
anchor_delta = (particle->anchorx - particle->pos.x);
particle->wobble += (SNOW::WOBBLE_FACTOR * anchor_delta) + graphicsRandom.randf(-SNOW::EPSILON, SNOW::EPSILON);
particle->wobble *= SNOW::WOBBLE_DECAY;
// Spinning
particle->wobble += (WOBBLE_FACTOR * anchor_delta) + graphicsRandom.randf(-m_epsilon, m_epsilon);
particle->wobble *= WOBBLE_DECAY;
// Spinning.
particle->angle += particle->spin_speed * dt_sec;
particle->angle = fmodf(particle->angle, 360.0);
}
Expand Down
37 changes: 22 additions & 15 deletions src/object/snow_particle_system.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SuperTux
// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
// Copyright (C) 2024 bruhmoent
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -35,14 +36,16 @@ class SnowParticleSystem final : public ParticleSystem
virtual std::string get_class_name() const override { return class_name(); }
static std::string display_name() { return _("Snow Particles"); }
virtual std::string get_display_name() const override { return display_name(); }
virtual ObjectSettings get_settings() override;

virtual const std::string get_icon_path() const override {
virtual const std::string get_icon_path() const override
{
return "images/engine/editor/snow.png";
}

private:
void init();

private:
class SnowParticle : public Particle
{
public:
Expand All @@ -51,10 +54,10 @@ class SnowParticleSystem final : public ParticleSystem
float anchorx;
float drift_speed;

// Turning speed
// Turning speed.
float spin_speed;

// for inertia
// For inertia.
unsigned int flake_size;

SnowParticle() :
Expand All @@ -67,9 +70,8 @@ class SnowParticleSystem final : public ParticleSystem
{}
};

// Wind is simulated in discrete "gusts"

// Gust state
// Wind is simulated in discrete "gusts",
// gust states:
enum State {
ATTACKING,
DECAYING,
Expand All @@ -80,19 +82,24 @@ class SnowParticleSystem final : public ParticleSystem
};

private:
State state;
State m_state;

// Gust state delay timer
Timer timer;
// Gust state delay timer.
Timer m_timer;

// Peak magnitude of gust is gust_onset * randf(5)
float gust_onset;
// Peak magnitude of gust is gust_onset * randf(5).
float m_gust_onset;

// Current blowing velocity of gust
float gust_current_velocity;
// Current blowing velocity of gust.
float m_gust_current_velocity;

SurfacePtr snowimages[3];
float m_wind_speed;
float m_epsilon; // Velocity changes by up to this much each tick.
float m_spin_speed;
float m_state_length; // Interval for how long to affect the particles with wind.

SurfacePtr m_snowimages[3];

private:
SnowParticleSystem(const SnowParticleSystem&) = delete;
SnowParticleSystem& operator=(const SnowParticleSystem&) = delete;
Expand Down

0 comments on commit 10fe530

Please sign in to comment.