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