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;
}
}