Particle fountain: Difference between revisions
Content added Content deleted
(Added Wren) |
(Added C++ solution) |
||
Line 14: | Line 14: | ||
[https://github.com/thundergnat/rc/blob/master/img/fountain-raku.mp4?raw=true Off-site link to a demo video] |
[https://github.com/thundergnat/rc/blob/master/img/fountain-raku.mp4?raw=true Off-site link to a demo video] |
||
=={{header|C++}}== |
|||
{{libheader|SDL}} |
|||
{{trans|Raku}} |
|||
<lang cpp>#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_; |
|||
int particles_; |
|||
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), particles_(n), 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; |
|||
}</lang> |
|||
=={{header|Julia}}== |
=={{header|Julia}}== |