Ripples With Cellular Automata

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

 

Leave a Reply

Your email address will not be published. Required fields are marked *