Concept
This project mimics wave-like patterns that appear and disappear over time, drawing inspiration from the natural phenomena of water ripples. The project employs basic mathematical principles to create ripples on the canvas by representing every grid cell as a point in a system. By enabling users to create waves through clicks, drags, or even randomly generated events, user involvement brings the picture to life.
Each cell’s value is determined by the values of its neighboring cells, and the basic algorithm changes the grid state frame by frame. The simulation is both aesthetically pleasing and scientifically sound because of this behavior, which mimics how ripples dissipate and interact in real life.
Code Review
let cols;
let rows;
let current;
let previous;
let dampening = 0.99; // Controls ripple dissipation
let cellSize = 4; // Size of each cell
let baseStrength = 5000; // Base intensity of interaction
let interactStrength = baseStrength; // Dynamic intensity
let autoRipples = false; // Automatic ripple generation
let mousePressDuration = 0; // Counter for how long the mouse is pressed
function setup() {
createCanvas(windowWidth, windowHeight);
initializeGrid();
textSize(16);
fill(255);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
initializeGrid();
}
function initializeGrid() {
cols = floor(width / cellSize);
rows = floor(height / cellSize);
current = new Array(cols).fill(0).map(() => new Array(rows).fill(0));
previous = new Array(cols).fill(0).map(() => new Array(rows).fill(0));
}
function mouseDragged() {
mousePressDuration++;
interactStrength = baseStrength + mousePressDuration * 50; // Increase ripple strength
addRipple(mouseX, mouseY);
}
function mousePressed() {
mousePressDuration++;
interactStrength = baseStrength + mousePressDuration * 50; // Increase ripple strength
addRipple(mouseX, mouseY);
}
function mouseReleased() {
mousePressDuration = 0; // Reset the counter when the mouse is released
interactStrength = baseStrength; // Reset ripple strength
}
function keyPressed() {
if (key === 'A' || key === 'a') {
autoRipples = !autoRipples; // Toggle automatic ripples
} else if (key === 'R' || key === 'r') {
initializeGrid(); // Reset the grid
} else if (key === 'W' || key === 'w') {
dampening = constrain(dampening + 0.01, 0.9, 1); // Increase dampening
} else if (key === 'S' || key === 's') {
dampening = constrain(dampening - 0.01, 0.9, 1); // Decrease dampening
} else if (key === '+' && cellSize < 20) {
cellSize += 1; // Increase cell size
initializeGrid();
} else if (key === '-' && cellSize > 2) {
cellSize -= 1; // Decrease cell size
initializeGrid();
}
}
function addRipple(x, y) {
let gridX = floor(x / cellSize);
let gridY = floor(y / cellSize);
if (gridX > 0 && gridX < cols && gridY > 0 && gridY < rows) {
previous[gridX][gridY] = interactStrength;
}
}
function draw() {
background(0);
noStroke();
// Display ripples
for (let i = 1; i < cols - 1; i++) {
for (let j = 1; j < rows - 1; j++) {
// Cellular automata ripple algorithm
current[i][j] =
(previous[i - 1][j] +
previous[i + 1][j] +
previous[i][j - 1] +
previous[i][j + 1]) /
2 -
current[i][j];
// Apply dampening to simulate energy dissipation
current[i][j] *= dampening;
// Map the current state to a color intensity
let intensity = map(current[i][j], -interactStrength, interactStrength, 0, 255);
// Render each cell as a circle with its intensity
fill(intensity, intensity * 0.8, 255); // Blue-tinted ripple effect
ellipse(i * cellSize, j * cellSize, cellSize, cellSize);
}
}
// Swap buffers
let temp = previous;
previous = current;
current = temp;
if (autoRipples && frameCount % 10 === 0) {
// Add a random ripple every 10 frames
addRipple(random(width), random(height));
}
// Display info text
displayInfoText();
}
function displayInfoText() {
fill(255);
noStroke();
textAlign(LEFT, TOP);
text(
`Controls:
A - Toggle auto ripples
R - Reset grid
W - Increase dampening (slower fade)
S - Decrease dampening (faster fade)
+ - Increase cell size
- - Decrease cell size
Click and drag to create ripples.`,
10,
10
);
}
The grid is represented as two 2D arrays to store the current and past states of the simulation. The cell’s new value is computed as the average of its neighboring cells’ values, minus its value from the previous frame. This controllers the ripple propagation. I also used a dampening factor that reduces the intensity of the ripples over time, simulating the gradual dissipation of energy.
Sketch
The sketch has the following user interactions.
- A or a: Toggles automatic ripples on or off.
- R or r: Resets the grid, clearing all current ripples.
- W or w: Increases the dampening factor, making the ripples fade slowly.
- S or s: Decreases the dampening factor, making the ripples fade faster.
- +: Increases the cell size, which reduces the number of grid cells but increases their size.
- -: Decreases the cell size, increasing the grid resolution for finer ripples.
Challenges and Future Improvements
The challenge in this project is managing large grids which makes the simulation computationally expensive. Also, achieving a smooth and seamless ripple effect was a bit challenging. For future improvements. Implementing this in 3D could be quite interesting.
Reference
thecodingtrain.com/challenges/102-2d-water-ripple