Skip to content

Instantly share code, notes, and snippets.

@yves-chevallier
Created December 4, 2021 16:36
Show Gist options
  • Save yves-chevallier/6b2bcccf6850d41e65fdb7e4d01d6810 to your computer and use it in GitHub Desktop.
Save yves-chevallier/6b2bcccf6850d41e65fdb7e4d01d6810 to your computer and use it in GitHub Desktop.
Particles in SFML
#include <SFML/Graphics.hpp>
#include <SFML/Graphics/Color.hpp>
#include <algorithm>
#include <cmath>
struct HSL {
double Hue;
double Saturation;
double Luminance;
HSL();
HSL(int H, int S, int L);
sf::Color TurnToRGB();
private:
double HueToRGB(double arg1, double arg2, double H);
};
HSL TurnToHSL(const sf::Color& C);
const double D_EPSILON = 0.00000000000001;
/// Feel free to move this to your constants .h file or use it as a static
/// constant if you don't like it here.
HSL::HSL() : Hue(0), Saturation(0), Luminance(0) {}
HSL::HSL(int H, int S, int L)
{
/// Range control for Hue.
if (H <= 360 && H >= 0) {
Hue = H;
} else {
if (H > 360) {
Hue = H % 360;
} else if (H < 0 && H > -360) {
Hue = -H;
} else if (H < 0 && H < -360) {
Hue = -(H % 360);
}
}
/// Range control for Saturation.
if (S <= 100 && S >= 0) {
Saturation = S;
} else {
if (S > 100) {
Saturation = S % 100;
} else if (S < 0 && S > -100) {
Saturation = -S;
} else if (S < 0 && S < -100) {
Saturation = -(S % 100);
}
}
/// Range control for Luminance
if (L <= 100 && L >= 0) {
Luminance = L;
} else {
if (L > 100) {
Luminance = L % 100;
}
if (L < 0 && L > -100) {
Luminance = -L;
}
if (L < 0 && L < -100) {
Luminance = -(L % 100);
}
}
}
double HSL::HueToRGB(double arg1, double arg2, double H)
{
if (H < 0) H += 1;
if (H > 1) H -= 1;
if ((6 * H) < 1) {
return (arg1 + (arg2 - arg1) * 6 * H);
}
if ((2 * H) < 1) {
return arg2;
}
if ((3 * H) < 2) {
return (arg1 + (arg2 - arg1) * ((2.0 / 3.0) - H) * 6);
}
return arg1;
}
sf::Color HSL::TurnToRGB()
{
/// Reconvert to range [0,1]
double H = Hue / 360.0;
double S = Saturation / 100.0;
double L = Luminance / 100.0;
float arg1, arg2;
if (S <= D_EPSILON) {
sf::Color C(L * 255, L * 255, L * 255);
return C;
} else {
if (L < 0.5) {
arg2 = L * (1 + S);
} else {
arg2 = (L + S) - (S * L);
}
arg1 = 2 * L - arg2;
sf::Uint8 r = (255 * HueToRGB(arg1, arg2, (H + 1.0 / 3.0)));
sf::Uint8 g = (255 * HueToRGB(arg1, arg2, H));
sf::Uint8 b = (255 * HueToRGB(arg1, arg2, (H - 1.0 / 3.0)));
sf::Color C(r, g, b);
return C;
}
}
HSL TurnToHSL(const sf::Color& C)
{
/// Trivial cases.
if (C == sf::Color::White) {
return HSL(0, 0, 100);
}
if (C == sf::Color::Black) {
return HSL(0, 0, 0);
}
if (C == sf::Color::Red) {
return HSL(0, 100, 50);
}
if (C == sf::Color::Yellow) {
return HSL(60, 100, 50);
}
if (C == sf::Color::Green) {
return HSL(120, 100, 50);
}
if (C == sf::Color::Cyan) {
return HSL(180, 100, 50);
}
if (C == sf::Color::Blue) {
return HSL(240, 100, 50);
}
if (C == sf::Color::Cyan) {
return HSL(300, 100, 50);
}
double R, G, B;
R = C.r / 255.0;
G = C.g / 255.0;
B = C.b / 255.0;
/// Casos no triviales.
double max, min, l, s;
/// Maximos
max = std::max(std::max(R, G), B);
/// Minimos
min = std::min(std::min(R, G), B);
HSL A;
l = ((max + min) / 2.0);
if (max - min <= D_EPSILON) {
A.Hue = 0;
A.Saturation = 0;
} else {
double diff = max - min;
if (A.Luminance < 0.5) {
s = diff / (max + min);
} else {
s = diff / (2 - max - min);
}
double diffR = (((max - R) * 60) + (diff / 2.0)) / diff;
double diffG = (((max - G) * 60) + (diff / 2.0)) / diff;
double diffB = (((max - B) * 60) + (diff / 2.0)) / diff;
if (max - R <= D_EPSILON) {
A.Hue = diffB - diffG;
} else if (max - G <= D_EPSILON) {
A.Hue = (1 * 360) / 3.0 + (diffR - diffB);
} else if (max - B <= D_EPSILON) {
A.Hue = (2 * 360) / 3.0 + (diffG - diffR);
}
if (A.Hue <= 0 || A.Hue >= 360) {
fmod(A.Hue, 360);
}
s *= 100;
}
l *= 100;
A.Saturation = s;
A.Luminance = l;
return A;
}
class ParticleSystem : public sf::Drawable, public sf::Transformable
{
public:
double hue;
ParticleSystem(unsigned int count)
: m_particles(count),
m_vertices(sf::Points, count),
m_lifetime(sf::seconds(3.f)),
m_emitter(0.f, 0.f),
hue(0)
{
}
void setEmitter(sf::Vector2f position) { m_emitter = position; }
void update(sf::Time elapsed)
{
hue += 0.5;
for (std::size_t i = 0; i < m_particles.size(); ++i) {
// update the particle lifetime
Particle& p = m_particles[i];
p.lifetime -= elapsed;
// if the particle is dead, respawn it
if (p.lifetime <= sf::Time::Zero) resetParticle(i);
// update the position of the corresponding vertex
m_vertices[i].position += p.velocity * elapsed.asSeconds();
// update the alpha (transparency) of the particle according to its
// lifetime
float ratio = p.lifetime.asSeconds() / m_lifetime.asSeconds();
m_vertices[i].color = HSL(p.hue, 140, 50).TurnToRGB();
m_vertices[i].color.a = static_cast<sf::Uint8>(ratio * 255);
}
}
private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
{
// apply the transform
states.transform *= getTransform();
// our particles don't use a texture
states.texture = NULL;
// draw the vertex array
target.draw(m_vertices, states);
}
private:
struct Particle {
sf::Vector2f velocity;
sf::Time lifetime;
float hue;
};
void resetParticle(std::size_t index)
{
// give a random velocity and lifetime to the particle
float angle = ((float)std::rand() / RAND_MAX) * 2.0 * 3.14f;
float speed = (std::rand() % 200) + 50.f;
m_particles[index].velocity =
sf::Vector2f(std::cos(angle) * speed, std::sin(angle) * speed);
m_particles[index].lifetime =
sf::milliseconds((std::rand() % 5000) + 500);
m_particles[index].hue = hue;
// reset the position of the corresponding vertex
m_vertices[index].position = m_emitter;
}
std::vector<Particle> m_particles;
sf::VertexArray m_vertices;
sf::Time m_lifetime;
sf::Vector2f m_emitter;
};
int main()
{
// create the window
sf::RenderWindow window(sf::VideoMode(1000, 1000), "Particles");
// create the particle system
ParticleSystem particles(100000);
// create a clock to track the elapsed time
sf::Clock clock;
// run the main loop
while (window.isOpen()) {
// handle events
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) window.close();
}
// make the particle system emitter follow the mouse
sf::Vector2i mouse = sf::Mouse::getPosition(window);
particles.setEmitter(window.mapPixelToCoords(mouse));
// update it
sf::Time elapsed = clock.restart();
particles.update(elapsed);
// draw it
window.clear();
window.draw(particles);
window.display();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment