CA air imitation issues

Hey! So I’m trying to create and prototype a small simulation for fun. I’m using CA(Cellular Automata) in 2D environment to imitate air. Now I done some progress and ironed out some issues I think… My math is sub par and a lot of what I’m doing is guesswork but somehow I got the code this far.

Here is how it works.

  • I create 2 list with cell objects and draw them in a grid
  • 1 list is for active objects that needs to be draw and other is for halted ones so they aren’t drawn every draw() step
  • In the main loop I try to check which cells needs to be sent to halted and which to simulated. I have used some hacky incompetent way to manage a lot of it.

Here are the problems I face.

  • Sometimes some of the cells get stuck in active without actually doing anything new
    Issue is the for loop that checks halted cells for cells that have density and should propagate.
    Now it gets sent to propagate but because propagate has clamps that won’t let the cell distribute air so its forever in a loop. I have no clue how to create a clamp function that would activate the cells that need to disperse their density and for that clamp to match with the propagate function so it actually does that.
  • The whole Propagate function is also kinda messy because air tends to move more density to the outside of its epicenter rather thatn evening it out so usually the room gets stuck in a loop of all the cells moving around.
  • When Density is really high and there is a lot of movement time from time the values just teleport from one part of the room to another and some density also dissapears, I have no clue why this only happens with bigger numbers.
  • Corners are also very chaotic

Now considering all this I’m still a little bit proud of what I managed to do so far.
If you managed to stick with me after reading this I would appreciate if you could take a look at the code and maybe point me in the right direction or help me solve it.

Basically the air should just even out instead of going all crazy. And It doesn’t really propagate big values well.

World

int IX(int x, int y) {
  return x + y * W;
}

final int DENSITY = 100;
final int CELL_SIZE = 20;
final int W = 10;
final float TEMP_FLOAT = 0;
int cols, rows;
Cell cell;
ArrayList<Cell> cellS; //Simulated cells
ArrayList<Cell> cellN; //Halted cells

void settings() {
  size(1200, 900);
}


void setup() {
  //text("Active Cells: ", W*W+200, W+40);
  //text("DisperseD: ", W*W+200, W+80);
  cellN = new ArrayList<Cell>();
  cellS = new ArrayList<Cell>();
  int index = 0;
  synchronized(cellS) {
    for (int x = 0; x < W; x++) {
      for (int y = 0; y < W; y++) { 
        index = IX(x, y);
        cellS.add(new Cell(x, y, true, 0, 0, 0, 0, index));
      }
    }
    // Set cells neigbour 
    for (Cell cell : cellS) {
      int cellX = cell.getX();
      int cellY = cell.getY();
      for (Cell neigh : cellS) {
        int neighX = neigh.getX();
        int neighY = neigh.getY();
        // add diagonal neighbours
        if ((neighX == (cellX+EAST) || neighX ==(cellX+WEST)) && (neighY == (cellY+NORTH) || neighY == (cellY+SOUTH))) {
          cell.neighbour.add(neigh);
        }
        // add North South
        if ((neighX == cellX && (neighY == (cellY+NORTH) || neighY == (cellY+SOUTH)))) {
          cell.neighbour.add(neigh);
        }
        // add East West
        if ((neighX == (cellX+EAST) || neighX ==(cellX+WEST)) && (neighY == cellY)) {
          cell.neighbour.add(neigh);
        }
      }
    }
  }
}

//On mouse pressed add density to a cell
void mousePressed() {
  float d = 0;
  int x = mouseX/CELL_SIZE;
  int y = mouseY/CELL_SIZE;
  boolean found = false;
  Cell cellchange = new Cell(0, 0, true, 0, 0, 0, 0, 0);// create a dummy cell so the code doesn't explode
  if (x <= W-1 && y <= W-1) {
    //Below code checks if the specific cell has been found. If the cell is found in the first arraylist then the code exits and stops looping. 
    if (!found) {
      //Iterate through list
      for (Cell cell : cellS) {
        if (cell.getX() == x & cell.getY() == y) {
          // add density set found as true and set cell as simulated
          cell.addDensity(DENSITY);
          found = true;
        }
      }
    }
    // Same code as above but for halted array
    if (!found) {
      for (Cell cell : cellN) {
        if (cell.getX() == x & cell.getY() == y) {
          cell.addDensity(DENSITY);
          found = true;
          // add cell to change
          cellchange = cell;
        }
      }
      // move cell to active and remove from halted
      cellS.add(cellchange);
      cellN.remove(cellchange);
    }
  } else {
    return;
  }
}
int turns;
int totalDen;
void draw() {
  totalDen = 0;
  turns++;
  mainLoop();
  fill(100);
  rect(W*CELL_SIZE, 0, 200, W*CELL_SIZE);
  debugText();
  //fill(245, 35, 54);
  //rect(300, W, CELL_SIZE, CELL_SIZE);
  //fill(0);
  //text(cellN.size(), 300, W+10);
  //fill(245, 35, 54);
  //rect(300, W+20, CELL_SIZE, CELL_SIZE);
  //fill(0);
  //text(cellS.size(), 300, W+40);
  debugText();
}

