would it stabilize like this: http://www.youtube.com/watch?v=DZ2X7CNXk8Q&feature=related ?
btw. when i touch the sliders it crashes: you might need controlEvent function just for the browser but i'm not sure...
class CellStats
{
public final float GRID_SIZE = 5;
public final float CLOSENESS = 5.0;
public final int PATTERN = 2;
public int RAD = 3;
public float TRAIL = 100;
public int FADE = 6;
public float SPEED = 1.09;
public float SNIFF = 0;
public boolean DRAW_CELLS = false;
CellStats()
{
}
}
class Environment
{
private PImage IDVals;
private PImage ground;
private CellStats stats;
Environment(CellStats stats)
{
ground = createImage(width, height, ARGB);
IDVals = createImage(width, height, RGB);
this.stats = stats;
}
void fade()
{
ground.loadPixels();
for(int i = 0; i < ground.pixels.length; i++)
{
int alph = ((ground.pixels[i]>>24)&0xFF) - stats.FADE;
alph = max(0, alph);
ground.pixels[i] = color((ground.pixels[i]>>16)&0xFF, (ground.pixels[i]>>8)&0xFF, (ground.pixels[i])&0xFF, alph);
}
ground.updatePixels();
}
void startEdit()
{
ground.loadPixels();
IDVals.loadPixels();
}
void endEdit()
{
ground.updatePixels();
IDVals.updatePixels();
}
void setPixel(int x, int y, int colour, float strength, int ID)
{
if(strength > 1)
{
if(x < ground.width && y < ground.height && x > 0 && y > 0)
{
float alph = alpha(ground.pixels[y*ground.width + x]);
ground.pixels[y*ground.width + x] = lerpColor(colour, ground.pixels[y*ground.width + x], .5);
ground.pixels[y*ground.width + x] = color((ground.pixels[y*ground.width + x]>>16)&0xFF, (ground.pixels[y*ground.width + x]>>8)&0xFF, (ground.pixels[y*ground.width + x])&0xFF, strength + alph);
IDVals.pixels[y*ground.width + x] = ID;
}
}
}
int getPixel(int x, int y, int ID)
{
if(x < ground.width && y < ground.height && x > 0 && y > 0)
{
if(IDVals.pixels[y*IDVals.width + x] == ID)
return -1;
return (int)alpha(ground.pixels[y*ground.width + x]);
}
else return -1;
}
void draw()
{
image(ground,0,0);
}
}
class SlimeCell
{
private float closeness;
private float myDir = (int)(random(4)+1)*(PI/2.0);
public CellStats stats;
public float x, y;
public int gridX = -1, gridY = -1;
public int cellColour, trailColour;
public boolean sniff;
private int ID;
private float direction;
private Environment enviro;
private float speedMult = random(3.5)+1;
private float sniffOffset = 2.5;
private ArrayList arrX;
private ArrayList arrY;
SlimeCell(float x, float y, int cellColour, int trailColour, CellStats stats, Environment enviro, int ID)
{
this.stats = stats;
this.place(x,y);
this.cellColour = cellColour;
this.trailColour = trailColour;
this.enviro = enviro;
this.ID = ID;
direction = random(TWO_PI);
}
void step()
{
if(random(1) > stats.SNIFF)
this.sniff();
else
this.direction = random(2) > 1 ? this.direction + random(.1)*PI : this.direction - random(.1)*PI ; //myDir;
this.walk();
}
void update()
{
this.excrete();
this.draw();
}
void walk()
{
float nX = this.x + cos(direction)*stats.SPEED*speedMult;
float nY = this.y + sin(direction)*stats.SPEED*speedMult;
this.place(nX, nY);
}
void place(float nX, float nY)
{
this.x = nX;
this.y = nY;
this.x = this.x >= width ? 0 : this.x;
this.x = this.x < 0 ? width-1 : this.x;
this.y = this.y >= height ? 0 : this.y;
this.y = this.y < 0 ? height-1 : this.y;
int newGridX = (int)floor(this.x/stats.GRID_SIZE);
int newGridY = (int)floor(this.y/stats.GRID_SIZE);
if((newGridX != this.gridX || newGridY != this.gridY) && gridX >= 0 && gridY >= 0)
{
int ind = grid[this.gridX][this.gridY].indexOf(this);
if(ind >= 0)
{
grid[gridX][gridY].remove(ind);
grid[newGridX][newGridY].add(this);
}
}
else if( gridX < 0 && gridY < 0 )
grid[newGridX][newGridY].add(this);
gridX = newGridX;
gridY = newGridY;
}
void excrete()
{
enviro.startEdit();
for(int i = (int)this.x-stats.RAD; i < (int)this.x+stats.RAD; i++)
{
for(int j = (int)this.y-stats.RAD; j < (int)this.y+stats.RAD; j++)
{
float strength = (255-((dist(i,j,x,y)/stats.RAD)*255) + stats.TRAIL)/6.0;
strength = min(255, strength);
enviro.setPixel(i,j,trailColour, strength, ID);
}
}
enviro.endEdit();
}
void sniff()
{
int xP = -1, yP = -1;
int maxStr = -1;
switch((int)random(stats.PATTERN))
{
case 0:
for(int i = (int)(this.x-(stats.RAD*sniffOffset)); i < (int)(this.x+(stats.RAD*sniffOffset)); i++)
{
for(int j = (int)(this.y-(stats.RAD*sniffOffset)); j < (int)(this.y+(stats.RAD*sniffOffset)); j++)
{
int tempStr = enviro.getPixel(i,j, ID);
if(tempStr > maxStr && tempStr > 0)
{
// arr = new ArrayList();
maxStr = tempStr;
// arr.add(tempStr);
xP = i;
yP = j;
}
// else if(tempStr == maxStr && tempStr > 0)
// {
// arr.add(tempStr);
// }
}
}
break;
case 1:
for(int i = (int)(this.x+(stats.RAD*sniffOffset)); i > (int)(this.x-(stats.RAD*sniffOffset)); i--)
{
for(int j = (int)(this.y-(stats.RAD*sniffOffset)); j < (int)(this.y+(stats.RAD*sniffOffset)); j++)
{
int tempStr = enviro.getPixel(i,j, ID);
if(tempStr > maxStr && tempStr > 0)
{
maxStr = tempStr;
xP = i;
yP = j;
}
}
}
break;
case 2:
for(int i = (int)(this.x+(stats.RAD*sniffOffset)); i > (int)(this.x-(stats.RAD*sniffOffset)); i--)
{
for(int j = (int)(this.y+(stats.RAD*sniffOffset)); j > (int)(this.y-(stats.RAD*sniffOffset)); j--)
{
int tempStr = enviro.getPixel(i,j, ID);
if(tempStr > maxStr && tempStr > 0)
{
maxStr = tempStr;
xP = i;
yP = j;
}
}
}
break;
case 3:
for(int i = (int)(this.x-(stats.RAD*sniffOffset)); i < (int)(this.x+(stats.RAD*sniffOffset)); i++)
{
for(int j = (int)(this.y+(stats.RAD*sniffOffset)); j > (int)(this.y-(stats.RAD*sniffOffset)); j--)
{
int tempStr = enviro.getPixel(i,j, ID);
if(tempStr > maxStr && tempStr > 0)
{
maxStr = tempStr;
xP = i;
yP = j;
}
}
}
break;
}
if(maxStr > 0)
{
direction = atan2(yP-y, xP-x);
}
}
void draw()
{
fill(cellColour);
if(stats.DRAW_CELLS)
ellipse(x,y,stats.RAD*2, stats.RAD*2);
}
public boolean testAndRepel(SlimeCell other)
{
closeness = stats.RAD-(stats.RAD/stats.CLOSENESS);
if(dist(this.x, this.y, other.x, other.y) < closeness)
{
float ang = atan2(other.y - this.y, other.x - this.x) + PI;
float nX = other.x + cos(ang)*closeness;
float nY = other.y + sin(ang)*closeness;
this.place(nX, nY);
return true;
}
else
return false;
}
}
//import processing.opengl.*;
import controlP5.*;
PFont font;
ControlP5 controlP5;
//ControlWindow controlWindow;
int cellCount = 3000;
ArrayList[][] grid;
CellStats stat = new CellStats();
SlimeCell[] slime = new SlimeCell[cellCount];
Environment enviro;
void setup()
{
size(600, 600);
// font = loadFont("arahoni.vlw");
// textFont(font, 32);
//cellCount/=4;
println((int)floor(width/stat.GRID_SIZE));
grid = new ArrayList[(int)floor(width/stat.GRID_SIZE)][(int)floor(height/stat.GRID_SIZE)];
enviro = new Environment(stat);
for(int i = 0; i < grid.length; i++)
{
for(int j = 0; j < grid[i].length; j++)
{
grid[i][j] = new ArrayList();
}
}
for(int i = 0; i < slime.length; i++)
{
slime[i] = new SlimeCell(random(width), random(height), color(255,0,0), color(150,220,255), stat, enviro, i);
slime[i].update();
}
noStroke();
setupController();
}
void draw()
{
background(40);
enviro.fade();
enviro.draw();
for(int i = 0; i < cellCount; i++)
{
slime[i].step();
}
gridCheck();
for(int i = 0; i < cellCount; i++)
{
slime[i].update();
}
//save("/Volumes/scratch/snaps2/"+frameCount+".jpg");
}
public void controlEvent(ControlEvent theEvent) {
//println(theEvent.controller().id());
switch(theEvent.controller().id()) {
case(1):
cellCount = (int)(theEvent.controller().value());
break;
case(2):
stat.RAD = (int)(theEvent.controller().value());
break;
case(3):
stat.FADE = (int)(theEvent.controller().value());
break;
case(4):
stat.SNIFF = (theEvent.controller().value());
break;
case(5):
stat.SPEED = (theEvent.controller().value());
break;
case(6):
stat.TRAIL = (theEvent.controller().value());
break;
case(7):
stat.DRAW_CELLS = !stat.DRAW_CELLS;
break;
}
}
void setupController()
{
controlP5 = new ControlP5(this);
//controlWindow = controlP5.addControlWindow("controlP5window",300,300,200,200);
//controlWindow.hideCoordinates();
//controlWindow.setBackground(color(40));
Controller mySlider;
mySlider = controlP5.addSlider("cell count",10,slime.length,cellCount,10,20,100,14);
//mySlider.setWindow(controlWindow);
mySlider.setId(1);
mySlider =controlP5.addSlider("radius",1,30,10,10,40,100,14);
//mySlider.setWindow(controlWindow);
mySlider.setId(2);
mySlider =controlP5.addSlider("fade",1,255,10,10,60,100,14);
//mySlider.setWindow(controlWindow);
mySlider.setId(3);
mySlider =controlP5.addSlider("sniff",0,1,0,10,80,100,14);
//mySlider.setWindow(controlWindow);
mySlider.setId(4);
mySlider =controlP5.addSlider("speed",.1,10,1.09,10,100,100,14);
//mySlider.setWindow(controlWindow);
mySlider.setId(5);
mySlider =controlP5.addSlider("trail",0,100,100,10,120,100,14);
//mySlider.setWindow(controlWindow);
mySlider.setId(6);
mySlider =controlP5.addButton("Toggle cell drawing", 1, 10, 140, 100, 14) ;
//mySlider.setWindow(controlWindow);
mySlider.setId(7);
}
SlimeCell initialCell;
SlimeCell otherCell;
void gridCheck()
{
for(int i = 0; i < grid.length; i++)
{
for(int j = 0; j < grid[i].length; j++)
{
for(int k = 0; k < grid[i][j].size(); k++)
{
initialCell = (SlimeCell)grid[i][j].get(k);
for(int m = 0; m < grid[i][j].size(); m++)
{
otherCell = (SlimeCell)grid[i][j].get(m);
if(otherCell != initialCell)
initialCell.testAndRepel(otherCell);
}
if(i < grid.length-1)
for(int l = 0; l < grid[i+1][j].size(); l++)
{
otherCell = (SlimeCell)grid[i+1][j].get(l);
initialCell.testAndRepel(otherCell);
}
if(i > 0 && j < grid[i].length-1)
for(int l = 0; l < grid[i-1][j+1].size(); l++)
{
otherCell = (SlimeCell)grid[i-1][j+1].get(l);
initialCell.testAndRepel(otherCell);
}
if(j < grid[i].length-1)
{
for(int l = 0; l < grid[i][j+1].size(); l++)
{
otherCell = (SlimeCell)grid[i][j+1].get(l);
initialCell.testAndRepel(otherCell);
}
if(i < grid.length-1)
for(int l = 0; l < grid[i+1][j+1].size(); l++)
{
otherCell = (SlimeCell)grid[i+1][j+1].get(l);
initialCell.testAndRepel(otherCell);
}
}
}
}
}
}
void mousePressed(){
save("img.jpg");
}
This was inspired while reading a description of slime mold in the book "emergence" by Steven Johnson.
I didn't actually look at other implementations of computerized slime mold simulations as they all seemed more scientific rather than aesthetically oriented.
It was the initial sketch that my interactive installation was based on:
http://vimeo.com/16329472