1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "object/snow_particle_system.hpp"
18
19#include <assert.h>
20#include <math.h>
21
22#include "math/random.hpp"
23#include "supertux/sector.hpp"
24#include "video/surface.hpp"
25#include "video/video_system.hpp"
26#include "video/viewport.hpp"
27
28// TODO: tweak values
29namespace SNOW {
30static const float SPIN_SPEED = 60.0f;
31static const float WIND_SPEED = 30.0f; // max speed of wind will be randf(WIND_SPEED) * randf(STATE_LENGTH)
32static const float STATE_LENGTH = 5.0f;
33static const float DECAY_RATIO = 0.2f; // ratio of attack speed to decay speed
34static const float EPSILON = 0.5f; //velocity changes by up to this much each tick
35static const float WOBBLE_DECAY = 0.99f; //wobble decays exponentially by this much each tick
36static const float WOBBLE_FACTOR = 4 * .005f; //wobble approaches drift_speed by this much each tick
37}
38
39SnowParticleSystem::SnowParticleSystem() :
40 state(RELEASING),
41 timer(),
42 gust_onset(0),
43 gust_current_velocity(0)
44{
45 init();
46}
47
48SnowParticleSystem::SnowParticleSystem(const ReaderMapping& reader) :
49 ParticleSystem(reader),
50 state(RELEASING),
51 timer(),
52 gust_onset(0),
53 gust_current_velocity(0)
54{
55 init();
56}
57
58SnowParticleSystem::~SnowParticleSystem()
59{
60}
61
62void SnowParticleSystem::init()
63{
64 snowimages[0] = Surface::from_file("images/objects/particles/snow2.png");
65 snowimages[1] = Surface::from_file("images/objects/particles/snow1.png");
66 snowimages[2] = Surface::from_file("images/objects/particles/snow0.png");
67
68 virtual_width = static_cast<float>(SCREEN_WIDTH) * 2.0f;
69
70 timer.start(.01f);
71
72 // create some random snowflakes
73 int snowflakecount = static_cast<int>(virtual_width / 10.0f);
74 for (int i = 0; i < snowflakecount; ++i) {
75 auto particle = std::make_unique<SnowParticle>();
76 int snowsize = graphicsRandom.rand(3);
77
78 particle->pos.x = graphicsRandom.randf(virtual_width);
79 particle->pos.y = graphicsRandom.randf(static_cast<float>(SCREEN_HEIGHT));
80 particle->anchorx = particle->pos.x + (graphicsRandom.randf(-0.5, 0.5) * 16);
81 // drift will change with wind gusts
82 particle->drift_speed = graphicsRandom.randf(-0.5f, 0.5f) * 0.3f;
83 particle->wobble = 0.0;
84
85 particle->texture = snowimages[snowsize];
86 particle->flake_size = static_cast<int>(powf(static_cast<float>(snowsize) + 3.0f, 4.0f)); // since it ranges from 0 to 2
87
88 particle->speed = 6.32f * (1.0f + (2.0f - static_cast<float>(snowsize)) / 2.0f + graphicsRandom.randf(1.8f));
89
90 // Spinning
91 particle->angle = graphicsRandom.randf(360.0);
92 particle->spin_speed = graphicsRandom.randf(-SNOW::SPIN_SPEED,SNOW::SPIN_SPEED);
93
94 particles.push_back(std::move(particle));
95 }
96}
97
98void SnowParticleSystem::update(float dt_sec)
99{
100 if (!enabled)
101 return;
102
103 // Simple ADSR wind gusts
104
105 if (timer.check()) {
106 // Change state
107 state = static_cast<State>((state + 1) % MAX_STATE);
108
109 if (state == RESTING) {
110 // stop wind
111 gust_current_velocity = 0;
112 // new wind strength
113 gust_onset = graphicsRandom.randf(-SNOW::WIND_SPEED, SNOW::WIND_SPEED);
114 }
115 timer.start(graphicsRandom.randf(SNOW::STATE_LENGTH));
116 }
117
118 // Update velocities
119 switch (state) {
120 case ATTACKING:
121 gust_current_velocity += gust_onset * dt_sec;
122 break;
123 case DECAYING:
124 gust_current_velocity -= gust_onset * dt_sec * SNOW::DECAY_RATIO;
125 break;
126 case RELEASING:
127 // uses current time/velocity instead of constants
128 gust_current_velocity -= gust_current_velocity * dt_sec / timer.get_timeleft();
129 break;
130 case SUSTAINING:
131 case RESTING:
132 //do nothing
133 break;
134 default:
135 assert(false);
136 }
137
138 float sq_g = sqrtf(Sector::get().get_gravity());
139
140 for (auto& part : particles) {
141 auto particle = dynamic_cast<SnowParticle*>(part.get());
142 if (!particle)
143 continue;
144
145 float anchor_delta;
146
147 // Falling
148 particle->pos.y += particle->speed * dt_sec * sq_g;
149 // Drifting (speed approaches wind at a rate dependent on flake size)
150 particle->drift_speed += (gust_current_velocity - particle->drift_speed) / static_cast<float>(particle->flake_size) + graphicsRandom.randf(-SNOW::EPSILON, SNOW::EPSILON);
151 particle->anchorx += particle->drift_speed * dt_sec;
152 // Wobbling (particle approaches anchorx)
153 particle->pos.x += particle->wobble * dt_sec * sq_g;
154 anchor_delta = (particle->anchorx - particle->pos.x);
155 particle->wobble += (SNOW::WOBBLE_FACTOR * anchor_delta) + graphicsRandom.randf(-SNOW::EPSILON, SNOW::EPSILON);
156 particle->wobble *= SNOW::WOBBLE_DECAY;
157 // Spinning
158 particle->angle += particle->spin_speed * dt_sec;
159 particle->angle = fmodf(particle->angle, 360.0);
160 }
161}
162
163/* EOF */
164