//WAIT!!!
void propagate() {
  //if (cell.getTurn0() > 2) {
  //cell.setTurn0(0);
  //return;
  //} else if(cell.getTurn0() < 2) { 
  ArrayList<Cell> neighProp = new ArrayList<Cell>();
  for (Cell cell : cellS) {
    int cDensity = cell.getDensity0();
    for (Cell neigh : cell.neighbour) {
      int nDensity = neigh.getDensity();
      int difference = abs(cDensity-nDensity);
      int clampser = difference / 3;
      if (cDensity == nDensity) {
      } else if (nDensity == 0 && difference > 3) {
        neighProp.add(neigh);
      } else if (cDensity > nDensity && difference > 3) {
        neighProp.add(neigh);
      }
    }

    if (neighProp.size() > 0) {
      int disperseD = (cell.getDensity0()/(neighProp.size()+1));
      if (disperseD == 0) {
        return;
      }
      //cell.addDensity(-(disperseD*neighProp.size()));
      synchronized(neighProp) {
        for (Cell prop : neighProp) {
          prop.addDensity(disperseD);
          cell.addDensity(-disperseD);
        }
      }
    }
  }
  // cell.setTurn0(cell.getTurn0()+1);
  // }
}

void mainLoop() {
  //Create GarbageList to exchange cells between the main arrays
  ArrayList<Cell> garbageN = new ArrayList<Cell>(); //Halted cells
  ArrayList<Cell> garbageS = new ArrayList<Cell>(); //Halted cells
  //Loop through CellS arrayList
  synchronized(cellS) {
    for (Cell cell : cellS) {
      //if ((turns - cell.getTurn()) > 0) {
      totalDen += cell.getDensity();
      cell.setTurn(turns);
      cell.step();
      if (cell.getDensity() > 0 && cell.getTurn0() < 3) {
        cell.setTurn0(cell.getTurn0()+1);
        propagate();
      }
      if (cell.getTurn0() >= 3) {
        cell.setTurn0(0);
      }
      boolean simulated = cell.getSimulated();
      // If cell is inactive move to garbageS 
      if (!simulated) {
        garbageS.add(cell);
      }
      //} else {
      //}
    }
  }
  // Allocate inactive cells to CellN
  for (Cell cell : garbageS) {
    cellN.add(cell);
    cellS.remove(cell);
  }
  synchronized(cellN) {
    for (Cell cell : cellN) {
      cell.step();
      totalDen += cell.getDensity();
      int avg = getAvgDensity(cell);
      if (cell.getDensity() > 0 && cell.getDensity() > avg) {
        cell.setTurn0(cell.getTurn0()+1);
        propagate();
      }
      /*if (cell.getTurn0() >= 3) {
       cell.setTurn0(0);
       cell.setSimulated();
       }*/
      boolean simulated = cell.getSimulated();
      if (simulated) {
        garbageN.add(cell);
      }
    }
  }
  // Move to active cells and set them as simulated so they are active.
  for (Cell cell : garbageN) {
    cell.setSimulated();
    cellS.add(cell);
    cellN.remove(cell);
  }
  if ( cellS.size() == 0) {
    for (Cell cell : cellN) {
      if (cell.getDensity() > 0) {
        boolean neighNull = isNeighNull(cell);
        int avg = getAvgDensity(cell);
        if (cell.getDensity() > avg && cell.getDensity() >= 5 && neighNull == true) {
          cell.setSimulated();
          cell.setTurn0(0);
        }
      }
    }
  }
  garbageN.clear();
  garbageS.clear();
}

