// boom(bloom) web version // mitchell whitelaw - april 2006 import processing.opengl.*; int numBits = 50; int cloudRadius = 250; // float friction = 0.996; float velFloor = 0.01; // the lower limit for velocity: stop drawing when things get this slow float distanceCeiling = 1000; // stop drawing when things get this far from the origin // float distancefactor = 6500; // int drawing = 0; int totalDrawing = 0; float fadeAlpha = 0; int originX; int originY; int cloudX; int cloudY; Particle[] theCloud; // the array of particles void setup() { size (900,550,OPENGL); colorMode(HSB,100); smooth(); noFill(); background(0,0,100); framerate(30); makeCloud(); } void makeCloud() { theCloud = new Particle[numBits]; for (int i=0; i < numBits; i++){ float radius = ((i+1)/float(numBits))*cloudRadius; float angle = random(TWO_PI); float xPos = cloudX + (radius*sin(angle)); float yPos = cloudY + (radius*cos(angle)); theCloud[i] = new Particle(xPos,yPos,0,0,i,0,0,0,0,0); // x,y,xvel,yvel,particle ID, neighbour1, neighbour2, neighbour1Distance, neighbour2Distance, drawingFlag } } class Particle { int id; // particle ID float x; // x position float y; // y position float xvel; // x velocity float yvel; // y velocity int myNeighbour; // ID of my neighbour int myNeighbour2; float neighbourDistance; // distance to my neighbour float neighbourDistance2; int drawingFlag; // am I drawing or not? Particle(float xIn, float yIn, float xvelIn, float yvelIn, int idIn, int myNeighbourIn, float neighbourDistanceIn, int myNeighbourIn2, int neighbourDistanceIn2, int drawingFlagIn) { // particle constructor x = xIn; y = yIn; xvel = xvelIn; yvel = yvelIn; id = idIn; myNeighbour = myNeighbourIn; myNeighbour2 = myNeighbourIn2; neighbourDistance = neighbourDistanceIn; neighbourDistance2 = neighbourDistanceIn2; drawingFlag = drawingFlagIn; } void moveParticle(){ x += xvel; // add xvel to xpos y += yvel; // add yvel to ypos xvel = xvel*friction; // factor in friction yvel = yvel*friction; } void findNeighbour(){ int theNeighbour = 0; int thenextNeighbour = 0; float minDistance = 10000; // make it a high number so we can find the minimum float nextminDistance = 10000; float theDistance = 0; for (int i=0; i < numBits; i++){ theDistance = sqrt(sq(x - theCloud[i].x) + sq(y - theCloud[i].y)); // distance from this particle to each of the others if (theDistance < minDistance && theDistance > 0){ // if the distance is less than the lowest yet, and more than 0 (ie it's not the distance from me to me). minDistance = theDistance; theNeighbour = i; } if (theDistance < nextminDistance && theDistance > minDistance){ nextminDistance = theDistance; thenextNeighbour = i; } } myNeighbour = theNeighbour; // the ID of the neighbour particle neighbourDistance = minDistance; myNeighbour2 = thenextNeighbour; neighbourDistance2 = nextminDistance; } void goBoom(){ noFill(); originX = mouseX; originY = mouseY; float xOffset = x - originX; float yOffset = y - originY; float thisDistance = sqrt(sq(xOffset) + sq(yOffset)); float absOffset = abs(xOffset) + abs(yOffset); float xRatio = xOffset/absOffset; // ratio of x/y - using abs to maintain sign of offset float yRatio = yOffset/absOffset; // ratio of y/x xvel = distancefactor*xRatio*(1/sq(thisDistance)); // the initial X velocity yvel = distancefactor*yRatio*(1/sq(thisDistance)); // the initial Y velocity drawingFlag = 1; } void drawParticle() { float originDistance = sqrt(sq(x-originX) + sq(y-originY)); if (drawing == 1 && (abs(xvel)+abs(yvel)) > velFloor && originDistance < distanceCeiling) { float[] theCenterPoint = threePointCircle(x, y, theCloud[myNeighbour].x, theCloud[myNeighbour].y, originX, originY); float theDiameter = sqrt(sq(x-theCenterPoint[0]) + sq(y-theCenterPoint[1])); ellipseMode(CENTER_RADIUS); float totalVel = abs(xvel)+abs(yvel); float hueBase = 115; float theHue = (hueBase+pow(neighbourDistance,-2)-0.12*min(theDiameter,200))%100; float theSaturation = 100 - min(50,((theDiameter-200)/30)); float theLightness = 90 - min(20,(pow(3,totalVel))); stroke(theHue,theSaturation,theLightness,min(1.5,totalVel*4)); if (theDiameter < width) { ellipse(theCenterPoint[0],theCenterPoint[1],theDiameter,theDiameter); } } else { drawingFlag=0; } } } void keyPressed(){ if (key == 'b') { drawing = 1; fadeAlpha = 1; for (int i = 0; i < numBits; i++){ theCloud[i].goBoom(); // all go boom } } else if (key == 'c') { background (0,0,100); drawing = 0; } else if (key == 'r') { cloudX = mouseX; cloudY = mouseY; makeCloud(); // remake the cloud } } void draw(){ int countDrawing = 0; for (int i=0; i < numBits; i++){ theCloud[i].findNeighbour(); theCloud[i].drawParticle(); theCloud[i].moveParticle(); countDrawing += theCloud[i].drawingFlag; } totalDrawing = countDrawing; if (totalDrawing == 0){ drawing=0; fill(255,fadeAlpha); stroke(255,fadeAlpha); rect(0,0,width,height); fadeAlpha = fadeAlpha * 1.01; } } float[] threePointCircle(float x1, float y1, float x2, float y2, float x3, float y3) { // geometry thanks to Paul Bourke - http://astronomy.swin.edu.au/~pbourke/geometry/circlefrom3/ if ((x2-x1) == 0){ // first slope is vertical, rotate points float oldx = x1; float oldy = y1; x1 = x2; y1 = y2; x2 = x3; y2 = y3; x3 = oldx; y3 = oldy; } else if ((x3-x2) == 0){ // second slope is vertical, rotate points the other way float oldx = x3; float oldy = y3; x3 = x2; y3 = y2; x2 = x1; y2 = y1; x1 = oldx; y1 = oldy; } float slope1 = (y2-y1)/(x2-x1); // slope of the first line float slope2 = (y3-y2)/(x3-x2); // slope of the second line float centerx = ((slope1*slope2*(y1-y3)) + (slope2*(x1+x2)) - (slope1*(x2+x3)))/(2*(slope2 - slope1)); // x point float centery = ((-1*(centerx - 0.5*(x1+x2)))/slope1) + 0.5*(y1+y2); // y point float[] thisCenter = {centerx,centery}; return thisCenter; }