Day 3 - Art 1
Exactly 42 lines of code
Fluid Particle Shapes
This generative art piece features a dynamic system of particles that transition between various states. The particles move in a dispersing pattern or arrange themselves into geometric shapes, such as circles and squares. The transitions between these states are smooth, with particles gradually shifting toward new positions, creating a fluid and evolving visual experience. The number of particles is adjusted to fit perfectly into the selected grid, and their motion is influenced by random velocities or interpolated target positions.
// Global variables
let p = []; // Array to store particle objects
let s = "particles"; // Current state or shape of the system
let t = 0; // Interpolation factor for smooth transitions
let n = 500; // Number of particles
let w = 400, h = 400; // Canvas dimensions
let d = 0.05; // Speed multiplier for dispersing motion
let nextShape = "circle"; // The next shape to transition to
let canvas; // Canvas element
function setup() {
// Create the canvas and attach it to a parent HTML element
canvas = createCanvas(w, h);
canvas.parent('canvas-container');
// Adjust `n` to the nearest perfect square for clean grid layouts
n = floor(sqrt(n)) ** 2;
// Initialize particles with random positions and velocities
for (let i = 0; i < n; i++) {
p.push({
x: random(w), // Initial x-coordinate
y: random(h), // Initial y-coordinate
vx: random(-1, 1), // Random x-velocity
vy: random(-1, 1), // Random y-velocity
});
}
// Set the initial target positions for the particles
setTargetPositions();
}
function draw() {
// Set the background color to a dark gray
background(30);
// Increment the interpolation factor for smooth transitions
t += 1 / 60;
// Reset the transition and switch states when `t` reaches 1
if (t >= 1) {
t = 0; // Reset the interpolation factor
if (s === "particles") {
// Transition from "particles" to the next shape
s = nextShape;
nextShape = (s === "circle") ? "square" : "circle";
} else {
// Transition back to "particles" (dispersing)
s = "particles";
}
setTargetPositions(); // Update target positions for the new state
}
// Update and draw each particle
p.forEach(e => {
if (s === "dispersing") {
// Apply random velocities for the "dispersing" state
e.x += e.vx * d;
e.y += e.vy * d;
} else {
// Interpolate particle positions towards their target
e.x = lerp(e.x, e.tx, t);
e.y = lerp(e.y, e.ty, t);
}
// Draw the particle as a small white ellipse
fill(255);
noStroke();
ellipse(e.x, e.y, 5);
});
}
function setTargetPositions() {
// Update target positions based on the current state `s`
if (s === "square") {
// Arrange particles in a square grid
let gridSize = sqrt(n); // Number of particles per row/column
let a = min(w, h) / 2; // Size of the square
let b = (w - a) / 2; // Horizontal offset to center the square
let c = (h - a) / 2; // Vertical offset to center the square
p.forEach((e, i) => {
let r = i % gridSize; // Column index
let o = floor(i / gridSize); // Row index
let g = a / gridSize; // Grid cell size
e.tx = b + r * g; // Target x-coordinate
e.ty = c + o * g; // Target y-coordinate
});
} else if (s === "circle") {
// Arrange particles in a circular pattern
let a = min(w, h) / 4; // Radius of the circle
let b = w / 2; // x-coordinate of the circle center
let c = h / 2; // y-coordinate of the circle center
let g = []; // Array to store valid positions on the circle
// Generate points within a bounding square and filter for circle
for (let x = -a; x <= a; x += 20) {
for (let y = -a; y <= a; y += 20) {
if (dist(x, y, 0, 0) <= a) {
g.push({ x: b + x, y: c + y });
}
}
}
// Assign target positions to particles
p.forEach((e, i) => {
e.tx = g[i % g.length].x;
e.ty = g[i % g.length].y;
});
} else {
// Assign random target positions for the "particles" state
p.forEach(e => {
e.tx = random(w);
e.ty = random(h);
});
}
}