Particle fountain: Difference between revisions

From Rosetta Code
Content added Content deleted
No edit summary
(New post.)
 
(29 intermediate revisions by 11 users not shown)
Line 1: Line 1:
{{draft task}}
{{task}}


Implement a particle fountain.
Implement a particle fountain.


Emulate a fountain of water droplets in a gravitational field bring sprayed up and then falling back down.
Emulate a fountain of water droplets in a gravitational field being sprayed up and then falling back down.


The particle fountain should be generally ordered but individually chaotic; the particles should being going mostly in the same direction, but should have slightly different vectors.
The particle fountain should be generally ordered but individually chaotic; the particles should be going mostly in the same direction, but should have slightly different vectors.


Your fountain should have at least several hundred particles in motion at any one time, and ideally several thousand.
Your fountain should have at least several hundred particles in motion at any one time, and ideally several thousand.
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}}
<syntaxhighlight 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_;
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;
}</syntaxhighlight>

=={{header|EasyLang}}==
[https://easylang.dev/show/#cod=ZVHLDsIgELzzFZN48RErVKtpjF9iOGCphthCQrSxf+8uttVEDrDMzO4ObDQWJ8hM5YXwFO2llGIG0dQer7OGxxEc9xwntPuFuw9+MdX9FsPTW+wOpahCEyLKshTBw3jXmkctAFwJddRE4RGwzRmi5TxbmPOxglqgDVZRzQ/5OhOuiS8ksdF4G9rrwPUj57DBbgC7KWM+qLGm9xULLOmYVFNu/i2bFIoVGW9VU5v4Z3s01oauZneabegBrFykLKpnv/Y1VqfkStOf9eP9JynFWPMUJE0htc/EGw== Run it]

<syntaxhighlight>
rad = 0.125
n = 6000
#
len x[] n ; len y[] n
len vx[] n ; len vy[] n
background 479
color 999
on animate
for i = 1 to 32
ind = (ind + 1) mod1 n
x[ind] = 50 + randomf
y[ind] = i / 4
vx[ind] = (randomf - 0.5) * 0.4
vy[ind] = 2 + randomf * 0.1
.
clear
for i = 1 to n
move x[i] y[i]
circle rad
x[i] += vx[i] ; y[i] += vy[i]
vy[i] -= 0.025
.
.
</syntaxhighlight>

=={{header|Java}}==
<syntaxhighlight lang="java">
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;

import javax.swing.JFrame;

public final class ParticleFountainTask {

public static void main(String[] args) {
EventQueue.invokeLater( () -> {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Particle Fountain");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
ParticleFountain particleFountain = new ParticleFountain(3_000, 1_000, 750);
frame.add(particleFountain);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
particleFountain.start();
} );
}

private static final class ParticleFountain extends Canvas {

public ParticleFountain(int aParticleCount, int aWidth, int aHeight) {
particleCount = aParticleCount;
width = aWidth;
height = aHeight;
saturation = 0.6;
spread = 1.5;
range = 1.5;
reciprocate = false;
setPreferredSize( new Dimension(width, height) );
addKeyListener( new InputHandler() );
executorService = Executors.newSingleThreadExecutor();
}

public void start() {
requestFocus();
createBufferStrategy(2);
executorService.execute( new DrawingCycle() );
}

private final class DrawingCycle implements Runnable {
public DrawingCycle() {
positions = new double[2 * particleCount];
velocities = new double[2 * particleCount];
lifetimes = new double[particleCount];
points = new Point[particleCount];
Arrays.fill(points, new Point(0, 0) );
random = ThreadLocalRandom.current();
}

@Override
public void run() {
bufferStrategy = getBufferStrategy();
while ( true ) {
update(0.005);
draw();
}
}

private void update(double animationSpeed) {
int xIndex = 0;
int yIndex = 1;
pointIndex = 0;

for ( int index = 0; index < particleCount; index++ ) {
boolean showParticle = false;
if ( lifetimes[index] <= 0.0 ) {
if ( random.nextDouble() < animationSpeed ) {
lifetimes[index] = 2.5;
positions[xIndex] = width / 20;
positions[yIndex] = height / 10;
velocities[xIndex] =
10 * ( spread * random.nextDouble() - spread / 2 + additionalXSpeed() );
velocities[yIndex] = ( random.nextDouble() - 2.9 ) * height / 20.5;
showParticle = true;
}
} else {
if ( positions[yIndex] > height / 10 && velocities[yIndex] > 0 ) {
velocities[yIndex] *= -0.3; // bounce particle
}
velocities[yIndex] += animationSpeed * height / 10;
positions[xIndex] += velocities[xIndex] * animationSpeed;
positions[yIndex] += velocities[yIndex] * animationSpeed;
lifetimes[index] -= animationSpeed;
showParticle = true;
}

if ( showParticle ) {
points[pointIndex] = new Point((int) ( positions[xIndex] * 10 ),
(int) ( positions[yIndex] * 10 ));
pointIndex += 1;
}
xIndex += 2;
yIndex = xIndex + 1;
}
}

private void draw() {
Graphics2D graphics2D = (Graphics2D) bufferStrategy.getDrawGraphics();
graphics2D.setColor(Color.BLACK);
graphics2D.fillRect(0, 0, getWidth(), getHeight());
for ( int i = 0; i < pointIndex; i++ ) {
graphics2D.setColor(Color.getHSBColor(random.nextFloat(), (float) saturation, 1.0F));
graphics2D.fillOval(points[i].x, points[i].y, 5, 5);
}
graphics2D.dispose();
bufferStrategy.show();
}
private double additionalXSpeed() {
return ( reciprocate ) ? range * Math.sin(System.currentTimeMillis() / 1_000) : 0.0;
}
private double[] positions;
private double[] velocities;
private double[] lifetimes;
private int pointIndex;
private Point[] points;
private BufferStrategy bufferStrategy;
private ThreadLocalRandom random;

} // End DrawingCycle class
private final class InputHandler extends KeyAdapter {
@Override
public void keyPressed(KeyEvent aKeyEvent) {
final int keyCode = aKeyEvent.getKeyCode();
switch ( keyCode ) {
case KeyEvent.VK_UP -> saturation = Math.min(saturation + 0.1, 1.0);
case KeyEvent.VK_DOWN -> saturation = Math.max(saturation - 0.1, 0.0);
case KeyEvent.VK_PAGE_UP -> spread = Math.min(spread + 0.1, 5.0);
case KeyEvent.VK_PAGE_DOWN -> spread = Math.max(spread - 0.1, 0.5);
case KeyEvent.VK_RIGHT -> range = Math.min(range + 0.1, 2.0);
case KeyEvent.VK_LEFT -> range = Math.max(range + 0.1, 0.1);
case KeyEvent.VK_SPACE -> reciprocate = ! reciprocate;
case KeyEvent.VK_Q -> Runtime.getRuntime().exit(0);
default -> { /* Take no action */ }
}
}
} // End InputHandler class
private int particleCount;
private int width;
private int height;
private double saturation;
private double spread;
private double range;
private boolean reciprocate;
private ExecutorService executorService;
} // End ParticleFountain class

} // End ParticleFountainTask class
</syntaxhighlight>

=={{header|Julia}}==
{{trans|Raku}}
<syntaxhighlight lang="julia">using Dates, Colors, SimpleDirectMediaLayer.LibSDL2

mutable struct ParticleFountain
particlenum::Int
positions::Vector{Float64}
velocities::Vector{Float64}
lifetimes::Vector{Float64}
points::Vector{SDL_Point}
numpoints::Int
saturation::Float64
spread::Float64
range::Float64
reciprocate::Bool
ParticleFountain(N) = new(N, zeros(2N), zeros(2N), zeros(N), fill(SDL_Point(0, 0), N),
0, 0.4, 1.5, 1.5, false)
end

function update(pf, w, h, df)
xidx, yidx, pointidx = 1, 2, 0
recip() = pf.reciprocate ? pf.range * sin(Dates.value(now()) / 1000) : 0.0
for idx in 1:pf.particlenum
willdraw = false
if pf.lifetimes[idx] <= 0.0
if rand() < df
pf.lifetimes[idx] = 2.5; # time to live
pf.positions[xidx] = (w / 20) # starting position x
pf.positions[yidx] = (h / 10) # and y
pf.velocities[xidx] = 10 * (pf.spread * rand() - pf.spread / 2 + recip()) # starting velocity x
pf.velocities[yidx] = (rand() - 2.9) * h / 20.5; # and y (randomized slightly so points reach different heights)
willdraw = true
end
else
if pf.positions[yidx] > h / 10 && pf.velocities[yidx] > 0
pf.velocities[yidx] *= -0.3 # "bounce"
end
pf.velocities[yidx] += df * h / 10 # adjust velocity
pf.positions[xidx] += pf.velocities[xidx] * df # adjust position x
pf.positions[yidx] += pf.velocities[yidx] * df # and y
pf.lifetimes[idx] -= df
willdraw = true
end

if willdraw # gather all of the points that are going to be rendered
pointidx += 1
pf.points[pointidx] = SDL_Point(Cint(floor(pf.positions[xidx] * 10)),
Cint(floor(pf.positions[yidx] * 10)))
end
xidx += 2
yidx = xidx + 1
pf.numpoints = pointidx
end
return pf
end

function fountain(particlenum = 3000, w = 800, h = 800)
SDL_Init(SDL_INIT_VIDEO)
window = SDL_CreateWindow("Julia Particle System!", SDL_WINDOWPOS_CENTERED_MASK,
SDL_WINDOWPOS_CENTERED_MASK, w, h, SDL_WINDOW_RESIZABLE)
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)
SDL_ClearError()
df = 0.0001
pf = ParticleFountain(3000)
overallstart, close, frames = now(), false, 0
while !close
dfstart = now()
event_ref = Ref{SDL_Event}()
while Bool(SDL_PollEvent(event_ref))
event_type = event_ref[].type
evt = event_ref[]
if event_type == SDL_QUIT
close = true
break
end
if event_type == SDL_WINDOWEVENT
if evt.window.event == 5
w = evt.window.data1
h = evt.window.data2
end
end
if event_type == SDL_KEYDOWN
comm = evt.key.keysym.scancode
if comm == SDL_SCANCODE_UP
saturation = min(pf.saturation + 0.1, 1.0)
elseif comm == SDL_SCANCODE_DOWN
saturation = max(pf.saturation - 0.1, 0.0)
elseif comm == SDL_SCANCODE_PAGEUP
spread = min(pf.spread + 1, 50.0)
elseif comm == SDL_SCANCODE_PAGEDOWN
spread = max(pf.spread - 0.1, 0.2)
elseif comm == SDL_SCANCODE_LEFT
range = min(pf.range + 0.1, 12.0)
elseif comm == SDL_SCANCODE_RIGHT
range = max(pf.range - 0.1, 0.1)
elseif comm == SDL_SCANCODE_SPACE
pf.reciprocate = !pf.reciprocate
elseif comm == SDL_SCANCODE_Q
close = true
break
end
end
end
pf = update(pf, w, h, df)
SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xff)
SDL_RenderClear(renderer)
rgb = parse(UInt32, hex(HSL((Dates.value(now()) % 5) * 72, pf.saturation, 0.5)), base=16)
red, green, blue = rgb & 0xff, (rgb >> 8) & 0xff, (rgb >>16) & 0xff
SDL_SetRenderDrawColor(renderer, red, green, blue, 0x7f)
SDL_RenderDrawPoints(renderer, pf.points, pf.numpoints)
SDL_RenderPresent(renderer)
frames += 1
df = Float64(Dates.value(now()) - Dates.value(dfstart)) / 1000
elapsed = Float64(Dates.value(now()) - Dates.value(overallstart)) / 1000
elapsed > 0.5 && print("\r", ' '^20, "\rFPS: ", round(frames / elapsed, digits=1))
end
SDL_Quit()
end

println("""
Use UP and DOWN arrow keys to modify the saturation of the particle colors.
Use PAGE UP and PAGE DOWN keys to modify the "spread" of the particles.
Toggle reciprocation off / on with the SPACE bar.
Use LEFT and RIGHT arrow keys to modify angle range for reciprocation.
Press the "q" key to quit.
""")

fountain()
</syntaxhighlight>

