For this assignment, I just wanted to create a simple game. The collecting ball game has a paddle and boxes that are done from the top of the screen and the user moves the paddle to collide with as many boxes as possible. The concept is simple right now and it could be a starting point for a more complicated game
The Game
The users use the left and right arrows to play and some of the challenges I faced in this game were making the movement movement of the paddles smooth and also using matter.js itself was a bit challenging.
Future Improvements
As I have said earlier this could be a starting point for a more complicated game. One way would be to allow users to focus on collecting a specific shape and on other occasions a specific color and sometimes both specific shapes and colors and also changing the area of the screen to focus in the game from left to right meaning asking users only to collect boxes dropping from the top right or top left. Adding these levels would make the game more interesting. Also, some of the mechanics like the movement of the paddle could be improved.
Here I tried to simulate flocking behavior by creating a “school” of boids that move together in a coordinated, natural pattern. Each boid follows three core rules—separation, alignment, and cohesion—to avoid crowding, match direction, and stay close to neighbors. Together, these rules give rise to a lifelike group movement, where each boid is influenced by its surroundings to form a cohesive, dynamic flock.
Code Breakdown
Each Boid has properties for position, velocity, and acceleration, and the draw loop continuously applies flocking behaviors, updates the boid’s position, and renders it on the canvas with a trailing effect for smooth movement. The flock method combines the three behaviors by calculating forces based on nearby boids within defined distances. The separation force keeps boids from colliding, alignment adjusts direction to match neighboring boids, and cohesion steers toward the group center. The wrapEdgesmethod ensures that boids reappear on the opposite side if they move off-screen. An interactive feature, mouseDragged, adds new boids at the mouse location when dragged, adding flexibility to the simulation.
let boids = []; // Array to store boid instances
let w = 600, h = 600;
function setup() {
createCanvas(w, h);
background(0);
// Initialize boids with random positions
for (let i = 0; i < 50; i++) {
boids.push(new Boid(random(width), random(height)));
}
}
function draw() {
background(255, 5); // Slight trail effect
boids.forEach(boid => {
boid.flock(boids); // Apply flocking behavior
boid.update(); // Update position and velocity
boid.wrapEdges(); // Wrap around edges of the canvas
boid.show(); // Draw the boid
});
}
// Constants for boid behavior
let M = 2; // Max speed
let F = 0.28; // Max force
let sepDist = 25; // Desired separation distance
let aliDist = 50; // Alignment neighbor distance
let cohDist = 150; // Cohesion neighbor distance
// Class to represent a single boid
class Boid {
constructor(x, y) {
this.position = createVector(x, y);
this.velocity = createVector(random(-1, 1), random(-1, 1));
this.acceleration = createVector(0, 0);
}
// Method to apply flocking behavior
flock(boids) {
let separation = this.separate(boids); // Separation
let alignment = this.align(boids); // Alignment
let cohesion = this.cohesion(boids); // Cohesion
// Adjust weights for the forces
separation.mult(1.5);
alignment.mult(0.99);
cohesion.mult(0.99);
// Apply forces to acceleration
this.acceleration.add(separation);
this.acceleration.add(alignment);
this.acceleration.add(cohesion);
}
// Update position based on velocity and acceleration
update() {
this.velocity.add(this.acceleration);
this.velocity.limit(M); // Limit speed
this.position.add(this.velocity);
this.acceleration.mult(0); // Reset acceleration
}
// Wrap boids around the screen edges
wrapEdges() {
this.position.x = (this.position.x + w) % w;
this.position.y = (this.position.y + h) % h;
}
// Draw boid as a small circle
show() {
fill(255, 0, 0);
ellipse(this.position.x, this.position.y, 10);
}
// Separation behavior: Avoid crowding neighbors
separate(boids) {
let steer = createVector(0, 0);
let count = 0;
boids.forEach(other => {
let distance = p5.Vector.dist(this.position, other.position);
if (distance > 0 && distance < sepDist) {
let diff = p5.Vector.sub(this.position, other.position);
diff.normalize();
diff.div(distance);
steer.add(diff);
count++;
}
});
if (count > 0) steer.div(count);
if (steer.mag() > 0) {
steer.normalize();
steer.mult(M);
steer.sub(this.velocity);
steer.limit(F);
}
return steer;
}
// Alignment behavior: Steer towards the average heading of local flockmates
align(boids) {
let sum = createVector(0, 0);
let count = 0;
boids.forEach(other => {
let distance = p5.Vector.dist(this.position, other.position);
if (distance > 0 && distance < aliDist) {
sum.add(other.velocity);
count++;
}
});
if (count > 0) {
sum.div(count);
sum.normalize();
sum.mult(M);
let steer = p5.Vector.sub(sum, this.velocity);
steer.limit(F);
return steer;
}
return createVector(0, 0);
}
// Cohesion behavior: Steer towards the average position of local flockmates
cohesion(boids) {
let sum = createVector(0, 0);
let count = 0;
boids.forEach(other => {
let distance = p5.Vector.dist(this.position, other.position);
if (distance > 0 && distance < cohDist) {
sum.add(other.position);
count++;
}
});
if (count > 0) {
sum.div(count);
return this.seek(sum);
}
return createVector(0, 0);
}
// Seek method to steer boid towards a target position
seek(target) {
let desired = p5.Vector.sub(target, this.position);
desired.normalize();
desired.mult(M);
let steer = p5.Vector.sub(desired, this.velocity);
steer.limit(F);
return steer;
}
}
// Function to add a new boid on mouse drag
function mouseDragged() {
boids.push(new Boid(mouseX, mouseY));
}
Future Improvements
While the current simulation focuses on realistic flocking dynamics, future enhancements will center around visual and auditory immersion. Adding a colorful background and subtle sound effects could heighten the simulation’s atmosphere, making the school of boids feel more alive and enhancing the viewer’s experience.
I was genuinely surprised by how the small, foundational concepts we’ve covered in class were applied to create something as complex and captivating as the digital sand dune simulation in Mujo. Concepts like layering textures, oscillations, and shadow, and adjusting particle movement seemed straightforward in isolation, yet seeing them combined in such a way brought an unexpected level of realism to the dunes. Each grain of sand appeared to move independently, yet it all worked together to mimic the fluid, shifting nature of real sand in the wind. It made me realize that these basic tools and techniques, which seemed almost too simple at first, can be powerful building blocks when used thoughtfully.
The idea for this assignment is to simulate the behavior of different living organisms that follow a certain leader. I just thought the idea of following something was interesting. In this case, the lead insect is wandering; however, the rest of the colony is seeking him.
Code
let leader;
let followers = [];
let numFollowers = 100;
let desiredSeparation = 50; // Minimum distance between flies
function setup() {
createCanvas(windowWidth, windowHeight);
// Create leader fly with random starting position
leader = new Vehicle(random(width), random(height), true);
// Create follower flies
for (let i = 0; i < numFollowers; i++) {
followers.push(new Vehicle(random(width), random(height), false));
}
}
function draw() {
background(220, 30); // Transparent background for trail effect
// Leader wanders around
leader.wander();
leader.update();
leader.checkEdges(); // Ensure leader stays in bounds
leader.show();
// Followers seek the leader and avoid overlapping
for (let follower of followers) {
follower.separate(followers); // Avoid overlapping with other followers
follower.seek(leader.position); // Seek the leader's current position
follower.update();
follower.checkEdges(); // Ensure followers stay in bounds
follower.show();
}
}
// Vehicle class definition
class Vehicle {
constructor(x, y, isLeader = false) {
this.position = createVector(x, y);
this.velocity = createVector(random(-1, 1), random(-1, 1));
this.acceleration = createVector(0, 0);
this.r = 6;
this.maxspeed = isLeader ? 2 : 2.5; // Leader moves slightly faster
this.maxforce = 0.05; // Reduce max force for smoother movements
this.isLeader = isLeader;
this.wanderTheta = 0; // Used for wander behavior for leader
}
// Wander behavior for the leader
wander() {
let wanderR = 25; // Radius for our "wander circle"
let wanderD = 80; // Distance for the wander circle to be ahead of the vehicle
let change = 0.3;
this.wanderTheta += random(-change, change); // Randomly change wanderTheta
// Now we have to calculate the new target
let circleLoc = this.velocity.copy();
circleLoc.setMag(wanderD); // Move circle ahead of vehicle
circleLoc.add(this.position); // Make it relative to the vehicle's position
let h = this.velocity.heading(); // Heading angle of the vehicle
let circleOffset = createVector(wanderR * cos(this.wanderTheta + h), wanderR * sin(this.wanderTheta + h));
let target = p5.Vector.add(circleLoc, circleOffset);
this.seek(target); // Seek the wandering target
}
// Method to update location
update() {
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxspeed); // Limit speed
this.position.add(this.velocity);
this.acceleration.mult(0); // Reset acceleration to 0 after each update
}
// A method that calculates a steering force towards a target
seek(target) {
let desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target
desired.setMag(this.maxspeed); // Scale to maximum speed
let steer = p5.Vector.sub(desired, this.velocity); // Steering = Desired minus velocity
steer.limit(this.maxforce); // Limit to maximum steering force
this.applyForce(steer);
}
// Separation behavior: Avoid overlapping
separate(vehicles) {
let sum = createVector(0, 0);
let count = 0;
// Check all other vehicles
for (let other of vehicles) {
let d = p5.Vector.dist(this.position, other.position);
if ((d > 0) && (d < desiredSeparation)) {
// Calculate vector pointing away from neighbor
let diff = p5.Vector.sub(this.position, other.position);
diff.normalize();
diff.div(d); // Weight by distance
sum.add(diff);
count++; // Keep track of how many are too close
}
}
// Average out the forces
if (count > 0) {
sum.div(count);
sum.setMag(this.maxspeed); // Implement Reynolds: Steering = Desired - Velocity
let steer = p5.Vector.sub(sum, this.velocity);
steer.limit(this.maxforce);
this.applyForce(steer);
}
}
// Apply force to the vehicle
applyForce(force) {
this.acceleration.add(force);
}
// Check edges to ensure vehicle stays within the canvas
checkEdges() {
if (this.position.x > width) {
this.position.x = width;
this.velocity.x *= -1;
} else if (this.position.x < 0) {
this.position.x = 0;
this.velocity.x *= -1;
}
if (this.position.y > height) {
this.position.y = height;
this.velocity.y *= -1;
} else if (this.position.y < 0) {
this.position.y = 0;
this.velocity.y *= -1;
}
}
// Display the vehicle as a triangle pointing in the direction of velocity
show() {
let angle = this.velocity.heading() + PI / 2;
fill(this.isLeader ? color(255, 0, 0) : color(0, 255, 0)); // Leader is red, followers are green
stroke(0);
push();
translate(this.position.x, this.position.y);
rotate(angle);
beginShape();
vertex(0, -this.r * 2);
vertex(-this.r, this.r * 2);
vertex(this.r, this.r * 2);
endShape(CLOSE);
pop();
}
}
The simulation uses a vehicle class that has different attributes including and is leader attribute which becomes true when the vehicle is a leader. The class also has a seek-and-wander method. The wander method makes the vehicle wander around by creating a circle around the head of the vehicle and making the vehicle seek that circle. The seek method takes a target and makes the vehicle seek the target object.
Reflection and Future Improvements
The mechanics of the motion look good however the sketch might need more styling and polishing. Another thing would be making the leader controllable using a mouse.
These are the A3 digital prints of the visualization.
Pen Plotting
Concept and Inspiration
Initially, I had a different idea for my midterm project in mind, but after several attempts to implement it, I realized it wasn’t working as expected. I was looking for something fresh yet technically challenging to help me express my creativity. During my search, I stumbled upon a YouTube video about Perlin flow fields, which instantly clicked with my vision.
What is a Perlin Flow Field?
Perlin noise, developed by Ken Perlin, is a type of gradient noise used in computer graphics to create natural-looking textures, movement, and patterns. Unlike purely random noise, Perlin noise produces smoother transitions, making it ideal for simulating natural phenomena like clouds, terrain, or, in this case, particle motion.
A flow field, on the other hand, is a vector field that controls the movement of particles. When combined with Perlin noise, it creates a smooth, organic movement that feels like the particles are being guided by invisible forces.
Features
To add more interactivity for the project I added an expllosion and attraction effects. I took this from my previous project on super novas (exploding starts). These are the features contained in the project:
Mouse click: triggers attraction to the point on the screen where you cliked the mouse
Mouse release: triggers repulstion from the point on the screen where you are releasing the mouse
p: triggers a perlin noise i.e chnages the attraction or repusion motions to a smooth perlin noise.
a: adds 500 particles at random position
r: removes 500 particles
Code
let particles = [];
const initialNumParticles = 9000;
let numParticles = initialNumParticles;
let noiseScale = 0.005; // adjust for smoother noise transitions
let speed = 0.1; // lower the speed multiplier to slow down particles
let particleSize = 4;
const maxParticles = 9000; // set the maximum number of particles
const maxSpeed = 0.5; // limit maximum speed for each particle
let colorPalette = []; // define a color palette
let targetFlow = false; // control if flow should go towards the mouse
let targetPosition; // position of the mouse when pressed
let explode = false; // control the explosion effect
let perli = true;
// variables for high-resolution export
let scaleRatio = 1;
let exportRatio = 4; // scale down by 4x for working, export at 4x full resolution
let buffer;
let canvas;
let a3Paper = {
width: 3508, // a3 width in pixels at 300 PPI
height: 4960 // a3 height in pixels at 300 PPI
};
// initialize a color palette (e.g., warm, cool, or any themed palette)
function createColorPalette() {
colorPalette = [
color(244, 67, 54), // red
color(255, 193, 7), // yellow
color(33, 150, 243), // blue
color(76, 175, 80), // green
color(156, 39, 176) // purple
];
}
// particle class definition using vector methods
class Particle {
constructor(x, y) {
this.position = createVector(x, y); // particle's position
this.velocity = createVector(random(-0.5 / 16, 0.5 / 16), random(-0.5 / 16, 0.5 / 16)); // smaller initial velocity
this.size = particleSize;
this.color = random(colorPalette); // assign color from the color palette
}
// update the position of the particle using Perlin noise or towards the mouse
update() {
if (explode && targetPosition) {
let repulsion = p5.Vector.sub(this.position, targetPosition).normalize().mult(0.3); // stronger repulsion force
this.velocity.add(repulsion);
} else if (targetFlow && targetPosition) {
let direction = p5.Vector.sub(targetPosition, this.position).normalize().mult(speed * 10); // stronger force towards the mouse
this.velocity.add(direction);
} else if (perli) {
let noiseVal = noise(this.position.x * noiseScale, this.position.y * noiseScale, noiseScale);
let angle = TAU * noiseVal;
let force = createVector(cos(angle), sin(angle)).normalize().mult(speed); // normal flow
this.velocity.add(force);
}
this.velocity.limit(maxSpeed);
this.position.add(this.velocity);
}
// respawn the particle if it hits the canvas edges
checkEdges() {
if (this.position.x >= width || this.position.x <= 0 || this.position.y >= height || this.position.y <= 0) {
this.position = createVector(random(width), random(height)); // respawn at a random position
this.velocity = createVector(random(-0.5 / 16, 0.5 / 16), random(-0.5 / 16, 0.5 / 16)); // reset velocity with lower values
}
}
// render the particle on the canvas
render() {
fill(this.color); // use the particle's color
noStroke();
ellipse(this.position.x, this.position.y, this.size * 2, this.size * 2); // draw particle as an ellipse
}
}
// setup function to initialize particles and canvas
function setup() {
let w = a3Paper.width / exportRatio; // scaled-down width
let h = a3Paper.height / exportRatio; // scaled-down height
buffer = createGraphics(w, h); // create off-screen buffer for scaled drawings
canvas = createCanvas(w, h); // create main canvas
exportRatio /= pixelDensity(); // adjust export ratio based on pixel density of screen
createColorPalette(); // initialize color palette
for (let i = 0; i < numParticles; i++) {
particles.push(new Particle(random(width), random(height))); // create particles
}
stroke(255);
background(0);
}
// draw function to update and render particles
function draw() {
background(0, 10); // lower opacity for longer fading trails
// clear buffer and render particles to buffer
buffer.clear();
for (let i = 0; i < numParticles; i++) {
particles[i].update();
particles[i].checkEdges();
particles[i].render();
}
// draw buffer to the canvas
image(buffer, 0, 0);
}
// add particles dynamically (with maximum threshold)
function addParticles(n) {
let newCount = numParticles + n;
if (newCount > maxParticles) {
n = maxParticles - numParticles; // limit to the maxParticles threshold
}
for (let i = 0; i < n; i++) {
particles.push(new Particle(random(width, height))); // add new particles
}
numParticles += n;
}
// remove particles dynamically
function removeParticles(n) {
numParticles = max(numParticles - n, 0); // prevent negative number of particles
particles.splice(numParticles, n); // remove particles
}
// key press handling for dynamic control
function keyPressed() {
if (key === 'a') {
addParticles(500); // add 500 particles
} else if (key === 'r') {
removeParticles(500); // remove 500 particles
} else if (key === 'p') {
perli = true;
explode = false;
targetFlow = false;
} else if (key === 's') {
save('Wagaye_FlowField.png'); // save canvas as PNG
} else if (key === 'e') {
exportHighResolution();
}
}
// mouse press handling to redirect flow towards mouse position
function mousePressed() {
targetFlow = true; // activate flow towards mouse
explode = false; // no explosion during mouse press
targetPosition = createVector(mouseX, mouseY); // set the target position to the mouse press location
}
// mouse release handling to trigger explosion
function mouseReleased() {
targetFlow = false; // disable flow towards mouse
explode = true; // trigger explosion effect
targetPosition = createVector(mouseX, mouseY); // use the mouse release position as the repulsion center
}
// export high-resolution A3 print
function exportHighResolution() {
scaleRatio = exportRatio; // set scaleRatio to the export size
// create a new buffer at the full A3 size
buffer = createGraphics(scaleRatio * width, scaleRatio * height);
// redraw everything at the export size
draw();
// get current timestamp for file naming
let timestamp = new Date().getTime();
// save the buffer as a PNG file
save(buffer, `A3_Print_${timestamp}`, 'png');
// reset scale ratio back to normal working size
scaleRatio = 1;
// re-create buffer at the original working size
buffer = createGraphics(width, height);
draw();
}
As you can see from the code the plot has three states explosion, target flow and perlin flow. And depending on the users interaction the plot changes. Maybe the most important part of this code is the perlin noise snippet. The code takes the particle’s coordinates, scales them down to control the smoothness of the transitions, and feeds them into the Perlin noise function. The result is a value between 0 and 1, which is then mapped to an angle in radians to determine the particle’s direction. This angle is used to create a vector, setting the direction and speed at which the particle moves. By continuously updating the particle’s velocity with these noise-driven vectors, the particles move in a way that feels organic, mimicking natural phenomena like wind currents or flowing water.
Challanges
The main challange was finding an otimal value eof noise scale and particle numbers to make the flow natural. Creating the explosion and attraction feature was also a bit challanging.
Future Improvments
A potential improvement to this project is the integration of music to make the flow fields react dynamically to beats. By incorporating an API or sound analysis tool that extracts key moments in the music, such as the kick drum or snare, the flow fields could “dance” to the rhythm. For instance, when a kick is detected, the particles could explode outward, and when the snare hits, they could contract or move toward a central point. During quieter sections, the particles could return to a smooth, flowing motion driven by Perlin noise. This interaction would create a synchronized visual experience where the flow fields change and evolve with the music, adding an extra layer of engagement and immersion.
The project’s main idea is to create a particle system with different interactive features. The generated art is still abstract to me right now. I was inspired by Memo Akten’s work which contains a particle system-like feature. Right now the project is still in its early stages but hopefully, I will find a more solid version of the visualization by the end of this week.
Features
The features I want this generative visualization to have include:
Movement in the direction of the mouse
Line connections
Random motion
Concentric polygon arrangements
Particle system features: lifetime and individual particle motion
Current Progress
Currently, I have done some of the features of this visual system. However, Maintaining a line connection when the arrangement of particles is changing is a bit difficult because the line connection creates a random motion. You can test this in the draft visualization provided below.
Press 1: to create a random motion
Press 2: to create line connections
Prees 4: to create a circular arrangement
Press 5: to create a triangular arrangement
Reflection and Improvement
So far what I have looks okay but I want to add interactivity with a mouse and also add tails to the particles. Still, the most important thing I have to figure out is to maintain the arrangement when there is a line connection.
My inspiration for this sketch is Memo Akten. However, I also wanted to include something I have seen before. I initially started experimenting with 16 points to create the Tesseract, a 4-dimensional cube. Later I found that to be a bit difficult and moved on to just experimenting with multiple points and connections to create something that has a DNA helix-like structure but also oscillates and moves.
Sketch
Highlight of Code
function generateLayers(n, r) {
let layerPoints = [];
let angleStep = TWO_PI / n; // divide the circle into equal segments
// create points in a circular layout
for (let i = 0; i < n; i++) {
let angle = i * angleStep;
let x = r * cos(angle);
let y = r * sin(angle);
layerPoints.push({ x: x, y: y });
}
points.push(layerPoints); // add this layer to the points array
// base case: stop when there's only one point in the layer
if (n > 1) {
// recursive call to generate the next layer with half the points and a smaller radius
generateLayers(floor(n / 2), r * 0.7); // reduce the radius and number of points for each layer
}
}
This is the function that generates the layers of points. The function generates circular layers based on the number of layers and the radius of each layer. Changing the radius and the number of points changes the visualization completely
The inspiration for this sketch is a supernova. A supernova is a powerful and luminous explosion that occurs at the end of a star’s life cycle. It marks the catastrophic death of a star, resulting in the release of an immense amount of energy and light, often outshining entire galaxies for a brief period.
Sketch
Highlight of Code
let stars = [];
let numStars = 5000;
let attractor;
let state = 'attract';
let attractionStrength = 0.1;
let explosionSpeed = 2;
let explosionStartFrame;
let damping = 0.95;
// array of possible attractor positions
let attractorPositions = [];
let attractorIndex = 0;
function setup() {
createCanvas(800, 800);
// define the attractor positions: center, 1/4, and 3/4
attractorPositions = [
createVector(width / 2, height / 2), // center
createVector(width / 4, height / 4), // top-left (1/4)
createVector(3 * width / 4, 3 * height / 4) // bottom-right (3/4)
];
attractor = attractorPositions[attractorIndex];
// initialize stars with random positions
for (let i = 0; i < numStars; i++) {
let x = random(width);
let y = random(height);
stars.push(new Star(x, y));
}
}
function draw() {
background(0);
// updates stars based on the current state
for (let star of stars) {
if (state === 'attract') {
star.attractToCenter(attractor, attractionStrength);
} else if (state === 'explode') {
star.attractToCenter(attractor, -1 * attractionStrength);
}
star.display();
}
if (state === 'attract' && stars.every(star => star.isAtCenter())) {
state = 'explode';
explosionStartFrame = frameCount;
} else if (state === 'explode' && frameCount > explosionStartFrame + 180) {
state = 'attract';
changeAttractorPosition(); // change attractor position after each explosion
}
}
// change the attractor's position
function changeAttractorPosition() {
attractorIndex = (attractorIndex + 1) % attractorPositions.length; // cycle through positions
attractor = attractorPositions[attractorIndex];
}
// star class
class Star {
constructor(x, y) {
this.position = createVector(x, y);
this.initialPosition = createVector(x, y);
this.velocity = createVector(0, 0);
this.exploding = false;
this.distanceToCenter = p5.Vector.dist(this.position, attractor); // initial distance to center
}
// attracts/repulse to/from the center
attractToCenter(center, attractionStrength) {
let force = p5.Vector.sub(center, this.position);
force.normalize();
force.mult(attractionStrength);
this.velocity.add(force); // add the positive attraction force to the velocity
this.velocity.mult(damping); // apply damping to gradually slow down stars as they approach the center
this.position.add(this.velocity);
this.distanceToCenter = p5.Vector.dist(this.position, center);
}
// check if the star is close to the center
isAtCenter() {
return this.distanceToCenter < 5;
}
display() {
noStroke();
fill(255, 204, 0); // yellow color for the stars
ellipse(this.position.x, this.position.y, 5);
}
}
The full code for the project is attached above. The hardest part was getting the attractor right. I’m using the attractor as a repulsor with a negative attraction strength which makes the force vetro opposite and that was challenging to implement at first. I had also to include a dumping factor to make the simulation go smoothly otherwise the force will just pull all the stars to the attractor and repulse them because the vector becomes negative. For instance, changing the dumping to one makes the simulation an infinite loop of attraction and repulsion without any obvious explosion.
The concept for this project was to create a rain simulation with lightning. Rain is not something we experience here usually but we can simulate it. I don’t have a specific video or picture in mind; I just went to p5 to simulate raindrops, ripples, and lightning.
Code Highlight
function generateLightningBolt() {
lightningBolt = [];
let x = random(width * 0.25, width * 0.75);
let y = 0;
lightningBolt.push(createVector(x, y));
for (let i = 0; i < 8; i++) {
x += random(-30, 30);
y += random(30, 60);
lightningBolt.push(createVector(x, y));
}
}
function drawLightningBolt() {
stroke(200, 200, 255);
strokeWeight(4);
noFill();
beginShape();
for (let i = 0; i < lightningBolt.length; i++) {
vertex(lightningBolt[i].x, lightningBolt[i].y);
}
endShape();
}
function drawSparkles() {
noStroke();
for (let i = 0; i < 20; i++) {
let sparkleX = lightningBolt[lightningBolt.length - 1].x + random(-40, 40);
let sparkleY = lightningBolt[lightningBolt.length - 1].y + random(-40, 40);
fill(255, 255, 255, random(150, 255));
ellipse(sparkleX, sparkleY, random(3, 8), random(3, 8));
}
}
The part of the code that I’m proud of is the lighting. The lighting has two elements, the bolt and the sparkles. For the bolt, I created a random vector starting from a random position at the top of the screen and started adding random values from fixed intervals to that random vector’s coordinates to get the next subsequent random vectors for the zigzag-like pattern of the lighting bolt. Finally, I plotted those vectors by converting each point to a vertex and using the begin and end shape functions to connect those vertices. For the sparkles, I used 20 ellipses the last vertex of the lightning bolt. Each ellipse has a random major and minor axis length fixed between two values.
Sketch
Future Improvements and Reflection
The simulation is a basic representation of rain with a thunderstorm. However, adding sound might make the experience full and realistic. The sounds are mainly for the rain, ripples, and thunder.
This project explores a unique blend of randomness and user interaction through a dynamic color random walk. The random walker is designed with two key features that make its movement and visual output both unpredictable and engaging.
Key Features:
Biased Random Movement:
The random walk incorporates a 40% probability of moving towards the direction of the user’s mouse, introducing an element of interactivity. The remaining 60% of the time, the walker moves in a random direction. However, this randomness is also biased; the walker does not have an equal probability of moving left, right, up, or down, which introduces subtle variations in the walker’s path and adds complexity to its movement.
Dynamic Color Evolution:
The visual aspect of the walker’s path is enhanced by a dynamic color mapping that evolves as the walker moves across the canvas. The color of each point is determined by its x coordinate, creating a spectrum of hues that shift horizontally across the canvas. Simultaneously, the brightness of each point is mapped to the y coordinate, resulting in a gradient that changes vertically. This color evolution adds a rich, visual narrative to the walker’s journey.
Spatial Harmony:
To ensure the visual clarity of the path, the walker only draws a point if it has moved a minimum distance from its previous position. This spacing prevents the points from overlapping, creating a more aesthetically pleasing and well-defined pattern.
Code
let walker;
let stepSize = 8;
let minDistance = 30;
function setup() {
createCanvas(600, 600);
walker = new ColorWalker();
background(0);
}
function draw() {
walker.step();
walker.render();
}
class ColorWalker {
constructor() {
this.x = width / 2;
this.y = height / 2;
this.prevX = this.x;
this.prevY = this.y;
}
step() {
let direction = random(1) < 0.3 ? 0 : 1;
let moveX = 0;
let moveY = 0;
if (direction === 0) {
if (mouseX > this.x) {
moveX = stepSize;
} else if (mouseX < this.x) {
moveX = -stepSize;
}
if (mouseY > this.y) {
moveY = stepSize;
} else if (mouseY < this.y) {
moveY = -stepSize;
}
}
else {
let choice = random(1);
if (choice <= 0.4) {
moveX = stepSize;
} else if (choice <= 0.6) {
moveX = -stepSize;
} else if (choice <= 0.8) {
moveY = stepSize;
} else {
moveY = -stepSize;
}
}
this.x += moveX;
this.y += moveY;
this.x = constrain(this.x, 0, width - 1);
this.y = constrain(this.y, 0, height - 1);
}
render() {
let distance = dist(this.x, this.y, this.prevX, this.prevY);
if (distance > minDistance) {
let hue = map(this.x, 0, width, 0, 360);
let brightness = map(this.y, 0, height, 100, 50);
colorMode(HSB, 360, 100, 100);
strokeWeight(15);
stroke(hue, 100, brightness);
strokeWeight(15);
point(this.x, this.y);
this.prevX = this.x;
this.prevY = this.y;
}
}
}
Potential Improvements
I want to make this walker a self-avoiding one I think that would be interesting visualization.