Introduction: Inspiration and Thought Process
When approaching this project, I wanted to create something that felt organic, dynamic, and visually engaging while staying true to my interest in systems, patterns, and movement. I’ve always been fascinated by the hidden structures that govern the natural world—whether it’s the way air currents shape cloud formations, how magnetic fields interact with charged particles, or how fluid dynamics influence ocean currents. These invisible forces dictate movement and structure on both microscopic and massive scales, yet they often go unnoticed in daily life.
This fascination led me to explore generative art as a way to reveal these hidden forces. By using a flow field generated through Perlin noise, I aimed to simulate an abstract yet realistic movement of particles through a force-driven system. The goal was to make the movement feel both unpredictable and structured—like a balance between chaos and order, mirroring how natural systems operate.
Another reason this concept resonated with me is my interest in interactive media and computational design. I see creative coding as a bridge between logic and aesthetics—using algorithms not just to solve problems but to create emotionally engaging experiences. This project became a way to explore how simple rules (vector fields and movement constraints) can lead to complex, emergent behaviors.
How I Decided on the Approach
Initially, I considered different ways of visualizing movement:
- Cellular automata (which follows a discrete rule set)
- Particle systems with basic physics (which simulate real-world gravity and collisions)
- Algorithmic drawing techniques (such as recursive fractals)
However, I specifically wanted smooth, flowing movement, where particles appear to drift through an unseen force field rather than follow rigid, predictable patterns. This led me to research flow fields, a technique often used in generative art to create dynamic motion based on vector fields derived from Perlin noise. The key insight was that by giving each particle a force vector at every point in space, I could create an artwork where movement itself became the visual expression.
Connecting to My Interests
This project ties into my broader interest in interactive systems and generative design. In the future, I could see expanding this into an interactive piece, where users could manipulate the flow field in real-time, changing the behavior of the particles with gestures or sound inputs. Additionally, this exploration of emergent patterns aligns with my curiosity about how simple rules can create complexity, something that applies not just to visual art but also to fields like engineering, physics, and artificial intelligence.
Ultimately, this artwork serves as both a study of motion and structure and a reflection of how natural forces shape our world in ways we don’t always perceive.
My code:
let scl = 20; // Scale of the grid
let zOff = 0; // Noise offset for animation
cols = floor(width / scl);
rows = floor(height / scl);
flowField = new Array(cols * rows);
for (let i = 0; i < 500; i++) {
particles.push(new Particle());
background(0, 10); // Faint trail effect
// Generate the flow field using Perlin noise
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
let index = x + y * cols;
let angle = noise(xOff, yOff, zOff) * TWO_PI * 4;
let v = p5.Vector.fromAngle(angle);
// Update and display particles
for (let particle of particles) {
particle.follow(flowField);
this.pos = createVector(random(width), random(height));
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.color = color(random(255), random(255), random(255), 100);
let x = floor(this.pos.x / scl);
let y = floor(this.pos.y / scl);
let index = x + y * cols;
let force = vectors[index];
this.vel.limit(this.maxSpeed);
if (this.pos.x > width) this.pos.x = 0;
if (this.pos.x < 0) this.pos.x = width;
if (this.pos.y > height) this.pos.y = 0;
if (this.pos.y < 0) this.pos.y = height;
point(this.pos.x, this.pos.y);
let particles = [];
let flowField;
let cols, rows;
let scl = 20; // Scale of the grid
let zOff = 0; // Noise offset for animation
function setup() {
createCanvas(600, 600);
cols = floor(width / scl);
rows = floor(height / scl);
flowField = new Array(cols * rows);
// Create particles
for (let i = 0; i < 500; i++) {
particles.push(new Particle());
}
}
function draw() {
background(0, 10); // Faint trail effect
// Generate the flow field using Perlin noise
let yOff = 0;
for (let y = 0; y < rows; y++) {
let xOff = 0;
for (let x = 0; x < cols; x++) {
let index = x + y * cols;
let angle = noise(xOff, yOff, zOff) * TWO_PI * 4;
let v = p5.Vector.fromAngle(angle);
flowField[index] = v;
xOff += 0.1;
}
yOff += 0.1;
}
zOff += 0.01;
// Update and display particles
for (let particle of particles) {
particle.follow(flowField);
particle.update();
particle.edges();
particle.show();
}
}
// Particle class
class Particle {
constructor() {
this.pos = createVector(random(width), random(height));
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.maxSpeed = 2;
this.color = color(random(255), random(255), random(255), 100);
}
follow(vectors) {
let x = floor(this.pos.x / scl);
let y = floor(this.pos.y / scl);
let index = x + y * cols;
let force = vectors[index];
this.applyForce(force);
}
applyForce(force) {
this.acc.add(force);
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
edges() {
if (this.pos.x > width) this.pos.x = 0;
if (this.pos.x < 0) this.pos.x = width;
if (this.pos.y > height) this.pos.y = 0;
if (this.pos.y < 0) this.pos.y = height;
}
show() {
stroke(this.color);
strokeWeight(2);
point(this.pos.x, this.pos.y);
}
}
let particles = [];
let flowField;
let cols, rows;
let scl = 20; // Scale of the grid
let zOff = 0; // Noise offset for animation
function setup() {
createCanvas(600, 600);
cols = floor(width / scl);
rows = floor(height / scl);
flowField = new Array(cols * rows);
// Create particles
for (let i = 0; i < 500; i++) {
particles.push(new Particle());
}
}
function draw() {
background(0, 10); // Faint trail effect
// Generate the flow field using Perlin noise
let yOff = 0;
for (let y = 0; y < rows; y++) {
let xOff = 0;
for (let x = 0; x < cols; x++) {
let index = x + y * cols;
let angle = noise(xOff, yOff, zOff) * TWO_PI * 4;
let v = p5.Vector.fromAngle(angle);
flowField[index] = v;
xOff += 0.1;
}
yOff += 0.1;
}
zOff += 0.01;
// Update and display particles
for (let particle of particles) {
particle.follow(flowField);
particle.update();
particle.edges();
particle.show();
}
}
// Particle class
class Particle {
constructor() {
this.pos = createVector(random(width), random(height));
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.maxSpeed = 2;
this.color = color(random(255), random(255), random(255), 100);
}
follow(vectors) {
let x = floor(this.pos.x / scl);
let y = floor(this.pos.y / scl);
let index = x + y * cols;
let force = vectors[index];
this.applyForce(force);
}
applyForce(force) {
this.acc.add(force);
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
edges() {
if (this.pos.x > width) this.pos.x = 0;
if (this.pos.x < 0) this.pos.x = width;
if (this.pos.y > height) this.pos.y = 0;
if (this.pos.y < 0) this.pos.y = height;
}
show() {
stroke(this.color);
strokeWeight(2);
point(this.pos.x, this.pos.y);
}
}