

๐ Rust Code
use nannou::prelude::*;
use nannou::noise::{NoiseFn, Perlin};
use rand::Rng;
struct Sperm {
head: Point2,
velocity: Vec2,
speed: f32,
phase: f32,
amp: f32,
swimming_phase: f32,
energy: f32,
head_bob_phase: f32,
head_bob_freq: f32,
}
struct Model {
sperms: Vec<Sperm>,
time: f32,
noise: Perlin,
}
fn main() {
nannou::app(model).update(update).view(view).run();
}
fn model(app: &App) -> Model {
app.new_window()
.size(800, 800)
.view(view)
.build()
.unwrap();
Model {
sperms: Vec::new(),
time: 0.0,
noise: Perlin::new(),
}
}
fn update(app: &App, model: &mut Model, update: Update) {
let dt = update.since_last.as_secs_f32();
model.time += dt;
let win = app.window_rect();
while model.sperms.len() < 1500 {
let mut rng = rand::thread_rng();
let y = rng.gen_range((win.bottom() + 20.0)..(win.top() - 20.0));
let speed = rng.gen_range(57.0..105.0);
let phase = rng.gen_range(0.0..TAU);
let amp = rng.gen_range(0.8..1.4);
let swimming_phase = rng.gen_range(0.0..TAU);
let head_bob_phase = rng.gen_range(0.0..TAU);
let head_bob_freq = rng.gen_range(2.5..4.5);
model.sperms.push(Sperm {
head: pt2(win.right() + 60.0, y),
velocity: vec2(-1.0, 0.0),
speed,
phase,
amp,
swimming_phase,
energy: rng.gen_range(0.7..1.0),
head_bob_phase,
head_bob_freq,
});
}
for s in model.sperms.iter_mut() {
s.swimming_phase += dt * 6.0 * s.amp;
s.head_bob_phase += dt * s.head_bob_freq;
s.energy = (s.energy - dt * 0.05).max(0.3);
let lateral_oscillation = (s.swimming_phase * 0.8).sin() * 6.0 * s.amp * s.energy;
let forward_oscillation = (s.swimming_phase * 0.5).cos() * 2.0 * s.amp;
let noise_x = model.noise.get([s.phase as f64 * 0.2, model.time as f64 * 0.4]) as f32;
let noise_y = model.noise.get([s.phase as f64 * 0.2 + 100.0, model.time as f64 * 0.4]) as f32;
let head_bob = (s.head_bob_phase).sin() * 4.0 * s.energy;
let head_bob_noise = model.noise.get([s.phase as f64 * 0.15, model.time as f64 * 0.6]) as f32 * 2.5;
s.velocity = vec2(
-1.0 + forward_oscillation * 0.08 + noise_x * 0.1,
lateral_oscillation * 0.015 + noise_y * 0.08
).normalize();
let rhythm_variation = (s.swimming_phase * 1.2).sin() * 0.15;
let current_speed = s.speed * s.energy * (1.0 + rhythm_variation);
s.head += s.velocity * current_speed * dt;
s.head.y += (head_bob + head_bob_noise) * dt * 25.0;
}
let tail_len = 80.0;
model.sperms.retain(|s| s.head.x > win.left() - tail_len - 10.0);
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
let tail_len = 80.0;
let segments = 25;
let base_amplitude = 8.0;
let wavelength = 0.6;
let wave_speed = 4.5;
for s in model.sperms.iter() {
let head = s.head;
let dir = if s.velocity.length() > 0.0 {
s.velocity.normalize()
} else {
vec2(-1.0, 0.0)
};
let perp = vec2(-dir.y, dir.x);
let mut points: Vec<Point2> = Vec::with_capacity(segments + 1);
for i in 0..=segments {
let t = i as f32 / segments as f32;
let base = head + dir * (-t * tail_len);
let primary_wave = (model.time * wave_speed + s.phase + t / wavelength).sin();
let secondary_wave = (model.time * wave_speed * 0.6 + s.phase * 1.3 + t / (wavelength * 1.2)).sin() * 0.3;
let rhythm_factor = (s.swimming_phase * 0.7 + t * 1.5).sin() * 0.2;
let noise_val = model.noise.get([
(s.phase as f64) * 0.25,
(model.time as f64) * 0.8 + (t as f64) * 2.0,
]) as f32;
let intensity = (t * t * 1.5).min(1.0);
let local_amp = base_amplitude * s.amp * s.energy;
let y_offset = (primary_wave * 0.6 + secondary_wave + rhythm_factor + noise_val * 0.4)
* local_amp * intensity;
let offset = perp * y_offset;
points.push(base + offset);
}
draw.ellipse().xy(head).radius(2.5).color(WHITE);
for i in 0..(points.len() - 1) {
let t = i as f32 / (points.len() - 1) as f32;
let alpha = 1.0 - (t * 0.9);
let thickness = 2.0 * (1.0 - t * 0.6);
draw.line()
.start(points[i])
.end(points[i + 1])
.weight(thickness.max(0.3))
.color(rgba(1.0, 1.0, 1.0, alpha));
}
}
draw.to_frame(app, &frame).unwrap();
}