// ** NEW ** -> screenshots of different visualizations
// of this program available @:
// http://users.design.ucla.edu/~lonergan/stuff/screenshots/
// BUGS
//
// guthrie
// CLICK MOUSE to generate new, randomly colored bug
// TYPE 'D' to return to Default bug configuration
// I like to hit 'D' a lot
// RULES:
// The background color is the average color of all the bugs
// Bugs attempt to catch the nearest bug with the rarest, most unique color
// (as different from the background as possible : in hue, saturation and brightness)
// The rarer a bug's color, the bigger he is
// When two bugs collide they spawn a new bug
// The baby bug's color is the average of the parent bugs' colors
// (collisions do not always mean spawning, or immediate spawning...)
// (...or else the screen would fill up with bugs way too fast!)
// Bugs die when they get old, but the rarer-colored bugs survive longer
// When bugs walk too far off of the screen, they are erased from memory
// If a spawning frenzy occurs, and there are too many bugs concentrated in one spot,
// bugs will run away in the opposite direction
// Also, there is a gentle push on the more rare bugs towards the center of the screen
int focusX=-1,focusY=-1;
float focusTime=-1;
int numBugs=12;
int bugBuffer=400;
Bug[] bugs = new Bug[bugBuffer];
//PImage ladybug;
//int rad=5; // collision margin
int margin=10; // initialize x,y margin
float redsum=0,greensum=0,bluesum=0;
float rs=10000000,gs=10000000,bs=10000000;
float recentExplosion=0;
float[] rareSort = new float[bugBuffer];
int count=0;
void setup()
{
size(300,300);
colorMode(RGB,255);
ellipseMode(CENTER);
angleMode(DEGREES);
framerate(60);
background(255,255,255);
//ladybug = loadImage("chartreuse.gif");
bugs[0] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(255),0,bugs);
for(int i=1; i<numBugs; i++)
{
if(random(0,1)>.9) // SUPER DIFFERENT BUG!!!!!!!!
{
bugs[i] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(0,255),random(0,255),random(0,255)),i,bugs);
}
else
{
bugs[i] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(150,200),random(0,255),random(0,255)),i,bugs);
}
}
/*
bugs[0] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(255,255,255),0,bugs);
bugs[1] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(0,200,200),1,bugs);
bugs[2] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(255,255,0),2,bugs);
bugs[3] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(255,0,255),3,bugs);
*/
}
void draw()
{
rs=smoother(rs,redsum/(numBugs+1),5,0,255);
gs=smoother(gs,greensum/(numBugs+1),5,0,255);
bs=smoother(bs,bluesum/(numBugs+1),5,0,255);
if(numBugs>=bugBuffer)
{
}
background(rs,gs,bs);
/*
float rare, maxRare=bugs[0].updateRareness();
int index=0;
// index = smallest rareness
for(int i=1; i<numBugs; i++)
{
rare = bugs[0].updateRareness();
if(rare<maxRare)
{
maxRare=rare;
index=i;
}
}
*/
for(int i=0; i<numBugs; i++)
{
if(bugs[i].isDead())
{
eraseBug(i);
i--;
}
}
for(int i=0; i<numBugs; i++)
{
rareSort[i]=bugs[i].updateRareness();
}
for(int i=0; i<numBugs; i++)
{
bugs[i].update();
bugs[i].draw();
}
//stroke(0);
//beginShape(LINES);
/*
for(int i=0; i<numBugs; i++)
{
bugs[i].giveVertex();
}*/
//endShape();
count++;
}
void defaultBugs()
{
println("hi");
numBugs=12;
bugs[0] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(255),0,bugs);
for(int i=1; i<numBugs; i++)
{
if(random(0,1)>.97) // SUPER DIFFERENT BUG!!!!!!!!
{
bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(0,255),random(0,255),random(0,255)),i,bugs);
}
else
{
bugs[i] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(150,200),random(0,255),random(0,255)),i,bugs);
}
}
}
float smoother(float pos, float dest, float speed, int bottom, int top)
{
float dif = dest - pos;
if(abs(dif) > 1.0) {
pos = pos + dif/speed;
}
return(constrain(pos, bottom, top));
}
void eraseBug(int which)
{
bugs[which].funeral();
bugs[which]=bugs[numBugs-1];
numBugs--;
}
void mousePressed()
{
/*
focusX=mouseX;
focusY=mouseY;
focusTime=millis();
*/
if(numBugs<bugBuffer)
{
if(random(0,1)>.97) // SUPER DIFFERENT BUG!!!!!!!!
{
bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(0,255),random(0,255),random(0,255)),numBugs,bugs);
}
else if(random(0,1)>.93) // whitey
{
bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(255),numBugs,bugs);
}
else
{
bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(150,200),random(0,255),random(0,255)),numBugs,bugs);
}
numBugs++;
}
else
{
if(random(0,1)>.97)
{
bugs[numBugs-1] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(0,255),random(0,255),random(0,255)),numBugs,bugs);
}
else if(random(0,1)>.93) // whitey
{
bugs[numBugs-1] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(255),numBugs,bugs);
}
else
{
bugs[numBugs-1] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(150,200),random(0,255),random(0,255)),numBugs-1,bugs);
}
}
}
void keyPressed()
{
if(keyCode=='d' || keyCode=='D')
{
for(int i=0; i<numBugs; i++)
{
eraseBug(i);
}
rs=10000000;
gs=10000000;
bs=10000000;
redsum=0;
greensum=0;
bluesum=0;
defaultBugs();
}
}
/*** BUG CLASS OUTLINE **\
FIELDS:
float M = mass
float D = damper (used to get the physics on the right scale, speed-wise)
float x,y = x and y position
float fx,fy = force in the x and y directions
float vx,vy = velocity in the x and y directions
float ax,ay, = acceleration in the x and y directions
int me = index of this bug in the bugs array
float birthday = set when bug is born, used to work out when bug dies
color myColor = the color of the bug
float rareness = how rare bug's color is compared to the average bug color
float rad = "radius" of bug
float rr = real radius of bug, "rad" approaches this number smoothly
Bug[] others = the bugs array
int numClose = how many bugs are within a certain radius of it
int lastTouch = last bug collided with, to lessen repeat collisions
int mostAttractive = index of the bug which is the most rare within a certain radius
METHODS:
Bug() = constructor sets initial values, adds its own color to the average color sums, sets the bug's birthday
float updateRareness() = sets the rareness and rr (based on rareness) fields, returns rareness
float calcRareness() = returns the difference between myColor and the average color of all the bugs
void update() = handles physics, "AI", collisions, everything else
boolean isCollided() = determines if this bug has collided with a bug at a certain position
color avgColor() = returns the average between myColor and another color (for spawning)
void collisions() = handles collisions between bugs and spawning
void draw() = draws two ellipses that represent the bug
void setMostAttractive() = sets mostAttractive field to the rarest bug in a certain radius
void funeral() = takes a dying bug's color out of the average color of all the bugs
float distance() = returns distance between this bug and another position
float giveRareness() = returns rareness!
float giveX() = returns x!
float giveY() = returns y!
boolean isDead() = returns true if a bug has died by stepping too far out of bounds or being alive for too long
*/
class Bug
{
float x,y,M,D=.48;
float fx,fy;
float vx,vy;
float ax,ay;
float rad=1.4;
float rr=rad;
float rareness=0;
int numClose=0;
float birthday;
color myColor;
int me;
Bug[] others;
int lastTouch=-1;
int mostAttractive=-1; // index of most attractive bug in immediate vicinity
Bug(float myx, float myy, float myM, float myfx, float myfy, float myrad, color mymyColor, int myme, Bug[] myothers)
{
x=myx;
y=myy;
M=myM;
fx=myfx;
fy=myfy;
myColor=mymyColor;
me=myme;
others=myothers;
rad=myrad;
redsum+=red(myColor);
greensum+=green(myColor);
bluesum+=blue(myColor);
birthday=millis();
}
float updateRareness() {
rareness = calcRareness();
// rareness is the abs() difference between myColor and the average color of all the bugs
// higher # == rarer
rr=3.4*(.4+rareness/180);
return rareness;
}
float calcRareness()
{
float calcr=abs(red(myColor)-rs);
float calcg=abs(green(myColor)-gs);
float calcb=abs(blue(myColor)-bs);
if(abs((red(myColor)+255)-rs)<calcr)
calcr=abs((red(myColor)+255)-rs);
if(abs((green(myColor)+255)-gs)<calcg)
calcg=abs((green(myColor)+255)-gs);
if(abs((blue(myColor)+255)-bs)<calcb)
calcb=abs((blue(myColor)+255)-bs);
return(calcr%255 + calcg%255 + calcb%255);
// rareness is the abs() difference between myColor and the average color of all the bugs
// higher # == rarer
// BUT!!!: rgbs are wraparound values... that is, red=0 is just as close to red=255 as it is to red=1
// hence all the if statements
}
void update() {
fx=smoother(fx,0,40,-10000,10000);
fy=smoother(fy,0,40,-10000,10000);
setMostAttractive();
if(mostAttractive>=0)
{
float damper=.0005;
float addfx=(others[mostAttractive].giveX()-x)*damper;
float addfy=(others[mostAttractive].giveY()-y)*damper;
if(numClose<random(30,55))
{
fx+=addfx;
fy+=addfy;
}
else
{
fx-=addfx*1.5;
fy-=addfy*1.5;
}
}
if(rareness>random(90,400))
{
float damperB=.000087;
float addfxB=(width/2-x)*damperB;
float addfyB=(height/2-y)*damperB;
fx+=addfxB;
fy+=addfyB;
}
if(focusX>=0)
{
float damperB=(millis()-focusTime+1)/900000;
float addfxB=(focusX-x)*damperB;
float addfyB=(focusY-y)*damperB;
fx+=addfxB;
fy+=addfyB;
}
ax = fx / M; // Set the acceleration, f=ma == a=f/m
ay = (fy) / M;
vx = D * (vx + ax); // Set the velocity
vy = D * (vy + ay);
// x = constrain(x + vx,rad/2,width-rad/2);
// y = constrain(y + vy,rad/2,height-rad/2);
x=x+vx;
y=y+vy;
float dif = rr - rad;
if(abs(dif) > .04) {
rad = rad + dif/15;
}
// rad = smoother(rad,rr,15,0,10);
/*
if(rad<rr)
rad+=.027;
if(rad-.06>rr)
rad-=.09;
*/
collisions(); // with other dots
}
boolean isCollided(float otherx, float othery)
{
if(dist(otherx,othery,x,y)<(rad*2))
return true;
return false;
}
color avgColor(color othercolor)
{
return color((red(othercolor)+red(myColor))/(2),(green(othercolor)+green(myColor))/(2),(blue(othercolor)+blue(myColor))/(2));
}
void collisions()
{
boolean cloned=false;
for(int i=0; i<numBugs && !cloned; i++)
{
if(i!=me && others[i].isCollided(x,y) && lastTouch!=i)
{
// COLLISION HAS OCCURED!
lastTouch=i;
cloned=true;
if(numBugs<bugBuffer && (millis()-recentExplosion)>140) // this last number: lower = bigger spawning explosions
{
recentExplosion=millis();
bugs[numBugs] = new Bug(x,y,random(1,3),random(-.8,.8),random(-.8,.8),1.6,others[i].avgColor(myColor),numBugs,bugs);
numBugs++;
}
rad+=.001;
/*
fx=-fx;
vx=-vx;
fy=-fy;
vy=-vy;
*/
}
}
}
void draw()
{
float ang=atan(vy/vx); // angle created by x and y forces
/*
if(me==0)
{
ellipse(x,y,rad*5,rad*5);
if(mostAttractive>=0)
{
stroke(0);
beginShape(LINES);
giveVertex();
others[mostAttractive].giveVertex();
endShape();
}
}*/
push();
translate(x,y);
if(vx>0)
rotate(90+ang);
else
rotate(ang-90);
rotate(cos(millis()*((abs(vx)+abs(vy))*4))*(7+6*noise(me*2000))); // makes the bug waddle
noStroke();
fill(0);
ellipse(0,-rad,rad*1.5,rad);
// stroke(red(myColor)-50,green(myColor)-50,blue(myColor)-50);
// stroke(0,(5-rad)*12);
// stroke(0,55);
// stroke(0,64-(numClose/2.5));
// stroke(0,(500-rareness)/20);
fill(myColor);
/*
vertex(0,rad+rad*(3.5/2));
vertex(-rad,rad);
vertex(0,rad-rad*(3.5/2));
vertex(rad,rad);
vertex(0,rad+rad*(3.5/2));
*/
ellipse(0,rad,rad*2,rad*3.5);
pop();
}
void setMostAttractive()
// finds and records the index of the most rare bug in a certain radius
// most attractive bug must be rarerer than itself
// most attractive bug must be above a threshhold "minRareness"
// also sets numClose -- how many bugs are really close to it
{
float searchRadius=120;
float minRareness=170;
float bestRareness=0;
int bestIndex=-1;
numClose=0;
for(int i=0; i<numBugs; i++)
{
float thisRareness = others[i].giveRareness();
if(others[i].distance(x,y)<searchRadius && i!=me)
{
numClose++;
if(thisRareness>bestRareness && thisRareness>minRareness)// && thisRareness>rareness)
{
bestRareness=thisRareness;
bestIndex=i;
}
}
}
mostAttractive=bestIndex;
}
void funeral()
{
redsum-=red(myColor);
greensum-=green(myColor);
bluesum-=blue(myColor);
}
float distance(float otherx, float othery)
{
return dist(otherx,othery,x,y);
}
float giveRareness()
{
return rareness;
}
float giveX()
{
return x;
}
float giveY()
{
return y;
}
boolean isDead()
{
if(x<=-margin || x>=width+margin || y<=-margin || y>=height+margin)
{
return true;
}
else if(rareness>0 && millis()-birthday>random(rareness*1500,rareness*3000))
{
//println(millis()-birthday);
return true;
}
else
{
return false;
}
}
}