=={{header|Lua}}==
{{libheader|LÖVE}}
[https://github.com/offlinesoftware/luafountain/blob/main/fountain.m4v?raw=true Video is here]
<syntaxhighlight lang="lua">-- Returns canvas of given width and height containing a circle
function initCanvas (width, height)
local c = love.graphics.newCanvas(width, height)
love.graphics.setCanvas(c) -- Switch to drawing on canvas 'c'
love.graphics.circle("fill", width / 2, height / 2, 2, 100)
love.graphics.setCanvas() -- Switch back to drawing on main screen
return c
end

-- Returns particle system with given canvas
function initPartSys (image, maxParticles)
local ps = love.graphics.newParticleSystem(image, maxParticles)
ps:setParticleLifetime(3, 5) -- (min, max)
ps:setDirection(math.pi * 1.5)
ps:setSpeed(700)
ps:setLinearAcceleration(-100, 500, 100, 700) -- (minX, minY, maxX, maxY)
ps:setEmissionRate(1000)
ps:setPosition(400, 550)
ps:setColors(1, 1, 1, 1, 0, 0, 1, 0) -- Start solid white, fade to transluscent blue
return ps
end

-- LÖVE callback that runs on program start
function love.load ()
love.window.setTitle("Lua particle fountain")
local canvas = initCanvas(10, 10)
psystem = initPartSys(canvas, 10000)
end

-- LÖVE callback to update values before each frame
function love.update (dt)
psystem:update(dt)
end

-- LÖVE callback to draw each frame to the screen
function love.draw ()
love.graphics.draw(psystem)
end</syntaxhighlight>

=={{header|Nim}}==
{{trans|Julia}}
{{libheader|SDL2}}
Note that for key events, the scan code doesn’t take in account the keyboard layout. So we check the "sym" value instead.
<syntaxhighlight lang="Nim">import std/[lenientops, math, monotimes, random, times]
import sdl2

type ParticleFountain[N: static Positive] = object
positions: array[1..2 * N, float]
velocities: array[1..2 * N, float]
lifetimes: array[1..N, float]
points: array[1..N, Point]
numPoints: int
saturation: float
spread: float
range: float
reciprocate: bool

proc initParticleFountain[N: static Positive](): ParticleFountain[N] =
ParticleFountain[N](saturation: 0.4, spread: 1.5, range: 1.5)

proc update(pf: var ParticleFountain; w, h: cint; df: float) =
var
xidx = 1
yidx = 2
pointidx = 0

template recip(pf: ParticleFountain): float =
if pf.reciprocate: pf.range * sin(epochTime() / 1000) else: 0.0

for idx in 1..pf.N:
var willDraw = false
if pf.lifetimes[idx] <= 0:
if rand(1.0) < df:
pf.lifetimes[idx] = 2.5 # Time to live.
# Starting position.
pf.positions[xidx] = w / 20
pf.positions[yidx] = h / 10
# Starting velocity.
pf.velocities[xidx] = 10 * (pf.spread * rand(1.0) - pf.spread / 2 + pf.recip())
pf.velocities[yidx] = (rand(1.0) - 2.9) * h / 20.5
willDraw = true
else:
if pf.positions[yidx] > h / 10 and pf.velocities[yidx] > 0:
pf.velocities[yidx] *= -0.3 # "Bounce".
pf.velocities[yidx] += df * h / 10 # Adjust velocity.
pf.positions[xidx] += pf.velocities[xidx] * df # Adjust position x.
pf.positions[yidx] += pf.velocities[yidx] * df # Adjust position y.
pf.lifetimes[idx] -= df
willDraw = true

if willDraw:
# Gather all of the points that are going to be rendered.
inc pointIdx
pf.points[pointidx] = (cint(pf.positions[xidx] * 10), cint(pf.positions[yidx] * 10))
inc xidx, 2
yidx = xidx + 1
pf.numPoints = pointidx

func hsvToRgb(h, s, v: float): (byte, byte, byte) =
let hp = h / 60.0
let c = s * v
let x = c * (1 - abs(hp mod 2 - 1))
let m = v - c
var (r, g, b) = if hp <= 1: (c, x, 0.0)
elif hp <= 2: (x, c, 0.0)
elif hp <= 3: (0.0, c, x)
elif hp <= 4: (0.0, x, c)
elif hp <= 5: (x, 0.0, c)
else: (c, 0.0, x)
r += m
g += m
b += m
result = (byte(r * 255), byte(g * 255), byte(b * 255))

proc fountain(particleNum = 3000; w = 800; h = 800) =
var w = w.cint
var h = h.cint
discard sdl2.init(INIT_VIDEO or INIT_EVENTS)
let window = createWindow("Nim Particle System!", SDL_WINDOWPOS_CENTERED_MASK,
SDL_WINDOWPOS_CENTERED_MASK, w, h, SDL_WINDOW_RESIZABLE)
let renderer = createRenderer(window, -1, 0)
clearError()
var df = 0.0001
var pf = initParticleFountain[3000]()
var close = false
var frames = 0
block Simulation:
while not close:
let dfStart = getMonoTime()
var event: Event
while bool(pollEvent(event)):
case event.kind
of QuitEvent:
break Simulation
of WindowEvent:
if event.window.event == WindowEvent_Resized:
w = event.window.data1
h = event.window.data2
of KeyDown:
let comm = event.key.keysym.sym
case comm
of K_UP:
pf.saturation = min(pf.saturation + 0.1, 1.0)
of K_DOWN:
pf.saturation = max(pf.saturation - 0.1, 0.0)
of K_PAGEUP:
pf.spread = min(pf.spread + 1.0, 50.0)
of K_PAGEDOWN:
pf.spread = max(pf.spread - 0.1, 0.2)
of K_LEFT:
pf.range = min(pf.range + 0.1, 12.0)
of K_RIGHT:
pf.range = max(pf.range - 0.1, 0.1)
of K_SPACE:
pf.reciprocate = not pf.reciprocate
of K_Q:
break Simulation
else:
discard
else:
discard

pf.update(w, h, df)
renderer.setDrawColor(0x0, 0x0, 0x0, 0xff)
renderer.clear()
let (red, green, blue) = hsvToRgb(epochTime() mod 5 * 72, pf.saturation, 1.0)
renderer.setDrawColor(red, green, blue, 0x7f)
renderer.drawPoints(pf.points[1].addr, pf.numPoints.cint)
renderer.present()
inc frames
df = (getMonoTime() - dfStart).inMilliseconds.float / 1000

sdl2.quit()

randomize()
echo """
Use UP and DOWN arrow keys to modify the saturation of the particle colors.
Use PAGE UP and PAGE DOWN keys to modify the "spread" of the particles.
Toggle reciprocation off / on with the SPACE bar.
Use LEFT and RIGHT arrow keys to modify angle range for reciprocation.
Press the "q" key to quit.
"""
fountain()
</syntaxhighlight>

=={{header|Perl}}==
=={{header|Perl}}==
<lang perl>#!/usr/bin/perl
<syntaxhighlight lang="perl">#!/usr/bin/perl


use strict; # https://rosettacode.org/wiki/Particle_fountain
use strict; # https://rosettacode.org/wiki/Particle_fountain
Line 63: Line 833:
}
}
$mw->after(1 => \&step);
$mw->after(1 => \&step);
}</lang>
}</syntaxhighlight>