public int getAvgDensity(Cell cell) {
  int size = 1;
  int totalDensity = 0;
  for (Cell ncell : cell.neighbour) {
    size++;
    totalDensity += ncell.getDensity();
  }
  int avg = totalDensity / size;
  return avg;
}
public boolean isNeighNull(Cell cell) {
  boolean neighNull = false;
  int cDen = cell.getDensity();
  for (Cell ncell : cell.neighbour) {
    int ncDen = ncell.getDensity();
    int difference = abs(ncDen / cDen);
    int avg = getAvgDensity(cell);
    if (ncDen == 0 || ncDen > avg) {
      neighNull = true;
    }
  }
  return neighNull;
}

void debugText() {
  int clamp = W * CELL_SIZE; 
  fill(10);
  text("Active Cells: "+cellS.size(), clamp, 20);
  text("Halted Cells: "+cellN.size(), clamp, 40);
  text("Total Den: "+totalDen, clamp, 60);
}
void debugNum() {
}

Cell


final int NORTH = 1;
final int SOUTH = -1;
final int EAST = 1;
final int WEST = -1;
final int TIMER_MAX = 40;
final int CLAMPS = 1000;

class Cell {

  int x, y, ID;
  boolean simulated;
  float O2, pressure, temperature;
  int density;
  int density0;
  int timer;
  int turn, turn0;
  ArrayList<Cell> neighbour;

  Cell(int x, int y, boolean simulated, float O2, float pressure, float temperature, int density, int ID) {
    this.ID = ID;
    this.x = x;
    this.y = y; 
    this.simulated = simulated;
    this.O2 = O2;
    this.pressure = constrain(pressure, 0, CLAMPS);
    this.temperature = temperature;
    this.density = constrain(density, 0, CLAMPS);
    this.timer = 0;
    this.density0 = 0;
    this.neighbour = new ArrayList();
    this.turn = 0;
    this.turn0 = 0;
  }
  void step() { 
    //print("cell ID: ", this.ID);
    //print("  ");
    //print("cell density old: ", this.density0);
    //print("  ");
    //print("cell density new: ", this.density);
    //print("----");
    this.density = this.density0;
    renderCell();
    checkSimulated();
    this.timer++;
  }
  // Adds density to cell and sets it to simulated
  public void addDensity(int amount) {
    this.density0 += amount;
    setSimulated();
    if (this.density0 > CLAMPS) {
      this.density0 = CLAMPS;
    } else if (this.density < 0) {
      this.density0 = 0;
    }
  }
  void renderCell() {
    colorMode(HSB, 100);
    float d = this.density;
    fill(67, d, 255);
    stroke(0);
    rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
    fill(0);
    text(this.density, this.x * CELL_SIZE, (this.y + 1) * CELL_SIZE);
  }

  void checkSimulated() {
    if (!this.simulated) {
      return;
    } else {
      if (this.timer > TIMER_MAX) {
        this.simulated = false;
        if (this.density != this.density0) {
          this.timer = 0;
        }
      }
    }
  }

  void setDensity(int amount) {    
    this.density = amount;
  }
  void setDensity0(int amount) {    
    this.density0 = amount;
  }
  public int getDensity() {
    return this.density;
  }
  public int getDensity0() {
    return this.density0;
  }
  public boolean getSimulated() {
    return simulated;
  }
  public int getTurn() {
    return this.turn;
  }
  void setTurn(int tturn) {
    this.turn = tturn;
  }
  public int getTurn0() {
    return this.turn0;
  }
  void setTurn0(int ttturn) {
    this.turn0 = ttturn;
  }
  void setSimulated() {
    this.simulated = true;
    this.timer = 0;
  }
  public int getID() {
    return this.ID;
  }
  public int getX() {
    return this.x;
  }
  public int getY() {
    return this.y;
  }
}

I already noticed that a rect() placed in draw() causes an error after some time drawing extra lines from the sketches top left to the rectangle. But in your sketch this is getting out of control all the time. Have you experienced that?

Image 1

No I haven’t but I got it working now. I rewrote most of the code and now it disperses perfectly. Sometimes have issues where 1 corner block wont update but it’s not a problem