
๐ Rust Code
use nannou::prelude::*;
use rand::{Rng, SeedableRng};
use std::collections::HashMap;
const WIN_W: u32 = 1000;
const WIN_H: u32 = 1000;
const GRID_COLS: i32 = 40;
const GRID_ROWS: i32 = 40;
const COLOR_SAT_RANGE: (f32, f32) = (0.5, 1.0);
const COLOR_VAL_RANGE: (f32, f32) = (0.4, 1.0);
const RADIUS_JITTER_RANGE: (f32, f32) = (0.2, 1.0);
#[derive(Clone)]
struct Cell {
x: f32,
y: f32,
radius: f32,
color: Rgb<f32>,
id: usize,
}
struct Model {
cols: i32,
rows: i32,
cell_w: f32,
cell_h: f32,
grid: HashMap<(i32, i32), Cell>,
rng: rand::rngs::SmallRng,
t: f32,
}
fn main() {
nannou::app(model).update(update).run();
}
fn model(app: &App) -> Model {
app.new_window()
.size(WIN_W, WIN_H)
.title("HashMap Grid Artwork (Refactored)")
.view(view)
.key_released(key_released)
.build()
.unwrap();
let seed = 42u64;
let rng = rand::rngs::SmallRng::seed_from_u64(seed);
let win = app.window_rect();
let cell_w = win.w() / GRID_COLS as f32;
let cell_h = win.h() / GRID_ROWS as f32;
let grid = create_grid(&win, GRID_COLS, GRID_ROWS, cell_w, cell_h, rng.clone());
Model {
cols: GRID_COLS,
rows: GRID_ROWS,
cell_w,
cell_h,
grid,
rng,
t: 0.0,
}
}
fn create_grid(
win: &Rect,
cols: i32,
rows: i32,
cell_w: f32,
cell_h: f32,
mut rng: rand::rngs::SmallRng,
) -> HashMap<(i32, i32), Cell> {
let mut grid = HashMap::new();
let mut id_counter = 0usize;
for row in 0..rows {
for col in 0..cols {
let x = map_range(
col as f32 + 0.5,
0.0,
cols as f32,
-win.w() / 2.0,
win.w() / 2.0,
);
let y = map_range(
row as f32 + 0.5,
0.0,
rows as f32,
-win.h() / 2.0,
win.h() / 2.0,
);
let color = random_color(&mut rng);
let base_radius = (cell_w.min(cell_h) * 0.45).abs();
let radius_jitter = rng.gen_range(RADIUS_JITTER_RANGE.0..RADIUS_JITTER_RANGE.1);
let radius = base_radius * radius_jitter;
grid.insert(
(col, row),
Cell {
x,
y,
radius,
color,
id: id_counter,
},
);
id_counter += 1;
}
}
grid
}
fn random_color(rng: &mut rand::rngs::SmallRng) -> Rgb<f32> {
let hue = rng.gen_range(0.0..1.0);
let sat = rng.gen_range(COLOR_SAT_RANGE.0..COLOR_SAT_RANGE.1);
let val = rng.gen_range(COLOR_VAL_RANGE.0..COLOR_VAL_RANGE.1);
hsv_to_rgb(hue, sat, val)
}
fn update(_app: &App, model: &mut Model, update: Update) {
model.t += update.since_last.as_secs_f32();
for cell in model.grid.values_mut() {
let phase = (cell.id as f32 * 0.13).sin();
let hue = ((model.t * 0.05) + phase).fract().abs();
let sat = 0.7;
let val = 0.7 + 0.2 * (model.t * 0.2 + phase).sin();
cell.color = hsv_to_rgb(hue, sat, val);
cell.radius *= 0.9999;
if cell.radius < 2.0 {
cell.radius *= 1.05;
}
}
}
fn key_released(app: &App, model: &mut Model, key: Key) {
if key == Key::Space {
model.rng = rand::rngs::SmallRng::from_entropy();
let win = app.window_rect();
model.grid = create_grid(
&win,
model.cols,
model.rows,
model.cell_w,
model.cell_h,
model.rng.clone(),
);
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(srgb(0.08, 0.08, 0.12));
for cell in model.grid.values() {
let r = cell.color.red;
let g = cell.color.green;
let b = cell.color.blue;
draw.ellipse()
.x_y(cell.x, cell.y)
.w_h(cell.radius * 2.0, cell.radius * 2.0)
.color(srgb(r, g, b));
draw.ellipse()
.x_y(cell.x, cell.y)
.w_h(cell.radius * 2.1, cell.radius * 2.1)
.no_fill()
.stroke_weight(1.2)
.stroke(srgb(
(r * 0.9).min(1.0),
(g * 0.9).min(1.0),
(b * 0.9).min(1.0),
));
}
draw.to_frame(app, &frame).unwrap();
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Rgb<f32> {
let i = (h * 6.0).floor() as i32;
let f = h * 6.0 - i as f32;
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
let (r, g, b) = match i.rem_euclid(6) {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
5 => (v, p, q),
_ => (1.0, 0.0, 0.0),
};
srgb(r, g, b)
}