Week #11

Introduction
Cellular automata are fascinating systems where simple rules applied to cells in a grid lead to complex and often mesmerizing patterns. While 2D cellular automata like Conway’s Game of Life are well-known, extending the concept into 3D opens up a whole new dimension of possibilities—literally! In this project, I used p5.js to create an interactive 3D cellular automaton, combining computational elegance with visual appeal.

The Project

  1. The Grid
    The automaton uses a 3D array to represent the cells. Each cell is a small cube, and the entire grid is visualized in a 3D space using p5.js’s WEBGL mode.
  2. Random Initialization
    The grid starts with a random distribution of alive and dead cells, giving each simulation a unique starting point.
  3. Rule Application
    At each frame, the automaton calculates the next state of every cell based on its neighbors. The updated grid is then displayed.
  4. Interactivity
    Using p5.js’s orbitControl(), users can rotate and zoom into the 3D grid, exploring the automaton’s patterns from different perspectives.Code

    let grid, nextGrid;
    let cols = 10, rows = 10, layers = 10; // Grid dimensions
    let cellSize = 20;
    
    function setup() {
      createCanvas(600, 600, WEBGL);
      grid = create3DArray(cols, rows, layers);
      nextGrid = create3DArray(cols, rows, layers);
      randomizeGrid();
    }
    
    function draw() {
      background(30);
      orbitControl(); // Allows rotation and zoom with mouse
      
      // Center the grid
      translate(-cols * cellSize / 2, -rows * cellSize / 2, -layers * cellSize / 2);
      
      // Draw cells
      for (let x = 0; x < cols; x++) {
        for (let y = 0; y < rows; y++) {
          for (let z = 0; z < layers; z++) {
            if (grid[x][y][z] === 1) {
              push();
              translate(x * cellSize, y * cellSize, z * cellSize);
              fill(255);
              noStroke();
              box(cellSize * 0.9); // A slightly smaller cube for spacing
              pop();
            }
          }
        }
      }
      
      updateGrid(); // Update the grid for the next frame
    }
    
    // Create a 3D array
    function create3DArray(cols, rows, layers) {
      let arr = new Array(cols);
      for (let x = 0; x < cols; x++) {
        arr[x] = new Array(rows);
        for (let y = 0; y < rows; y++) {
          arr[x][y] = new Array(layers).fill(0);
        }
      }
      return arr;
    }
    
    // Randomize the initial state of the grid
    function randomizeGrid() {
      for (let x = 0; x < cols; x++) {
        for (let y = 0; y < rows; y++) {
          for (let z = 0; z < layers; z++) {
            grid[x][y][z] = random() > 0.7 ? 1 : 0; // 30% chance of being alive
          }
        }
      }
    }
    
    // Update the grid based on rules
    function updateGrid() {
      for (let x = 0; x < cols; x++) {
        for (let y = 0; y < rows; y++) {
          for (let z = 0; z < layers; z++) {
            let neighbors = countNeighbors(x, y, z);
            if (grid[x][y][z] === 1) {
              // Survival: A live cell stays alive with 4-6 neighbors
              nextGrid[x][y][z] = neighbors >= 4 && neighbors <= 6 ? 1 : 0;
            } else {
              // Birth: A dead cell becomes alive with exactly 5 neighbors
              nextGrid[x][y][z] = neighbors === 5 ? 1 : 0;
            }
          }
        }
      }
      // Swap grids
      let temp = grid;
      grid = nextGrid;
      nextGrid = temp;
    }
    
    // Count the alive neighbors of a cell
    function countNeighbors(x, y, z) {
      let count = 0;
      for (let dx = -1; dx <= 1; dx++) {
        for (let dy = -1; dy <= 1; dy++) {
          for (let dz = -1; dz <= 1; dz++) {
            if (dx === 0 && dy === 0 && dz === 0) continue; // Skip the cell itself
            let nx = (x + dx + cols) % cols;
            let ny = (y + dy + rows) % rows;
            let nz = (z + dz + layers) % layers;
            count += grid[nx][ny][nz];
          }
        }
      }
      return count;
    }
    

    Future Enhancements

    • Custom Rules: Experiment with different neighbor conditions to discover new behaviors.
    • Larger Grids: Scale up the grid size for more complex patterns (optimize for performance).
    • Color Variations: Assign colors based on neighbor count or generation age.
    • Save States: Let users save and reload interesting configurations.

Leave a Reply

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