237 lines
6.7 KiB
C++
237 lines
6.7 KiB
C++
#include <SDL2/SDL.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <random>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
auto now() {
|
|
using namespace std::chrono;
|
|
auto time = system_clock::now();
|
|
return duration_cast<milliseconds>(time.time_since_epoch()).count();
|
|
}
|
|
|
|
auto hsv_to_rgb(int h, double s, double v) {
|
|
double hp = h / 60.0;
|
|
double c = s * v;
|
|
double x = c * (1 - std::abs(std::fmod(hp, 2) - 1));
|
|
double m = v - c;
|
|
double r = 0, g = 0, b = 0;
|
|
if (hp <= 1) {
|
|
r = c;
|
|
g = x;
|
|
} else if (hp <= 2) {
|
|
r = x;
|
|
g = c;
|
|
} else if (hp <= 3) {
|
|
g = c;
|
|
b = x;
|
|
} else if (hp <= 4) {
|
|
g = x;
|
|
b = c;
|
|
} else if (hp <= 5) {
|
|
r = x;
|
|
b = c;
|
|
} else {
|
|
r = c;
|
|
b = x;
|
|
}
|
|
r += m;
|
|
g += m;
|
|
b += m;
|
|
return std::make_tuple(Uint8(r * 255), Uint8(g * 255), Uint8(b * 255));
|
|
}
|
|
|
|
class ParticleFountain {
|
|
public:
|
|
ParticleFountain(int particles, int width, int height);
|
|
void run();
|
|
|
|
private:
|
|
struct WindowDeleter {
|
|
void operator()(SDL_Window* window) const { SDL_DestroyWindow(window); }
|
|
};
|
|
struct RendererDeleter {
|
|
void operator()(SDL_Renderer* renderer) const {
|
|
SDL_DestroyRenderer(renderer);
|
|
}
|
|
};
|
|
struct PointInfo {
|
|
double x = 0;
|
|
double y = 0;
|
|
double vx = 0;
|
|
double vy = 0;
|
|
double lifetime = 0;
|
|
};
|
|
|
|
void update(double df);
|
|
bool handle_event();
|
|
void render();
|
|
double rand() { return dist_(rng_); }
|
|
double reciprocate() const {
|
|
return reciprocate_ ? range_ * std::sin(now() / 1000.0) : 0.0;
|
|
}
|
|
|
|
std::unique_ptr<SDL_Window, WindowDeleter> window_;
|
|
std::unique_ptr<SDL_Renderer, RendererDeleter> renderer_;
|
|
int width_;
|
|
int height_;
|
|
std::vector<PointInfo> point_info_;
|
|
std::vector<SDL_Point> points_;
|
|
int num_points_ = 0;
|
|
double saturation_ = 0.4;
|
|
double spread_ = 1.5;
|
|
double range_ = 1.5;
|
|
bool reciprocate_ = false;
|
|
std::mt19937 rng_;
|
|
std::uniform_real_distribution<> dist_;
|
|
};
|
|
|
|
ParticleFountain::ParticleFountain(int n, int width, int height)
|
|
: width_(width), height_(height), point_info_(n), points_(n, {0, 0}),
|
|
rng_(std::random_device{}()), dist_(0.0, 1.0) {
|
|
window_.reset(SDL_CreateWindow(
|
|
"C++ Particle System!", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
width, height, SDL_WINDOW_RESIZABLE));
|
|
if (window_ == nullptr)
|
|
throw std::runtime_error(SDL_GetError());
|
|
|
|
renderer_.reset(
|
|
SDL_CreateRenderer(window_.get(), -1, SDL_RENDERER_ACCELERATED));
|
|
if (renderer_ == nullptr)
|
|
throw std::runtime_error(SDL_GetError());
|
|
}
|
|
|
|
void ParticleFountain::run() {
|
|
for (double df = 0.0001;;) {
|
|
auto start = now();
|
|
if (!handle_event())
|
|
break;
|
|
update(df);
|
|
render();
|
|
df = (now() - start) / 1000.0;
|
|
}
|
|
}
|
|
|
|
void ParticleFountain::update(double df) {
|
|
int pointidx = 0;
|
|
for (PointInfo& point : point_info_) {
|
|
bool willdraw = false;
|
|
if (point.lifetime <= 0.0) {
|
|
if (rand() < df) {
|
|
point.lifetime = 2.5;
|
|
point.x = width_ / 20.0;
|
|
point.y = height_ / 10.0;
|
|
point.vx =
|
|
(spread_ * rand() - spread_ / 2 + reciprocate()) * 10.0;
|
|
point.vy = (rand() - 2.9) * height_ / 20.5;
|
|
willdraw = true;
|
|
}
|
|
} else {
|
|
if (point.y > height_ / 10.0 && point.vy > 0)
|
|
point.vy *= -0.3;
|
|
point.vy += (height_ / 10.0) * df;
|
|
point.x += point.vx * df;
|
|
point.y += point.vy * df;
|
|
point.lifetime -= df;
|
|
willdraw = true;
|
|
}
|
|
if (willdraw) {
|
|
points_[pointidx].x = std::floor(point.x * 10.0);
|
|
points_[pointidx].y = std::floor(point.y * 10.0);
|
|
++pointidx;
|
|
}
|
|
}
|
|
num_points_ = pointidx;
|
|
}
|
|
|
|
bool ParticleFountain::handle_event() {
|
|
bool result = true;
|
|
SDL_Event event;
|
|
while (result && SDL_PollEvent(&event)) {
|
|
switch (event.type) {
|
|
case SDL_QUIT:
|
|
result = false;
|
|
break;
|
|
case SDL_WINDOWEVENT:
|
|
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
|
width_ = event.window.data1;
|
|
height_ = event.window.data2;
|
|
}
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
switch (event.key.keysym.scancode) {
|
|
case SDL_SCANCODE_UP:
|
|
saturation_ = std::min(saturation_ + 0.1, 1.0);
|
|
break;
|
|
case SDL_SCANCODE_DOWN:
|
|
saturation_ = std::max(saturation_ - 0.1, 0.0);
|
|
break;
|
|
case SDL_SCANCODE_PAGEUP:
|
|
spread_ = std::min(spread_ + 0.1, 5.0);
|
|
break;
|
|
case SDL_SCANCODE_PAGEDOWN:
|
|
spread_ = std::max(spread_ - 0.1, 0.2);
|
|
break;
|
|
case SDL_SCANCODE_RIGHT:
|
|
range_ = std::min(range_ + 0.1, 2.0);
|
|
break;
|
|
case SDL_SCANCODE_LEFT:
|
|
range_ = std::max(range_ - 0.1, 0.1);
|
|
break;
|
|
case SDL_SCANCODE_SPACE:
|
|
reciprocate_ = !reciprocate_;
|
|
break;
|
|
case SDL_SCANCODE_Q:
|
|
result = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ParticleFountain::render() {
|
|
SDL_Renderer* renderer = renderer_.get();
|
|
SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xff);
|
|
SDL_RenderClear(renderer);
|
|
auto [red, green, blue] = hsv_to_rgb((now() % 5) * 72, saturation_, 1);
|
|
SDL_SetRenderDrawColor(renderer, red, green, blue, 0x7f);
|
|
SDL_RenderDrawPoints(renderer, points_.data(), num_points_);
|
|
SDL_RenderPresent(renderer);
|
|
}
|
|
|
|
int main() {
|
|
std::cout << "Use UP and DOWN arrow keys to modify the saturation of the "
|
|
"particle colors.\n"
|
|
"Use PAGE UP and PAGE DOWN keys to modify the \"spread\" of "
|
|
"the particles.\n"
|
|
"Toggle reciprocation off / on with the SPACE bar.\n"
|
|
"Use LEFT and RIGHT arrow keys to modify angle range for "
|
|
"reciprocation.\n"
|
|
"Press the \"q\" key to quit.\n";
|
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
|
std::cerr << "ERROR: " << SDL_GetError() << '\n';
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
try {
|
|
ParticleFountain pf(3000, 800, 800);
|
|
pf.run();
|
|
} catch (const std::exception& ex) {
|
|
std::cerr << "ERROR: " << ex.what() << '\n';
|
|
SDL_Quit();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
SDL_Quit();
|
|
return EXIT_SUCCESS;
|
|
}
|