=={{header|Phix}}==
{{trans|Raku}}
{{libheader|Phix/pGUI}}
{{libheader|Phix/online}}
You can run this online [http://phix.x10.mx/p2js/Particle_fountain.htm here].
<!--<syntaxhighlight lang="phix">(phixonline)-->
<span style="color: #000080;font-style:italic;">--
-- demo\rosetta\Particle_fountain.exw
-- ==================================
--</span>
<span style="color: #008080;">with</span> <span style="color: #008080;">javascript_semantics</span>
<span style="color: #008080;">include</span> <span style="color: #000000;">pGUI</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span>
<span style="color: #004080;">Ihandle</span> <span style="color: #000000;">dlg</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">canvas</span>
<span style="color: #004080;">cdCanvas</span> <span style="color: #000000;">cddbuffer</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">cdcanvas</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">title</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"Particle fountain"</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">help_text</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"""
Uparrow increases the saturation of the particle colors,
downarrow decreases saturation until they all become white.
PageUp sprays the particles out at a wider angle/spread,
PageDown makes the jet narrower.
Space toggles reciprocation (wobble) on and off (straight up).
Left arrow decreases the angle range for reciprocation,
right arrow increases the angle range for reciprocation.
Press the "q" key to quit.
"""</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">particlenum</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">3000</span>
<span style="color: #000080;font-style:italic;">-- each particle is {x,y,color,life,dx,dy}</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">particles</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">repeat</span><span style="color: #0000FF;">({</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">},</span><span style="color: #000000;">particlenum</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">atom</span> <span style="color: #000000;">t1</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">time</span><span style="color: #0000FF;">()+</span><span style="color: #000000;">1</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">fps</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span>
<span style="color: #004080;">bool</span> <span style="color: #000000;">reciprocate</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">true</span>
<span style="color: #004080;">atom</span> <span style="color: #000000;">range</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">1.5</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">spread</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">1.5</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">saturation</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0.4</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">start</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">time</span><span style="color: #0000FF;">(),</span>
<span style="color: #000000;">df</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0.0001</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">redraw_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*ih*/</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">integer</span> <span style="color: #000080;font-style:italic;">/*posx*/</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">/*posy*/</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">w</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">h</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupGetIntInt</span><span style="color: #0000FF;">(</span><span style="color: #000000;">canvas</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"DRAWSIZE"</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">cdCanvasActivate</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cddbuffer</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">cdCanvasClear</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cddbuffer</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">particles</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">atom</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">x</span><span style="color: #0000FF;">,</span><span style="color: #000000;">y</span><span style="color: #0000FF;">,</span><span style="color: #000000;">color</span><span style="color: #0000FF;">,</span><span style="color: #000000;">life</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">particles</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">life</span><span style="color: #0000FF;">></span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">cdCanvasPixel</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cddbuffer</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">x</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">h</span><span style="color: #0000FF;">/</span><span style="color: #000000;">10</span><span style="color: #0000FF;">-</span><span style="color: #000000;">y</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">color</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #7060A8;">cdCanvasFlush</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cddbuffer</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">map_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000000;">ih</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">cdcanvas</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">cdCreateCanvas</span><span style="color: #0000FF;">(</span><span style="color: #004600;">CD_IUP</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">ih</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">cddbuffer</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">cdCreateCanvas</span><span style="color: #0000FF;">(</span><span style="color: #004600;">CD_DBUFFER</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">cdcanvas</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">cdCanvasSetBackground</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cddbuffer</span><span style="color: #0000FF;">,</span> <span style="color: #004600;">CD_BLACK</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">timer_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*ih*/</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">w</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">h</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupGetIntInt</span><span style="color: #0000FF;">(</span><span style="color: #000000;">canvas</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"DRAWSIZE"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">fps</span> <span style="color: #0000FF;">+=</span> <span style="color: #000000;">1</span>
<span style="color: #000000;">df</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">time</span><span style="color: #0000FF;">()-</span><span style="color: #000000;">start</span>
<span style="color: #000000;">start</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">time</span><span style="color: #0000FF;">()</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #000000;">particlenum</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">atom</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">x</span><span style="color: #0000FF;">,</span><span style="color: #000000;">y</span><span style="color: #0000FF;">,</span><span style="color: #000000;">color</span><span style="color: #0000FF;">,</span><span style="color: #000000;">life</span><span style="color: #0000FF;">,</span><span style="color: #000000;">dx</span><span style="color: #0000FF;">,</span><span style="color: #000000;">dy</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">particles</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">life</span><span style="color: #0000FF;"><=</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">rnd</span><span style="color: #0000FF;">()<</span><span style="color: #000000;">df</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">life</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">2.5</span> <span style="color: #000080;font-style:italic;">-- time to live</span>
<span style="color: #000000;">x</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">w</span><span style="color: #0000FF;">/</span><span style="color: #000000;">2</span> <span style="color: #000080;font-style:italic;">-- starting position x</span>
<span style="color: #000000;">y</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">h</span><span style="color: #0000FF;">/</span><span style="color: #000000;">10</span> <span style="color: #000080;font-style:italic;">-- and y
-- randomize velocity so points reach different heights:</span>
<span style="color: #004080;">atom</span> <span style="color: #000000;">r</span> <span style="color: #0000FF;">=</span> <span style="color: #008080;">iff</span><span style="color: #0000FF;">(</span><span style="color: #000000;">reciprocate</span><span style="color: #0000FF;">?</span><span style="color: #000000;">range</span><span style="color: #0000FF;">*</span><span style="color: #7060A8;">sin</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">time</span><span style="color: #0000FF;">()):</span><span style="color: #000000;">0</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">dx</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">(</span><span style="color: #000000;">spread</span><span style="color: #0000FF;">*</span><span style="color: #7060A8;">rnd</span><span style="color: #0000FF;">()-</span><span style="color: #000000;">spread</span><span style="color: #0000FF;">/</span><span style="color: #000000;">2</span><span style="color: #0000FF;">+</span><span style="color: #000000;">r</span><span style="color: #0000FF;">)*</span><span style="color: #000000;">50</span> <span style="color: #000080;font-style:italic;">-- starting velocity x</span>
<span style="color: #000000;">dy</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">(</span><span style="color: #7060A8;">rnd</span><span style="color: #0000FF;">()-</span><span style="color: #000000;">2.9</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">*</span> <span style="color: #000000;">h</span><span style="color: #0000FF;">/</span><span style="color: #000000;">20.5</span> <span style="color: #000080;font-style:italic;">-- and y </span>
<span style="color: #000000;">color</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">hsv_to_rgb</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">round</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">remainder</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">time</span><span style="color: #0000FF;">(),</span><span style="color: #000000;">5</span><span style="color: #0000FF;">)/</span><span style="color: #000000;">5</span><span style="color: #0000FF;">,</span><span style="color: #000000;">100</span><span style="color: #0000FF;">),</span> <span style="color: #000000;">saturation</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">1</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">else</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">y</span><span style="color: #0000FF;">></span><span style="color: #000000;">h</span><span style="color: #0000FF;">/</span><span style="color: #000000;">10</span> <span style="color: #008080;">and</span> <span style="color: #000000;">dy</span><span style="color: #0000FF;">></span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">dy</span> <span style="color: #0000FF;">*=</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">0.3</span> <span style="color: #000080;font-style:italic;">-- "bounce"</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">dy</span> <span style="color: #0000FF;">+=</span> <span style="color: #0000FF;">(</span><span style="color: #000000;">h</span><span style="color: #0000FF;">/</span><span style="color: #000000;">10</span><span style="color: #0000FF;">)*</span><span style="color: #000000;">df</span> <span style="color: #000080;font-style:italic;">-- adjust velocity</span>
<span style="color: #000000;">x</span> <span style="color: #0000FF;">+=</span> <span style="color: #000000;">dx</span><span style="color: #0000FF;">*</span><span style="color: #000000;">df</span> <span style="color: #000080;font-style:italic;">-- adjust position x</span>
<span style="color: #000000;">y</span> <span style="color: #0000FF;">+=</span> <span style="color: #000000;">dy</span><span style="color: #0000FF;">*</span><span style="color: #000000;">df</span><span style="color: #0000FF;">*</span><span style="color: #000000;">8</span> <span style="color: #000080;font-style:italic;">-- and y</span>
<span style="color: #000000;">life</span> <span style="color: #0000FF;">-=</span> <span style="color: #000000;">df</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">particles</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">x</span><span style="color: #0000FF;">,</span><span style="color: #000000;">y</span><span style="color: #0000FF;">,</span><span style="color: #000000;">color</span><span style="color: #0000FF;">,</span><span style="color: #000000;">life</span><span style="color: #0000FF;">,</span><span style="color: #000000;">dx</span><span style="color: #0000FF;">,</span><span style="color: #000000;">dy</span><span style="color: #0000FF;">}</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #7060A8;">IupRedraw</span><span style="color: #0000FF;">(</span><span style="color: #000000;">canvas</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">time</span><span style="color: #0000FF;">()></span><span style="color: #000000;">t1</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">IupSetStrAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">dlg</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"TITLE"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"%s (%d, %d fps/s [%dx%d])"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">title</span><span style="color: #0000FF;">,</span><span style="color: #000000;">particlenum</span><span style="color: #0000FF;">,</span><span style="color: #000000;">fps</span><span style="color: #0000FF;">,</span><span style="color: #000000;">w</span><span style="color: #0000FF;">,</span><span style="color: #000000;">h</span><span style="color: #0000FF;">})</span>
<span style="color: #000000;">t1</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">time</span><span style="color: #0000FF;">()+</span><span style="color: #000000;">1</span>
<span style="color: #000000;">fps</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">key_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*dlg*/</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">atom</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_ESC</span> <span style="color: #008080;">or</span> <span style="color: #7060A8;">lower</span><span style="color: #0000FF;">(</span><span style="color: #000000;">c</span><span style="color: #0000FF;">)=</span><span style="color: #008000;">'q'</span> <span style="color: #008080;">then</span> <span style="color: #008080;">return</span> <span style="color: #004600;">IUP_CLOSE</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_F1</span> <span style="color: #008080;">then</span> <span style="color: #7060A8;">IupMessage</span><span style="color: #0000FF;">(</span><span style="color: #000000;">title</span><span style="color: #0000FF;">,</span><span style="color: #000000;">help_text</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_UP</span> <span style="color: #008080;">then</span> <span style="color: #000000;">saturation</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">min</span><span style="color: #0000FF;">(</span><span style="color: #000000;">saturation</span><span style="color: #0000FF;">+</span><span style="color: #000000;">0.1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_DOWN</span> <span style="color: #008080;">then</span> <span style="color: #000000;">saturation</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">max</span><span style="color: #0000FF;">(</span><span style="color: #000000;">saturation</span><span style="color: #0000FF;">-</span><span style="color: #000000;">0.1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_PGUP</span> <span style="color: #008080;">then</span> <span style="color: #000000;">spread</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">min</span><span style="color: #0000FF;">(</span><span style="color: #000000;">spread</span><span style="color: #0000FF;">+</span><span style="color: #000000;">0.1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">5</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_PGDN</span> <span style="color: #008080;">then</span> <span style="color: #000000;">spread</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">max</span><span style="color: #0000FF;">(</span><span style="color: #000000;">spread</span><span style="color: #0000FF;">-</span><span style="color: #000000;">0.1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0.2</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_RIGHT</span> <span style="color: #008080;">then</span> <span style="color: #000000;">range</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">min</span><span style="color: #0000FF;">(</span><span style="color: #000000;">range</span><span style="color: #0000FF;">+</span><span style="color: #000000;">0.1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">2</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_LEFT</span> <span style="color: #008080;">then</span> <span style="color: #000000;">range</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">max</span><span style="color: #0000FF;">(</span><span style="color: #000000;">range</span><span style="color: #0000FF;">-</span><span style="color: #000000;">0.1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0.1</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_SP</span> <span style="color: #008080;">then</span> <span style="color: #000000;">reciprocate</span> <span style="color: #0000FF;">=</span> <span style="color: #008080;">not</span> <span style="color: #000000;">reciprocate</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_CONTINUE</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">main</span><span style="color: #0000FF;">()</span>
<span style="color: #7060A8;">IupOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">canvas</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupGLCanvas</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"RASTERSIZE=400x300"</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetCallbacks</span><span style="color: #0000FF;">({</span><span style="color: #000000;">canvas</span><span style="color: #0000FF;">},</span> <span style="color: #0000FF;">{</span><span style="color: #008000;">"ACTION"</span><span style="color: #0000FF;">,</span> <span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"redraw_cb"</span><span style="color: #0000FF;">),</span>
<span style="color: #008000;">"MAP_CB"</span><span style="color: #0000FF;">,</span> <span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"map_cb"</span><span style="color: #0000FF;">)})</span>
<span style="color: #000000;">dlg</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupDialog</span><span style="color: #0000FF;">(</span><span style="color: #000000;">canvas</span><span style="color: #0000FF;">,</span><span style="color: #008000;">`TITLE="%s"`</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">title</span><span style="color: #0000FF;">})</span>
<span style="color: #7060A8;">IupSetCallback</span><span style="color: #0000FF;">(</span><span style="color: #000000;">dlg</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"KEY_CB"</span><span style="color: #0000FF;">,</span> <span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"key_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #004080;">Ihandle</span> <span style="color: #000000;">timer</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupTimer</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"timer_cb"</span><span style="color: #0000FF;">),</span> <span style="color: #000000;">1000</span><span style="color: #0000FF;">/</span><span style="color: #000000;">25</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupShowXY</span><span style="color: #0000FF;">(</span><span style="color: #000000;">dlg</span><span style="color: #0000FF;">,</span><span style="color: #004600;">IUP_CENTER</span><span style="color: #0000FF;">,</span><span style="color: #004600;">IUP_CENTER</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">canvas</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"RASTERSIZE"</span><span style="color: #0000FF;">,</span> <span style="color: #004600;">NULL</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">platform</span><span style="color: #0000FF;">()!=</span><span style="color: #004600;">JS</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">IupMainLoop</span><span style="color: #0000FF;">()</span>
<span style="color: #7060A8;">IupClose</span><span style="color: #0000FF;">()</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #000000;">main</span><span style="color: #0000FF;">()</span>
<!--</syntaxhighlight>-->

=={{header|Python}}==
{{libheader|SDL}}
{{trans|C++}}

Use arrow keys, PageUp, PageDown and Space to vary fountain parameters.
<syntaxhighlight lang="python">
# Using SDL2 library: # pip install PySDL2

import sys
import random
import time
import math

import sdl2
import sdl2.ext

FPS = 60
NEW_PARTICLES_PER_FRAME = 10
MAX_PARTICLES = 5_000
GRAVITY = 100
WIDTH = 640
HEIGHT = 480


def clamp(value, min_, max_):
"""Return value clamped between min and max"""
return max(min_, min(value, max_))


class Particle:
"""Particle obeying gravity law."""

def __init__(self):
self.x = 0
self.y = 0
self.v_x = 0
self.v_y = 0

def update(self, dtime: float) -> None:
"""Move particle and update speed with gravity"""
self.x = self.x + self.v_x * dtime
self.y = self.y + self.v_y * dtime
self.v_y = self.v_y + GRAVITY * dtime

def set(self, x, y, v_x, v_y):
"""Set particle values"""
self.x = x
self.y = y
self.v_x = v_x
self.v_y = v_y


class Fountain:
"""The fountain"""

def __init__(self, max_particles: int, particles_per_frame: int):
self.particles_per_frame = particles_per_frame
self.max_particles = max_particles
self.spread = 10.0
self.range = math.sqrt(2 * GRAVITY * (HEIGHT - 20 - self.spread))
self.saturation = 155
self.reciprocate = False
self.reciprocating_time = 0.0
self.particles = [
self.init_particle(Particle()) for _ in range(self.particles_per_frame)
]

def update(self, dtime) -> None:
"""Update particles"""
if self.reciprocate:
self.reciprocating_time += dtime

for particle in self.particles:
particle.update(dtime)
if particle.y > HEIGHT - 10:
self.init_particle(particle)

if len(self.particles) < self.max_particles:
for _ in range(self.particles_per_frame):
self.particles.append(self.init_particle(Particle()))
# print(len(particles))

def render(self, renderer: sdl2.ext.renderer.Renderer) -> None:
"""Render particles"""
points = [(particle.x, particle.y) for particle in self.particles]

renderer.clear()
renderer.draw_point(
points, sdl2.ext.Color(self.saturation, self.saturation, 255)
)
renderer.present()

def step_parameter(self, param, step):
"""Change parameters"""
if param == "spread":
self.spread = clamp(self.spread + step, 0, 50)
elif param == "range":
self.range = clamp(self.range + step, 0, 300)
elif param == "color":
self.saturation = clamp(self.saturation + step, 0, 255)
elif param == "reciprocate":
self.reciprocate = not self.reciprocate
self.reciprocating_time = 0.0

def init_particle(self, particle: Particle) -> Particle:
"""Move particle at initial position with a random-y speed"""
radius = random.random() * self.spread
direction = random.random() * math.pi * 2
v_x = radius * math.cos(direction) + math.sin(self.reciprocating_time) * 20.0
v_y = -self.range + radius * math.sin(direction)
particle.set(WIDTH // 2, HEIGHT - 10, v_x, v_y)
return particle


def make_renderer() -> sdl2.ext.renderer.Renderer:
"""Initialise SDL and make renderer"""
sdl2.ext.init()

window = sdl2.ext.Window("Particle Fountain", size=(WIDTH, HEIGHT))
window.show()

renderer = sdl2.ext.renderer.Renderer(window)

return renderer


def limit_frame_rate(fps: float, cur_time: int) -> bool:
"""Limit frame rate"""
dtime = time.monotonic_ns() - cur_time
frame_duration = 1e9 / fps
if dtime < frame_duration:
time.sleep((frame_duration - dtime) / 1e9)
return True
return False


def handle_events(fountain: Fountain):
"""Act on events"""
key_actions = {
sdl2.SDL_SCANCODE_PAGEUP: lambda: fountain.step_parameter("color", 5),
sdl2.SDL_SCANCODE_PAGEDOWN: lambda: fountain.step_parameter("color", -5),
sdl2.SDL_SCANCODE_UP: lambda: fountain.step_parameter("range", 1),
sdl2.SDL_SCANCODE_DOWN: lambda: fountain.step_parameter("range", -1),
sdl2.SDL_SCANCODE_LEFT: lambda: fountain.step_parameter("spread", -1),
sdl2.SDL_SCANCODE_RIGHT: lambda: fountain.step_parameter("spread", 1),
sdl2.SDL_SCANCODE_SPACE: lambda: fountain.step_parameter("reciprocate", 1),
}

events = sdl2.ext.get_events()
for event in events:
if event.type == sdl2.SDL_QUIT:
return False
if event.type == sdl2.SDL_KEYDOWN:
if event.key.keysym.scancode in key_actions:
key_actions[event.key.keysym.scancode]()
elif event.key.keysym.scancode == sdl2.SDL_SCANCODE_Q:
return False
return True


def main_loop(renderer: sdl2.ext.renderer.Renderer, fountain: Fountain) -> None:
"""Main animation loop"""
running = True

cur_time = time.monotonic_ns()
while running:
running = handle_events(fountain)

fountain.render(renderer)

if not limit_frame_rate(FPS, cur_time):
print(f"Didn't make it in time with {len(fountain.particles)} particles.")

dtime = (time.monotonic_ns() - cur_time) / 1e9 # in seconds
fountain.update(dtime)
cur_time = time.monotonic_ns()

sdl2.ext.quit()


def run():
"""Start!"""

renderer = make_renderer()
fountain = Fountain(MAX_PARTICLES, NEW_PARTICLES_PER_FRAME)

main_loop(renderer, fountain)

return 0


if __name__ == "__main__":
sys.exit(run())

</syntaxhighlight>


=={{header|Raku}}==
=={{header|Raku}}==


Has options to vary the direction at which the fountain sprays, the "spread" angle and the color of the emitted particles.
Has options to vary the direction at which the fountain sprays, the "spread" angle and the color of the emitted particles.
<lang perl6>use NativeCall;
<syntaxhighlight lang="raku" line>use NativeCall;
use SDL2::Raw;
use SDL2::Raw;


Line 80: Line 1,178:
SDL_Init(VIDEO);
SDL_Init(VIDEO);
$window = SDL_CreateWindow(
$window = SDL_CreateWindow(
"Raku Particle System!",
"Raku Particle System!",
SDL_WINDOWPOS_CENTERED_MASK, SDL_WINDOWPOS_CENTERED_MASK,
SDL_WINDOWPOS_CENTERED_MASK, SDL_WINDOWPOS_CENTERED_MASK,
$w, $h,
$w, $h,
RESIZABLE
RESIZABLE
);
);
$renderer = SDL_CreateRenderer( $window, -1, ACCELERATED );
$renderer = SDL_CreateRenderer( $window, -1, ACCELERATED );
Line 101: Line 1,199:


sub update (num \df) {
sub update (num \df) {
my int $xidx = 0;
my int $xidx = 0;
my int $yidx = 1;
my int $yidx = 1;
my int $pointidx = 0;
my int $pointidx = 0;
loop (my int $idx = 0; $idx < $particlenum; $idx = $idx + 1) {
loop (my int $idx = 0; $idx < $particlenum; $idx = $idx + 1) {
my int $willdraw = 0;
my int $willdraw = 0;
if (@lifetimes[$idx] <= 0e0) {
if (@lifetimes[$idx] <= 0e0) {
if (rand < df) {
if (rand < df) {
@lifetimes[$idx] = 25e-1; # time to live
@lifetimes[$idx] = 25e-1; # time to live
@positions[$xidx] = ($w / 20e0).Num; # starting position x
@positions[$xidx] = ($w / 20e0).Num; # starting position x
@positions[$yidx] = ($h / 10).Num; # and y
@positions[$yidx] = ($h / 10).Num; # and y
@velocities[$xidx] = ($spread * rand - $spread/2 + reciprocate()) * 10; # starting velocity x
@velocities[$xidx] = ($spread * rand - $spread/2 + reciprocate()) * 10; # starting velocity x
@velocities[$yidx] = (rand - 2.9e0) * $h / 20.5; # and y (randomized slightly so points reach different heights)
@velocities[$yidx] = (rand - 2.9e0) * $h / 20.5; # and y (randomized slightly so points reach different heights)
$willdraw = 1;
$willdraw = 1;
}
}
} else {
} else {
if @positions[$yidx] > $h / 10 && @velocities[$yidx] > 0 {
if @positions[$yidx] > $h / 10 && @velocities[$yidx] > 0 {
@velocities[$yidx] = @velocities[$yidx] * -0.3e0; # "bounce"
@velocities[$yidx] = @velocities[$yidx] * -0.3e0; # "bounce"
}
}


@velocities[$yidx] = @velocities[$yidx] + $h/10.Num * df; # adjust velocity
@velocities[$yidx] = @velocities[$yidx] + $h/10.Num * df; # adjust velocity
@positions[$xidx] = @positions[$xidx] + @velocities[$xidx] * df; # adjust position x
@positions[$xidx] = @positions[$xidx] + @velocities[$xidx] * df; # adjust position x
@positions[$yidx] = @positions[$yidx] + @velocities[$yidx] * df; # and y
@positions[$yidx] = @positions[$yidx] + @velocities[$yidx] * df; # and y


@lifetimes[$idx] = @lifetimes[$idx] - df;
@lifetimes[$idx] = @lifetimes[$idx] - df;
$willdraw = 1;
$willdraw = 1;
}
}


if ($willdraw) {
if ($willdraw) {
$points[$pointidx++] = (@positions[$xidx] * 10).floor; # gather all of the points that
$points[$pointidx++] = (@positions[$xidx] * 10).floor; # gather all of the points that
$points[$pointidx++] = (@positions[$yidx] * 10).floor; # are still going to be rendered
$points[$pointidx++] = (@positions[$yidx] * 10).floor; # are still going to be rendered
}
}


$xidx = $xidx + 2;
$xidx = $xidx + 2;
$yidx = $xidx + 1;
$yidx = $xidx + 1;
}
}
$numpoints = ($pointidx - 1) div 2;
$numpoints = ($pointidx - 1) div 2;
}
}


sub render {
sub render {
SDL_SetRenderDrawColor($renderer, 0x0, 0x0, 0x0, 0xff);
SDL_SetRenderDrawColor($renderer, 0x0, 0x0, 0x0, 0xff);
SDL_RenderClear($renderer);
SDL_RenderClear($renderer);


SDL_SetRenderDrawColor($renderer, |hsv2rgb(((now % 5) / 5).round(.01), $saturation, 1), 0x7f);
SDL_SetRenderDrawColor($renderer, |hsv2rgb(((now % 5) / 5).round(.01), $saturation, 1), 0x7f);
SDL_RenderDrawPoints($renderer, $points, $numpoints);
SDL_RenderDrawPoints($renderer, $points, $numpoints);


SDL_RenderPresent($renderer);
SDL_RenderPresent($renderer);
}
}


enum KEY_CODES (
enum KEY_CODES (
K_UP => 82,
K_UP => 82,
K_DOWN => 81,
K_DOWN => 81,
K_LEFT => 80,
K_LEFT => 80,
K_RIGHT => 79,
K_RIGHT => 79,
K_SPACE => 44,
K_SPACE => 44,
K_PGUP => 75,
K_PGUP => 75,
K_PGDN => 78,
K_PGDN => 78,
K_Q => 20,
K_Q => 20,
);
);


Line 173: Line 1,271:


main: loop {
main: loop {
my $start = now;
my $start = now;


while SDL_PollEvent($event) {
while SDL_PollEvent($event) {
my $casted_event = SDL_CastEvent($event);
my $casted_event = SDL_CastEvent($event);


given $casted_event {
given $casted_event {
when *.type == QUIT {
when *.type == QUIT {
last main;
last main;
}
}
when *.type == WINDOWEVENT {
when *.type == WINDOWEVENT {
if .event == RESIZED {
if .event == RESIZED {
$w = .data1;
$w = .data1;
$h = .data2;
$h = .data2;
}
}
}
}
when *.type == KEYDOWN {
when *.type == KEYDOWN {
if KEY_CODES(.scancode) -> $comm {
if KEY_CODES(.scancode) -> $comm {
given $comm {
given $comm {
when 'K_UP' { $saturation = (($saturation + .1) min 1e0) }
when 'K_UP' { $saturation = (($saturation + .1) min 1e0) }
when 'K_DOWN' { $saturation = (($saturation - .1) max 0e0) }
when 'K_DOWN' { $saturation = (($saturation - .1) max 0e0) }
when 'K_PGUP' { $spread = (($spread + .1) min 5e0) }
when 'K_PGUP' { $spread = (($spread + .1) min 5e0) }
when 'K_PGDN' { $spread = (($spread - .1) max 2e-1) }
when 'K_PGDN' { $spread = (($spread - .1) max 2e-1) }
when 'K_RIGHT' { $range = (($range + .1) min 2e0) }
when 'K_RIGHT' { $range = (($range + .1) min 2e0) }
when 'K_LEFT' { $range = (($range - .1) max 1e-1) }
when 'K_LEFT' { $range = (($range - .1) max 1e-1) }
when 'K_SPACE' { &reciprocate = reciprocate() == 0 ?? sub { $range * sin(now) } !! sub { 0 } }
when 'K_SPACE' { &reciprocate = reciprocate() == 0 ?? sub { $range * sin(now) } !! sub { 0 } }
when 'K_Q' { last main }
when 'K_Q' { last main }
}
}
}
}
}
}
}
}
}
}


update($df);
update($df);


render();
render();


$df = (now - $start).Num;
$df = (now - $start).Num;


print fps();
print fps();
}
}


Line 217: Line 1,315:


sub fps {
sub fps {
state $fps-frames = 0;
state $fps-frames = 0;
state $fps-now = now;
state $fps-now = now;
state $fps = '';
state $fps = '';
$fps-frames++;
$fps-frames++;
if now - $fps-now >= 1 {
if now - $fps-now >= 1 {
$fps = [~] "\r", ' ' x 20, "\r",
$fps = [~] "\r", ' ' x 20, "\r",
sprintf "FPS: %5.1f ", ($fps-frames / (now - $fps-now));
sprintf "FPS: %5.1f ", ($fps-frames / (now - $fps-now));
$fps-frames = 0;
$fps-frames = 0;
$fps-now = now;
$fps-now = now;
}
}
$fps
$fps
}
}


sub hsv2rgb ( $h, $s, $v ){
sub hsv2rgb ( $h, $s, $v ){
state %cache;
state %cache;
%cache{"$h|$s|$v"} //= do {
%cache{"$h|$s|$v"} //= do {
my $c = $v * $s;
my $c = $v * $s;
my $x = $c * (1 - abs( (($h*6) % 2) - 1 ) );
my $x = $c * (1 - abs( (($h*6) % 2) - 1 ) );
my $m = $v - $c;
my $m = $v - $c;
[(do given $h {
[(do given $h {
when 0..^1/6 { $c, $x, 0 }
when 0..^1/6 { $c, $x, 0 }
when 1/6..^1/3 { $x, $c, 0 }
when 1/6..^1/3 { $x, $c, 0 }
when 1/3..^1/2 { 0, $c, $x }
when 1/3..^1/2 { 0, $c, $x }
when 1/2..^2/3 { 0, $x, $c }
when 1/2..^2/3 { 0, $x, $c }
when 2/3..^5/6 { $x, 0, $c }
when 2/3..^5/6 { $x, 0, $c }
when 5/6..1 { $c, 0, $x }
when 5/6..1 { $c, 0, $x }
} ).map: ((*+$m) * 255).Int]
} ).map: ((*+$m) * 255).Int]
}
}
}
}</lang>
</syntaxhighlight>


[https://github.com/thundergnat/rc/blob/master/img/fountain-raku.mp4?raw=true Link to off-site .mp4 video]
[https://github.com/thundergnat/rc/blob/master/img/fountain-raku.mp4?raw=true Link to off-site .mp4 video]

=={{header|Wren}}==
{{trans|Julia}}
{{libheader|DOME}}
{{libheader|Wren-dynamic}}
<syntaxhighlight lang="wren">import "dome" for Window, Platform, Process
import "graphics" for Canvas, Color
import "math" for Math, Point
import "random" for Random
import "input" for Keyboard
import "./dynamic" for Struct

var Start = Platform.time
var Rand = Random.new()

var fields = [
"particleNum",
"positions",
"velocities",
"lifetimes",
"points",
"numPoints",
"saturation",
"spread",
"range",
"reciprocate"
]
var ParticleFountain = Struct.create("ParticleFountain", fields)

class ParticleDisplay {
construct new(particleNum, width, height) {
Window.resize(width, height)
Canvas.resize(width, height)
Window.title = "Wren Particle System!"
_pn = particleNum
_w = width
_h = height
_df = 1 / 200 // say
_pf = ParticleFountain.new(
_pn, // particleNum
List.filled(_pn * 2, 0), // positions
List.filled(_pn * 2, 0), // velocities
List.filled(_pn, 0), // lifetimes
List.filled(_pn, null), // points
0, // numPoints
0.4, // saturation
1.5, // spread
1.5, // range
false // reciprocate
)
for (i in 0..._pn) _pf.points[i] = Point.new(0, 0)
}

init() {
Canvas.cls()
_frames = 0
}

updatePF() {
var xidx = 0
var yidx = 1
var pointIdx = 0
var recip = Fn.new { _pf.reciprocate ? _pf.range * Math.sin(Platform.time/1000) : 0 }
for (idx in 0..._pf.particleNum) {
var willDraw = false
if (_pf.lifetimes[idx] <= 0) {
if (Rand.float() < _df) {
_pf.lifetimes[idx] = 2.5 // time to live
_pf.positions[xidx] = _w / 20 // starting position x
_pf.positions[yidx] = _h / 10 // and y

// starting velocities x and y
// randomized slightly so points reach different heights
_pf.velocities[xidx] = 10 * (_pf.spread * Rand.float() - _pf.spread / 2 + recip.call())
_pf.velocities[yidx] = (Rand.float() - 2.9) * _h / 20.5
_willDraw = true
}
} else {
if (_pf.positions[yidx] > _h/10 && _pf.velocities[yidx] > 0) {
_pf.velocities[yidx] = _pf.velocities[yidx] * (-0.3) // bounce
}
_pf.velocities[yidx] = _pf.velocities[yidx] + _df * _h / 10 // adjust velocity
_pf.positions[xidx] = _pf.positions[xidx] + _pf.velocities[xidx] * _df // adjust position x
_pf.positions[yidx] = _pf.positions[yidx] + _pf.velocities[yidx] * _df // and y
_pf.lifetimes[idx] = _pf.lifetimes[idx] - _df
willDraw = true
}
if (willDraw) { // gather all the points that are going to be rendered
_pf.points[pointIdx] = Point.new((_pf.positions[xidx] * 10).floor,
(_pf.positions[yidx] * 10).floor)
pointIdx = pointIdx + 1
}
xidx = xidx + 2
yidx = xidx + 1
_pf.numPoints = pointIdx
}
}

update() {
if (Keyboard["Up"].justPressed) {
_pf.saturation = Math.min(_pf.saturation + 0.1, 1)
} else if (Keyboard["Down"].justPressed) {
_pf.saturation = Math.max(_pf.saturation - 0.1, 0)
} else if (Keyboard["PageUp"].justPressed) {
_pf.spread = Math.min(_pf.spread + 1, 50)
} else if (Keyboard["PageDown"].justPressed) {
_pf.spread = Math.max(_pf.spread - 0.1, 0.2)
} else if (Keyboard["Left"].justPressed) {
_pf.range = Math.min(_pf.range + 0.1, 12)
} else if (Keyboard["Right"].justPressed) {
_pf.range = Math.max(_pf.range - 0.1, 0.1)
} else if (Keyboard["Space"].justPressed) {
_pf.reciprocate = !_pf.reciprocate
} else if (Keyboard["Q"].justPressed) {
Process.exit()
}
updatePF()
}

draw(alpha) {
var c = Color.hsv((Platform.time % 5) * 72, _pf.saturation, 0.5, 0x7f)
for (i in 0..._pf.numPoints) {
Canvas.pset(_pf.points[i].x, _pf.points[i].y, c)
}
_frames = _frames + 1
var now = Platform.time
if (now - Start >= 1) {
Start = now
Window.title = "Wren Particle System! (FPS = %(_frames))"
_frames = 0
}
}
}

System.print("""

Use UP and DOWN arrow keys to modify the saturation of the particle colors.
Use PAGE UP and PAGE DOWN keys to modify the "spread" of the particles.
Toggle reciprocation off / on with the SPACE bar.
Use LEFT and RIGHT arrow keys to modify angle range for reciprocation.
Press the "q" key to quit.
""")

var Game = ParticleDisplay.new(3000, 800, 800)</syntaxhighlight>

=={{header|XPL0}}==
{{trans|EasyLang}}
[[File:XPL0_Fountain.gif|right]]
<syntaxhighlight lang "XPL0">func real RandomF;
return float(Ran(1000)) / 1000.;

def N = 6000;
real X(N), Y(N);
real VX(N), VY(N);
def Color = 15;
int I, Ind;
[SetVid($12);
Ind:= 0;
repeat
for I:= 1 to 32 do
[Ind:= rem((Ind+1)/N);
X(Ind):= 50. + RandomF;
Y(Ind):= float(I)/4.;
VX(Ind):= (RandomF - 0.5) * 0.4;
VY(Ind):= 2. + RandomF*0.1;
];
WaitForVSync;
Clear;
for I:= 0 to N-1 do
[Point(fix(X(I)), 480-fix(Y(I)), Color);
X(I):= X(I) + VX(I); Y(I):= Y(I) + VY(I);
VY(I):= VY(I) - 0.025;
];
until KeyHit;
]</syntaxhighlight>

Latest revision as of 20:32, 12 March 2024

Task
Particle fountain
You are encouraged to solve this task according to the task description, using any language you may know.

Implement a particle fountain.

Emulate a fountain of water droplets in a gravitational field being sprayed up and then falling back down.

The particle fountain should be generally ordered but individually chaotic; the particles should be going mostly in the same direction, but should have slightly different vectors.

Your fountain should have at least several hundred particles in motion at any one time, and ideally several thousand.

It is optional to have the individual particle interact with each other.

If at all possible, link to a short video clip of your fountain in action.

Off-site link to a demo video

C++

Library: SDL
Translation of: Raku
#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;
}

EasyLang

Run it

rad = 0.125
n = 6000
# 
len x[] n ; len y[] n
len vx[] n ; len vy[] n
background 479
color 999
on animate
   for i = 1 to 32
      ind = (ind + 1) mod1 n
      x[ind] = 50 + randomf
      y[ind] = i / 4
      vx[ind] = (randomf - 0.5) * 0.4
      vy[ind] = 2 + randomf * 0.1
   .
   clear
   for i = 1 to n
      move x[i] y[i]
      circle rad
      x[i] += vx[i] ; y[i] += vy[i]
      vy[i] -= 0.025
   .
.

Java

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;

import javax.swing.JFrame;

public final class ParticleFountainTask {

    public static void main(String[] args) {
    	EventQueue.invokeLater( () -> {
    		 JFrame.setDefaultLookAndFeelDecorated(true);
             JFrame frame = new JFrame("Particle Fountain");
             frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
             frame.setResizable(false);
             ParticleFountain particleFountain = new ParticleFountain(3_000, 1_000, 750);
             frame.add(particleFountain);
             frame.pack();
             frame.setLocationRelativeTo(null);
             frame.setVisible(true);
             
             particleFountain.start();
    	} );
    }

    private static final class ParticleFountain extends Canvas {        

        public ParticleFountain(int aParticleCount, int aWidth, int aHeight) {
        	particleCount = aParticleCount;
    		width = aWidth;
    		height = aHeight;
    		saturation = 0.6;
    		spread = 1.5;
    		range = 1.5;
    		reciprocate = false;    		  		
    				
    		setPreferredSize( new Dimension(width, height) );
    		addKeyListener( new InputHandler() );    		
    		
    		executorService = Executors.newSingleThreadExecutor();            
        }

        public void start() {
        	requestFocus();
            createBufferStrategy(2);
            executorService.execute( new DrawingCycle() );                         
        }      

        private final class DrawingCycle implements Runnable {
        	
        	public DrawingCycle() {
        		positions = new double[2 * particleCount];
        		velocities = new double[2 * particleCount];
        	    lifetimes = new double[particleCount];
        	    points = new Point[particleCount];
        	    Arrays.fill(points, new Point(0, 0) );
        	    
        	    random = ThreadLocalRandom.current();
        	}

            @Override
            public void run() {
                bufferStrategy = getBufferStrategy();
                
                while ( true ) {
                    update(0.005);
                    draw();
                }
            }            

            private void update(double animationSpeed) {
            	int xIndex = 0;
        		int yIndex = 1;
        		pointIndex = 0;

        		for ( int index = 0; index < particleCount; index++ ) {
        		    boolean showParticle = false;
        		    if ( lifetimes[index] <= 0.0 ) {
        		        if ( random.nextDouble() < animationSpeed ) {
        			        lifetimes[index] = 2.5;
        			        positions[xIndex] = width / 20;
        			        positions[yIndex] = height / 10;
        			        velocities[xIndex] = 
        			        	10 * ( spread * random.nextDouble() - spread / 2 + additionalXSpeed() );
        			        velocities[yIndex] = ( random.nextDouble() - 2.9 ) * height / 20.5;
        			        showParticle = true;
        		        }
        		    } else {
        		        if ( positions[yIndex] > height / 10 && velocities[yIndex] > 0 ) {
        		            velocities[yIndex] *= -0.3; // bounce particle
        		        }
        		        
        		        velocities[yIndex] += animationSpeed * height / 10;
        		        positions[xIndex] += velocities[xIndex] * animationSpeed;
        		        positions[yIndex] += velocities[yIndex] * animationSpeed;
        		        lifetimes[index] -= animationSpeed;
        		        showParticle = true;
        		    }

        		    if ( showParticle ) {
        		        points[pointIndex] = new Point((int) ( positions[xIndex] * 10 ),
        		        		                       (int) ( positions[yIndex] * 10 ));
        		        pointIndex += 1;
        		    }
        		    
        		    xIndex += 2;
        		    yIndex = xIndex + 1;
        		}
            }

            private void draw() {
                Graphics2D graphics2D = (Graphics2D) bufferStrategy.getDrawGraphics();
        		graphics2D.setColor(Color.BLACK);
        		graphics2D.fillRect(0, 0, getWidth(), getHeight());		
        		for ( int i = 0; i < pointIndex; i++ ) {
        			graphics2D.setColor(Color.getHSBColor(random.nextFloat(), (float) saturation, 1.0F));
        			graphics2D.fillOval(points[i].x, points[i].y, 5, 5);
        		}
        		graphics2D.dispose(); 
                    
                bufferStrategy.show();
            } 
            
            private double additionalXSpeed() {
        		return ( reciprocate ) ? range * Math.sin(System.currentTimeMillis() / 1_000) : 0.0;		
        	}
           
            private double[] positions;
        	private double[] velocities;
            private double[] lifetimes;
            private int pointIndex;
            private Point[] points;            
            private BufferStrategy bufferStrategy;  
            private ThreadLocalRandom random;

        } // End DrawingCycle class
        
        private final class InputHandler extends KeyAdapter {
   		 
    		@Override
    		public void keyPressed(KeyEvent aKeyEvent) {
    			final int keyCode = aKeyEvent.getKeyCode();
    			switch ( keyCode ) {
    				case KeyEvent.VK_UP        -> saturation = Math.min(saturation + 0.1, 1.0);
    				case KeyEvent.VK_DOWN      -> saturation = Math.max(saturation - 0.1, 0.0);
    				case KeyEvent.VK_PAGE_UP   -> spread = Math.min(spread + 0.1, 5.0);
    				case KeyEvent.VK_PAGE_DOWN -> spread = Math.max(spread - 0.1, 0.5);
    				case KeyEvent.VK_RIGHT     -> range = Math.min(range + 0.1, 2.0);
    				case KeyEvent.VK_LEFT      -> range = Math.max(range + 0.1, 0.1);
    				case KeyEvent.VK_SPACE     -> reciprocate = ! reciprocate;
    				case KeyEvent.VK_Q         -> Runtime.getRuntime().exit(0);
    				default -> { /* Take no action */ }
    			}	
    		}
    		
    	} // End InputHandler class
        
        private int particleCount;
    	private int width;
    	private int height;
    	private double saturation;
     	private double spread;
     	private double range;
     	private boolean reciprocate;	
    	private ExecutorService executorService;    	  	
        
    } // End ParticleFountain class        

} // End ParticleFountainTask class

Julia

Translation of: Raku
using Dates, Colors, SimpleDirectMediaLayer.LibSDL2

mutable struct ParticleFountain
    particlenum::Int
    positions::Vector{Float64}
    velocities::Vector{Float64}
    lifetimes::Vector{Float64}
    points::Vector{SDL_Point}
    numpoints::Int
    saturation::Float64
    spread::Float64
    range::Float64
    reciprocate::Bool
    ParticleFountain(N) = new(N, zeros(2N), zeros(2N), zeros(N), fill(SDL_Point(0, 0), N),
        0, 0.4, 1.5, 1.5, false)
end

function update(pf, w, h, df)
    xidx, yidx, pointidx = 1, 2, 0
    recip() = pf.reciprocate ? pf.range * sin(Dates.value(now()) / 1000) : 0.0
    for idx in 1:pf.particlenum
        willdraw = false
        if pf.lifetimes[idx] <= 0.0
            if rand() < df
                pf.lifetimes[idx]   = 2.5;                       # time to live
                pf.positions[xidx]  = (w / 20)                   # starting position x
                pf.positions[yidx]  = (h / 10)                   # and y
                pf.velocities[xidx] = 10 * (pf.spread * rand() - pf.spread / 2 + recip()) # starting velocity x
                pf.velocities[yidx] = (rand() - 2.9) * h / 20.5; # and y (randomized slightly so points reach different heights)
                willdraw = true
            end
        else
            if pf.positions[yidx] > h / 10 && pf.velocities[yidx] > 0
                pf.velocities[yidx] *= -0.3                  # "bounce"
            end
            pf.velocities[yidx] += df * h / 10                  # adjust velocity
            pf.positions[xidx]  += pf.velocities[xidx] * df     # adjust position x
            pf.positions[yidx]  += pf.velocities[yidx] * df     # and y
            pf.lifetimes[idx]  -= df
            willdraw = true
        end

        if willdraw # gather all of the points that are going to be rendered
            pointidx += 1
            pf.points[pointidx] = SDL_Point(Cint(floor(pf.positions[xidx] * 10)),
                Cint(floor(pf.positions[yidx] * 10)))
        end
        xidx += 2
        yidx = xidx + 1
        pf.numpoints = pointidx
    end
    return pf
end

function fountain(particlenum = 3000, w = 800, h = 800)
    SDL_Init(SDL_INIT_VIDEO)
    window = SDL_CreateWindow("Julia Particle System!", SDL_WINDOWPOS_CENTERED_MASK,
        SDL_WINDOWPOS_CENTERED_MASK, w, h, SDL_WINDOW_RESIZABLE)
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)
    SDL_ClearError()
    df = 0.0001
    pf = ParticleFountain(3000)
    overallstart, close, frames = now(), false, 0
    while !close
        dfstart = now()
        event_ref = Ref{SDL_Event}()
        while Bool(SDL_PollEvent(event_ref))
            event_type = event_ref[].type
            evt = event_ref[]
            if event_type == SDL_QUIT
                close = true
                break
            end
            if event_type == SDL_WINDOWEVENT
                if evt.window.event == 5
                    w = evt.window.data1
                    h = evt.window.data2
                end
            end
            if event_type == SDL_KEYDOWN
                comm = evt.key.keysym.scancode
                if comm == SDL_SCANCODE_UP
                    saturation = min(pf.saturation + 0.1, 1.0)
                elseif comm == SDL_SCANCODE_DOWN
                    saturation = max(pf.saturation - 0.1, 0.0)
                elseif comm == SDL_SCANCODE_PAGEUP
                    spread = min(pf.spread + 1, 50.0)
                elseif comm == SDL_SCANCODE_PAGEDOWN
                    spread = max(pf.spread - 0.1, 0.2)
                elseif comm == SDL_SCANCODE_LEFT
                    range = min(pf.range + 0.1, 12.0)
                elseif comm == SDL_SCANCODE_RIGHT
                    range = max(pf.range - 0.1, 0.1)
                elseif comm == SDL_SCANCODE_SPACE
                    pf.reciprocate = !pf.reciprocate
                elseif comm == SDL_SCANCODE_Q
                    close = true
                    break
                end
            end
        end
        pf = update(pf, w, h, df)
        SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xff)
        SDL_RenderClear(renderer)
        rgb = parse(UInt32, hex(HSL((Dates.value(now()) % 5) * 72, pf.saturation, 0.5)), base=16)
        red, green, blue = rgb & 0xff, (rgb >> 8) & 0xff, (rgb >>16) & 0xff
        SDL_SetRenderDrawColor(renderer, red, green, blue, 0x7f)
        SDL_RenderDrawPoints(renderer, pf.points, pf.numpoints)
        SDL_RenderPresent(renderer)
        frames += 1
        df = Float64(Dates.value(now()) - Dates.value(dfstart)) / 1000
        elapsed = Float64(Dates.value(now()) - Dates.value(overallstart)) / 1000
        elapsed > 0.5 && print("\r", ' '^20, "\rFPS: ", round(frames / elapsed, digits=1))
    end
    SDL_Quit()
end

println("""
    Use UP and DOWN arrow keys to modify the saturation of the particle colors.
    Use PAGE UP and PAGE DOWN keys to modify the "spread" of the particles.
    Toggle reciprocation off / on with the SPACE bar.
    Use LEFT and RIGHT arrow keys to modify angle range for reciprocation.
    Press the "q" key to quit.
""")

fountain()

Lua

Library: LÖVE

Video is here

-- Returns canvas of given width and height containing a circle
function initCanvas (width, height)
    local c = love.graphics.newCanvas(width, height)
    love.graphics.setCanvas(c) -- Switch to drawing on canvas 'c'
    love.graphics.circle("fill", width / 2, height / 2, 2, 100)
    love.graphics.setCanvas() -- Switch back to drawing on main screen
    return c
end

-- Returns particle system with given canvas
function initPartSys (image, maxParticles)
    local ps = love.graphics.newParticleSystem(image, maxParticles)
    ps:setParticleLifetime(3, 5) -- (min, max)
    ps:setDirection(math.pi * 1.5)
    ps:setSpeed(700)
    ps:setLinearAcceleration(-100, 500, 100, 700) -- (minX, minY, maxX, maxY)
    ps:setEmissionRate(1000)
    ps:setPosition(400, 550)
    ps:setColors(1, 1, 1, 1, 0, 0, 1, 0) -- Start solid white, fade to transluscent blue
    return ps
end

-- LÖVE callback that runs on program start
function love.load ()
    love.window.setTitle("Lua particle fountain")
    local canvas = initCanvas(10, 10)
    psystem = initPartSys(canvas, 10000)
end

-- LÖVE callback to update values before each frame
function love.update (dt)
    psystem:update(dt)
end

-- LÖVE callback to draw each frame to the screen
function love.draw ()
    love.graphics.draw(psystem)
end

Nim

Translation of: Julia
Library: SDL2

Note that for key events, the scan code doesn’t take in account the keyboard layout. So we check the "sym" value instead.

import std/[lenientops, math, monotimes, random, times]
import sdl2

type ParticleFountain[N: static Positive] = object
  positions: array[1..2 * N, float]
  velocities: array[1..2 * N, float]
  lifetimes: array[1..N, float]
  points: array[1..N, Point]
  numPoints: int
  saturation: float
  spread: float
  range: float
  reciprocate: bool

proc initParticleFountain[N: static Positive](): ParticleFountain[N] =
  ParticleFountain[N](saturation: 0.4, spread: 1.5, range: 1.5)

proc update(pf: var ParticleFountain; w, h: cint; df: float) =
  var
    xidx = 1
    yidx = 2
    pointidx = 0

  template recip(pf: ParticleFountain): float =
    if pf.reciprocate: pf.range * sin(epochTime() / 1000) else: 0.0

  for idx in 1..pf.N:
    var willDraw = false
    if pf.lifetimes[idx] <= 0:
      if rand(1.0) < df:
        pf.lifetimes[idx] = 2.5   # Time to live.
        # Starting position.
        pf.positions[xidx] = w / 20
        pf.positions[yidx] = h / 10
        # Starting velocity.
        pf.velocities[xidx] = 10 * (pf.spread * rand(1.0) - pf.spread / 2 + pf.recip())
        pf.velocities[yidx] = (rand(1.0) - 2.9) * h / 20.5
        willDraw = true
    else:
      if pf.positions[yidx] > h / 10 and pf.velocities[yidx] > 0:
        pf.velocities[yidx] *= -0.3   # "Bounce".
      pf.velocities[yidx] += df * h / 10                  # Adjust velocity.
      pf.positions[xidx] += pf.velocities[xidx] * df      # Adjust position x.
      pf.positions[yidx] += pf.velocities[yidx] * df      # Adjust position y.
      pf.lifetimes[idx] -= df
      willDraw = true

    if willDraw:
      # Gather all of the points that are going to be rendered.
      inc pointIdx
      pf.points[pointidx] = (cint(pf.positions[xidx] * 10), cint(pf.positions[yidx] * 10))
    inc xidx, 2
    yidx = xidx + 1
    pf.numPoints = pointidx

func hsvToRgb(h, s, v: float): (byte, byte, byte) =
  let hp = h / 60.0
  let c = s * v
  let x = c * (1 - abs(hp mod 2 - 1))
  let m = v - c
  var (r, g, b) = if hp <= 1: (c, x, 0.0)
                  elif hp <= 2: (x, c, 0.0)
                  elif hp <= 3: (0.0, c, x)
                  elif hp <= 4: (0.0, x, c)
                  elif hp <= 5: (x, 0.0, c)
                  else: (c, 0.0, x)
  r += m
  g += m
  b += m
  result = (byte(r * 255), byte(g * 255), byte(b * 255))

proc fountain(particleNum = 3000; w = 800; h = 800) =
  var w = w.cint
  var h = h.cint
  discard sdl2.init(INIT_VIDEO or INIT_EVENTS)
  let window = createWindow("Nim Particle System!", SDL_WINDOWPOS_CENTERED_MASK,
                            SDL_WINDOWPOS_CENTERED_MASK, w, h, SDL_WINDOW_RESIZABLE)
  let renderer = createRenderer(window, -1, 0)
  clearError()
  var df = 0.0001
  var pf = initParticleFountain[3000]()
  var close = false
  var frames = 0
  block Simulation:
    while not close:
      let dfStart = getMonoTime()
      var event: Event
      while bool(pollEvent(event)):
        case event.kind
        of QuitEvent:
          break Simulation
        of WindowEvent:
          if event.window.event == WindowEvent_Resized:
            w = event.window.data1
            h = event.window.data2
        of KeyDown:
          let comm = event.key.keysym.sym
          case comm
          of K_UP:
            pf.saturation = min(pf.saturation + 0.1, 1.0)
          of K_DOWN:
            pf.saturation = max(pf.saturation - 0.1, 0.0)
          of K_PAGEUP:
            pf.spread = min(pf.spread + 1.0, 50.0)
          of K_PAGEDOWN:
            pf.spread = max(pf.spread - 0.1, 0.2)
          of K_LEFT:
            pf.range = min(pf.range + 0.1, 12.0)
          of K_RIGHT:
            pf.range = max(pf.range - 0.1, 0.1)
          of K_SPACE:
            pf.reciprocate = not pf.reciprocate
          of K_Q:
            break Simulation
          else:
            discard
        else:
          discard

      pf.update(w, h, df)
      renderer.setDrawColor(0x0, 0x0, 0x0, 0xff)
      renderer.clear()
      let (red, green, blue) = hsvToRgb(epochTime() mod 5 * 72, pf.saturation, 1.0)
      renderer.setDrawColor(red, green, blue, 0x7f)
      renderer.drawPoints(pf.points[1].addr, pf.numPoints.cint)
      renderer.present()
      inc frames
      df = (getMonoTime() - dfStart).inMilliseconds.float / 1000

  sdl2.quit()

randomize()
echo """
  Use UP and DOWN arrow keys to modify the saturation of the particle colors.
  Use PAGE UP and PAGE DOWN keys to modify the "spread" of the particles.
  Toggle reciprocation off / on with the SPACE bar.
  Use LEFT and RIGHT arrow keys to modify angle range for reciprocation.
  Press the "q" key to quit.
"""
fountain()

Perl

#!/usr/bin/perl

use strict; # https://rosettacode.org/wiki/Particle_fountain
use warnings;
use Tk;

my $size = 900;
my @particles;
my $maxparticles = 500;
my @colors = qw( red green blue yellow cyan magenta orange white );

my $mw = MainWindow->new;
my $c = $mw->Canvas( -width => $size, -height => $size, -bg => 'black',
  )->pack;
$mw->Button(-text => 'Exit', -command => sub {$mw->destroy},
  )->pack(-fill => 'x');

step();
MainLoop;
-M $0 < 0 and exec $0;

sub step
  {
  $c->delete('all');
  $c->createLine($size / 2 - 10, $size, $size / 2, $size - 10,
    $size / 2 + 10, $size, -fill => 'white' );
  for ( @particles )
    {
    my ($ox, $oy, $vx, $vy, $color) = @$_;
    my $x = $ox + $vx;
    my $y = $oy + $vy;
    $c->createRectangle($ox, $oy, $x, $y, -fill => $color, -outline => $color);
    if( $y < $size )
      {
      $_->[0] = $x;
      $_->[1] = $y;
      $_->[3] += 0.006; # gravity :)
      }
    else { $_ = undef }
    }
  @particles = grep defined, @particles;
  if( @particles < $maxparticles and --$| )
    {
    push @particles, [ $size >> 1, $size - 10,
      (1 - rand 2) / 2.5 , -3 - rand 0.05, $colors[rand @colors] ];
    }
  $mw->after(1 => \&step);
  }

Phix

Translation of: Raku
Library: Phix/pGUI
Library: Phix/online

You can run this online here.

--
-- demo\rosetta\Particle_fountain.exw
-- ==================================
--
with javascript_semantics
include pGUI.e

Ihandle dlg, canvas
cdCanvas cddbuffer, cdcanvas

constant title = "Particle fountain"
constant help_text = """
Uparrow increases the saturation of the particle colors,
downarrow decreases saturation until they all become white.
PageUp sprays the particles out at a wider angle/spread,
PageDown makes the jet narrower.
Space toggles reciprocation (wobble) on and off (straight up).
Left arrow decreases the angle range for reciprocation,
right arrow increases the angle range for reciprocation.
Press the "q" key to quit.
"""

constant particlenum = 3000
-- each particle is {x,y,color,life,dx,dy}
sequence particles = repeat({0,0,0,0,0,0},particlenum)
atom t1 = time()+1
integer fps = 0
bool reciprocate = true
atom range = 1.5,
     spread = 1.5,
     saturation = 0.4,
     start = time(),
     df = 0.0001

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*posy*/)
    integer {w, h} = IupGetIntInt(canvas, "DRAWSIZE")
    cdCanvasActivate(cddbuffer)
    cdCanvasClear(cddbuffer)
    for i=1 to length(particles) do
        atom {x,y,color,life} = particles[i]
        if life>0 then
            cdCanvasPixel(cddbuffer, x, h/10-y, color) 
        end if
    end for
    cdCanvasFlush(cddbuffer)
    return IUP_DEFAULT
end function

function map_cb(Ihandle ih)
    cdcanvas = cdCreateCanvas(CD_IUP, ih)
    cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
    cdCanvasSetBackground(cddbuffer, CD_BLACK)
    return IUP_DEFAULT
end function

function timer_cb(Ihandle /*ih*/)
    integer {w, h} = IupGetIntInt(canvas, "DRAWSIZE")
    fps += 1
    df = time()-start
    start = time()
    for i=1 to particlenum do
        atom {x,y,color,life,dx,dy} = particles[i]
        if life<=0 then
            if rnd()<df then
                life = 2.5          -- time to live
                x = w/2             -- starting position x
                y = h/10            --               and y
                -- randomize velocity so points reach different heights:
                atom r = iff(reciprocate?range*sin(time()):0)
                dx = (spread*rnd()-spread/2+r)*50   -- starting velocity x
                dy = (rnd()-2.9) * h/20.5           --               and y 
                color = hsv_to_rgb(round(remainder(time(),5)/5,100), saturation, 1)
            end if
        else
            if y>h/10 and dy>0 then
                dy *= -0.3  -- "bounce"
            end if
            dy += (h/10)*df -- adjust velocity
            x += dx*df      -- adjust position x
            y += dy*df*8    --             and y
            life -= df
        end if
        particles[i] = {x,y,color,life,dx,dy}
    end for
    IupRedraw(canvas)
    if time()>t1 then
        IupSetStrAttribute(dlg,"TITLE","%s (%d, %d fps/s [%dx%d])",{title,particlenum,fps,w,h})
        t1 = time()+1
        fps = 0
    end if
    return IUP_DEFAULT
end function

function key_cb(Ihandle /*dlg*/, atom c)
    if c=K_ESC or lower(c)='q' then return IUP_CLOSE
    elsif c=K_F1 then   IupMessage(title,help_text)
    elsif c=K_UP then   saturation = min(saturation+0.1,1)
    elsif c=K_DOWN then saturation = max(saturation-0.1,0)
    elsif c=K_PGUP then spread = min(spread+0.1,5)
    elsif c=K_PGDN then spread = max(spread-0.1,0.2)
    elsif c=K_RIGHT then range = min(range+0.1,2)
    elsif c=K_LEFT then range = max(range-0.1,0.1)
    elsif c=K_SP then reciprocate = not reciprocate
    end if
    return IUP_CONTINUE
end function

procedure main()
    IupOpen()
    canvas = IupGLCanvas("RASTERSIZE=400x300")
    IupSetCallbacks({canvas}, {"ACTION", Icallback("redraw_cb"),
                               "MAP_CB", Icallback("map_cb")})
    dlg = IupDialog(canvas,`TITLE="%s"`,{title})
    IupSetCallback(dlg, "KEY_CB", Icallback("key_cb"))
    Ihandle timer = IupTimer(Icallback("timer_cb"), 1000/25)
    IupShowXY(dlg,IUP_CENTER,IUP_CENTER)
    IupSetAttribute(canvas, "RASTERSIZE", NULL)
    if platform()!=JS then
        IupMainLoop()
        IupClose()
    end if
end procedure

main()

Python

Library: SDL
Translation of: C++

Use arrow keys, PageUp, PageDown and Space to vary fountain parameters.

# Using SDL2 library: # pip install PySDL2

import sys
import random
import time
import math

import sdl2
import sdl2.ext

FPS = 60
NEW_PARTICLES_PER_FRAME = 10
MAX_PARTICLES = 5_000
GRAVITY = 100
WIDTH = 640
HEIGHT = 480


def clamp(value, min_, max_):
    """Return value clamped between min and max"""
    return max(min_, min(value, max_))


class Particle:
    """Particle obeying gravity law."""

    def __init__(self):
        self.x = 0
        self.y = 0
        self.v_x = 0
        self.v_y = 0

    def update(self, dtime: float) -> None:
        """Move particle and update speed with gravity"""
        self.x = self.x + self.v_x * dtime
        self.y = self.y + self.v_y * dtime
        self.v_y = self.v_y + GRAVITY * dtime

    def set(self, x, y, v_x, v_y):
        """Set particle values"""
        self.x = x
        self.y = y
        self.v_x = v_x
        self.v_y = v_y


class Fountain:
    """The fountain"""

    def __init__(self, max_particles: int, particles_per_frame: int):
        self.particles_per_frame = particles_per_frame
        self.max_particles = max_particles
        self.spread = 10.0
        self.range = math.sqrt(2 * GRAVITY * (HEIGHT - 20 - self.spread))
        self.saturation = 155
        self.reciprocate = False
        self.reciprocating_time = 0.0
        self.particles = [
            self.init_particle(Particle()) for _ in range(self.particles_per_frame)
        ]

    def update(self, dtime) -> None:
        """Update particles"""
        if self.reciprocate:
            self.reciprocating_time += dtime

        for particle in self.particles:
            particle.update(dtime)
            if particle.y > HEIGHT - 10:
                self.init_particle(particle)

        if len(self.particles) < self.max_particles:
            for _ in range(self.particles_per_frame):
                self.particles.append(self.init_particle(Particle()))
            # print(len(particles))

    def render(self, renderer: sdl2.ext.renderer.Renderer) -> None:
        """Render particles"""
        points = [(particle.x, particle.y) for particle in self.particles]

        renderer.clear()
        renderer.draw_point(
            points, sdl2.ext.Color(self.saturation, self.saturation, 255)
        )
        renderer.present()

    def step_parameter(self, param, step):
        """Change parameters"""
        if param == "spread":
            self.spread = clamp(self.spread + step, 0, 50)
        elif param == "range":
            self.range = clamp(self.range + step, 0, 300)
        elif param == "color":
            self.saturation = clamp(self.saturation + step, 0, 255)
        elif param == "reciprocate":
            self.reciprocate = not self.reciprocate
            self.reciprocating_time = 0.0

    def init_particle(self, particle: Particle) -> Particle:
        """Move particle at initial position with a random-y speed"""
        radius = random.random() * self.spread
        direction = random.random() * math.pi * 2
        v_x = radius * math.cos(direction) + math.sin(self.reciprocating_time) * 20.0
        v_y = -self.range + radius * math.sin(direction)
        particle.set(WIDTH // 2, HEIGHT - 10, v_x, v_y)
        return particle


def make_renderer() -> sdl2.ext.renderer.Renderer:
    """Initialise SDL and make renderer"""
    sdl2.ext.init()

    window = sdl2.ext.Window("Particle Fountain", size=(WIDTH, HEIGHT))
    window.show()

    renderer = sdl2.ext.renderer.Renderer(window)

    return renderer


def limit_frame_rate(fps: float, cur_time: int) -> bool:
    """Limit frame rate"""
    dtime = time.monotonic_ns() - cur_time
    frame_duration = 1e9 / fps
    if dtime < frame_duration:
        time.sleep((frame_duration - dtime) / 1e9)
        return True
    return False


def handle_events(fountain: Fountain):
    """Act on events"""
    key_actions = {
        sdl2.SDL_SCANCODE_PAGEUP: lambda: fountain.step_parameter("color", 5),
        sdl2.SDL_SCANCODE_PAGEDOWN: lambda: fountain.step_parameter("color", -5),
        sdl2.SDL_SCANCODE_UP: lambda: fountain.step_parameter("range", 1),
        sdl2.SDL_SCANCODE_DOWN: lambda: fountain.step_parameter("range", -1),
        sdl2.SDL_SCANCODE_LEFT: lambda: fountain.step_parameter("spread", -1),
        sdl2.SDL_SCANCODE_RIGHT: lambda: fountain.step_parameter("spread", 1),
        sdl2.SDL_SCANCODE_SPACE: lambda: fountain.step_parameter("reciprocate", 1),
    }

    events = sdl2.ext.get_events()
    for event in events:
        if event.type == sdl2.SDL_QUIT:
            return False
        if event.type == sdl2.SDL_KEYDOWN:
            if event.key.keysym.scancode in key_actions:
                key_actions[event.key.keysym.scancode]()
            elif event.key.keysym.scancode == sdl2.SDL_SCANCODE_Q:
                return False
    return True


def main_loop(renderer: sdl2.ext.renderer.Renderer, fountain: Fountain) -> None:
    """Main animation loop"""
    running = True

    cur_time = time.monotonic_ns()
    while running:
        running = handle_events(fountain)

        fountain.render(renderer)

        if not limit_frame_rate(FPS, cur_time):
            print(f"Didn't make it in time with {len(fountain.particles)} particles.")

        dtime = (time.monotonic_ns() - cur_time) / 1e9  # in seconds
        fountain.update(dtime)
        cur_time = time.monotonic_ns()

    sdl2.ext.quit()


def run():
    """Start!"""

    renderer = make_renderer()
    fountain = Fountain(MAX_PARTICLES, NEW_PARTICLES_PER_FRAME)

    main_loop(renderer, fountain)

    return 0


if __name__ == "__main__":
    sys.exit(run())

Raku

Has options to vary the direction at which the fountain sprays, the "spread" angle and the color of the emitted particles.

use NativeCall;
use SDL2::Raw;

my int ($w, $h) = 800, 800;
my SDL_Window $window;
my SDL_Renderer $renderer;

my int $particlenum = 3000;


SDL_Init(VIDEO);
$window = SDL_CreateWindow(
   "Raku Particle System!",
   SDL_WINDOWPOS_CENTERED_MASK, SDL_WINDOWPOS_CENTERED_MASK,
   $w, $h,
   RESIZABLE
);
$renderer = SDL_CreateRenderer( $window, -1, ACCELERATED );

SDL_ClearError();

my num @positions  = 0e0 xx ($particlenum * 2);
my num @velocities = 0e0 xx ($particlenum * 2);
my num @lifetimes  = 0e0 xx  $particlenum;

my CArray[int32] $points .= new;
my int $numpoints;
my Num $saturation = 4e-1;
my Num $spread = 15e-1;
my &reciprocate = sub { 0 }
my $range = 1.5;

sub update (num \df) {
   my int $xidx = 0;
   my int $yidx = 1;
   my int $pointidx = 0;
   loop (my int $idx = 0; $idx < $particlenum; $idx = $idx + 1) {
       my int $willdraw = 0;
       if (@lifetimes[$idx] <= 0e0) {
           if (rand < df) {
               @lifetimes[$idx]   = 25e-1;                       # time to live
               @positions[$xidx]  = ($w / 20e0).Num;             # starting position x
               @positions[$yidx]  = ($h / 10).Num;               # and y
               @velocities[$xidx] = ($spread * rand - $spread/2 + reciprocate()) * 10; # starting velocity x
               @velocities[$yidx] = (rand - 2.9e0) * $h / 20.5;    # and y (randomized slightly so points reach different heights)
               $willdraw = 1;
           }
       } else {
           if @positions[$yidx] > $h / 10 && @velocities[$yidx] > 0 {
               @velocities[$yidx] = @velocities[$yidx] * -0.3e0; # "bounce"
           }

           @velocities[$yidx] = @velocities[$yidx] + $h/10.Num * df;         # adjust velocity
           @positions[$xidx]  = @positions[$xidx] + @velocities[$xidx] * df; # adjust position x
           @positions[$yidx]  = @positions[$yidx] + @velocities[$yidx] * df; # and y

           @lifetimes[$idx]   = @lifetimes[$idx] - df;
           $willdraw = 1;
       }

       if ($willdraw) {
           $points[$pointidx++] = (@positions[$xidx] * 10).floor; # gather all of the points that
           $points[$pointidx++] = (@positions[$yidx] * 10).floor; # are still going to be rendered
       }

       $xidx = $xidx + 2;
       $yidx = $xidx + 1;
   }
   $numpoints = ($pointidx - 1) div 2;
}

sub render {
   SDL_SetRenderDrawColor($renderer, 0x0, 0x0, 0x0, 0xff);
   SDL_RenderClear($renderer);

   SDL_SetRenderDrawColor($renderer, |hsv2rgb(((now % 5) / 5).round(.01), $saturation, 1), 0x7f);
   SDL_RenderDrawPoints($renderer, $points, $numpoints);

   SDL_RenderPresent($renderer);
}

enum KEY_CODES (
   K_UP     => 82,
   K_DOWN   => 81,
   K_LEFT   => 80,
   K_RIGHT  => 79,
   K_SPACE  => 44,
   K_PGUP   => 75,
   K_PGDN   => 78,
   K_Q      => 20,
);

say q:to/DOCS/;
Use UP and DOWN arrow keys to modify the saturation of the particle colors.
Use PAGE UP and PAGE DOWN keys to modify the "spread" of the particles.
Toggle reciprocation off / on with the SPACE bar.
Use LEFT and RIGHT arrow keys to modify angle range for reciprocation.
Press the "q" key to quit.
DOCS

my $event = SDL_Event.new;

my num $df = 0.0001e0;

main: loop {
   my $start = now;

   while SDL_PollEvent($event) {
       my $casted_event = SDL_CastEvent($event);

       given $casted_event {
           when *.type == QUIT {
               last main;
           }
           when *.type == WINDOWEVENT {
               if .event == RESIZED {
                   $w = .data1;
                   $h = .data2;
               }
           }
           when *.type == KEYDOWN {
               if KEY_CODES(.scancode) -> $comm {
                   given $comm {
                       when 'K_UP'    { $saturation = (($saturation + .1) min 1e0) }
                       when 'K_DOWN'  { $saturation = (($saturation - .1) max 0e0) }
                       when 'K_PGUP'  { $spread = (($spread + .1) min 5e0) }
                       when 'K_PGDN'  { $spread = (($spread - .1) max 2e-1) }
                       when 'K_RIGHT' { $range = (($range + .1) min 2e0) }
                       when 'K_LEFT'  { $range = (($range - .1) max 1e-1) }
                       when 'K_SPACE' { &reciprocate = reciprocate() == 0 ?? sub { $range * sin(now) } !! sub { 0 } }
                       when 'K_Q'     { last main }
                   }
               }
           }
       }
   }

   update($df);

   render();

   $df = (now - $start).Num;

   print fps();
}

say '';

sub fps {
   state $fps-frames = 0;
   state $fps-now    = now;
   state $fps        = '';
   $fps-frames++;
   if now - $fps-now >= 1 {
       $fps = [~] "\r", ' ' x 20, "\r",
           sprintf "FPS: %5.1f  ", ($fps-frames / (now - $fps-now));
       $fps-frames = 0;
       $fps-now = now;
   }
   $fps
}

sub hsv2rgb ( $h, $s, $v ){
   state %cache;
   %cache{"$h|$s|$v"} //= do {
       my $c = $v * $s;
       my $x = $c * (1 - abs( (($h*6) % 2) - 1 ) );
       my $m = $v - $c;
       [(do given $h {
           when   0..^1/6 { $c, $x, 0 }
           when 1/6..^1/3 { $x, $c, 0 }
           when 1/3..^1/2 { 0, $c, $x }
           when 1/2..^2/3 { 0, $x, $c }
           when 2/3..^5/6 { $x, 0, $c }
           when 5/6..1    { $c, 0, $x }
       } ).map: ((*+$m) * 255).Int]
   }
}

Link to off-site .mp4 video

Wren

Translation of: Julia
Library: DOME
Library: Wren-dynamic
import "dome" for Window, Platform, Process
import "graphics" for Canvas, Color
import "math" for Math, Point
import "random" for Random
import "input" for Keyboard
import "./dynamic" for Struct

var Start  = Platform.time
var Rand   = Random.new()

var fields = [
    "particleNum",
    "positions",
    "velocities",
    "lifetimes",
    "points",
    "numPoints",
    "saturation",
    "spread",
    "range",
    "reciprocate"
]
var ParticleFountain = Struct.create("ParticleFountain", fields)

class ParticleDisplay {
    construct new(particleNum, width, height) {
        Window.resize(width, height)
        Canvas.resize(width, height)
        Window.title = "Wren Particle System!"
        _pn = particleNum
        _w = width
        _h = height
        _df = 1 / 200 // say
        _pf = ParticleFountain.new(
            _pn,                      // particleNum
            List.filled(_pn * 2, 0),  // positions
            List.filled(_pn * 2, 0),  // velocities
            List.filled(_pn, 0),      // lifetimes
            List.filled(_pn, null),   // points
            0,                        // numPoints
            0.4,                      // saturation
            1.5,                      // spread
            1.5,                      // range
            false                     // reciprocate
        )
        for (i in 0..._pn) _pf.points[i] = Point.new(0, 0)
    }

    init() {
        Canvas.cls()
        _frames = 0 
    }

    updatePF() {
        var xidx = 0
        var yidx = 1
        var pointIdx = 0
        var recip = Fn.new { _pf.reciprocate ? _pf.range * Math.sin(Platform.time/1000) : 0 }
        for (idx in 0..._pf.particleNum) {
            var willDraw = false
            if (_pf.lifetimes[idx] <= 0) {
                if (Rand.float() < _df) {
                    _pf.lifetimes[idx]  = 2.5       // time to live
                    _pf.positions[xidx] = _w / 20   // starting position x
                    _pf.positions[yidx] = _h / 10   // and y

                     // starting velocities x and y
                     // randomized slightly so points reach different heights
                    _pf.velocities[xidx] = 10 * (_pf.spread * Rand.float() - _pf.spread / 2 + recip.call())
                    _pf.velocities[yidx] = (Rand.float() - 2.9) * _h / 20.5
                    _willDraw = true
                }
            } else {
                if (_pf.positions[yidx] > _h/10 && _pf.velocities[yidx] > 0) {
                    _pf.velocities[yidx] = _pf.velocities[yidx] * (-0.3)   // bounce
                }
                _pf.velocities[yidx] = _pf.velocities[yidx] + _df * _h / 10             // adjust velocity
                _pf.positions[xidx]  = _pf.positions[xidx] + _pf.velocities[xidx] * _df // adjust position x
                _pf.positions[yidx]  = _pf.positions[yidx] + _pf.velocities[yidx] * _df // and y
                _pf.lifetimes[idx]   = _pf.lifetimes[idx] - _df
                willDraw = true
            }
            if (willDraw) {  // gather all the points that are going to be rendered
                _pf.points[pointIdx] = Point.new((_pf.positions[xidx] * 10).floor,
                                                 (_pf.positions[yidx] * 10).floor)
                 pointIdx = pointIdx + 1
            }
            xidx = xidx + 2
            yidx = xidx + 1
            _pf.numPoints = pointIdx
        }
    }

    update() {
        if (Keyboard["Up"].justPressed) {
            _pf.saturation = Math.min(_pf.saturation + 0.1, 1)
        } else if (Keyboard["Down"].justPressed) {
            _pf.saturation = Math.max(_pf.saturation - 0.1, 0)
        } else if (Keyboard["PageUp"].justPressed) {
            _pf.spread = Math.min(_pf.spread + 1, 50)
        } else if (Keyboard["PageDown"].justPressed) {
            _pf.spread = Math.max(_pf.spread - 0.1, 0.2)
        } else if (Keyboard["Left"].justPressed) {
            _pf.range = Math.min(_pf.range + 0.1, 12)
        } else if (Keyboard["Right"].justPressed) {
            _pf.range = Math.max(_pf.range - 0.1, 0.1)
        } else if (Keyboard["Space"].justPressed) {
            _pf.reciprocate = !_pf.reciprocate
        } else if (Keyboard["Q"].justPressed) {
            Process.exit()
        }
        updatePF()
    }

    draw(alpha) {
        var c = Color.hsv((Platform.time % 5) * 72, _pf.saturation, 0.5, 0x7f)
        for (i in 0..._pf.numPoints) {
            Canvas.pset(_pf.points[i].x, _pf.points[i].y, c)
        }
        _frames = _frames + 1
        var now = Platform.time
        if (now - Start >= 1) {
            Start = now
            Window.title = "Wren Particle System!    (FPS = %(_frames))"
            _frames = 0
        }
    }
}

System.print("""

    Use UP and DOWN arrow keys to modify the saturation of the particle colors.
    Use PAGE UP and PAGE DOWN keys to modify the "spread" of the particles.
    Toggle reciprocation off / on with the SPACE bar.
    Use LEFT and RIGHT arrow keys to modify angle range for reciprocation.
    Press the "q" key to quit.
""")

var Game = ParticleDisplay.new(3000, 800, 800)

XPL0

Translation of: EasyLang
File:XPL0 Fountain.gif
func real RandomF;
return float(Ran(1000)) / 1000.;

def  N = 6000;
real X(N), Y(N);
real VX(N), VY(N);
def  Color = 15;
int  I, Ind;
[SetVid($12);
Ind:= 0;
repeat
    for I:= 1 to 32 do
        [Ind:= rem((Ind+1)/N);
        X(Ind):= 50. + RandomF;
        Y(Ind):= float(I)/4.;
        VX(Ind):= (RandomF - 0.5) * 0.4;
        VY(Ind):= 2. + RandomF*0.1;
        ];
    WaitForVSync;
    Clear;
    for I:= 0 to N-1 do
        [Point(fix(X(I)), 480-fix(Y(I)), Color);
        X(I):= X(I) + VX(I);  Y(I):= Y(I) + VY(I);
        VY(I):= VY(I) - 0.025;
        ];
until KeyHit;
]