Last active
March 15, 2021 20:40
-
-
Save volfegan/9f4e8f594eda34c292eedba4f3d51464 to your computer and use it in GitHub Desktop.
Bubbles drifting with circle packing algorithm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// author Volfegan [Daniel L Lacerda] | |
import java.lang.Math; | |
import java.util.Random; | |
public class Circle { | |
public float x, y, r, colour, angle; | |
public Circle(float x, float y, float r) { | |
this.x = x; | |
this.y = y; | |
this.r = Math.abs(r); //radius of the circle | |
Random rand = new Random(); | |
this.colour = rand.nextInt(255); | |
this.angle = rand.nextFloat()*(float)(2*Math.PI); //starting angle of the movement [0, 360º] | |
} | |
//yeah, a strange way to update the position for bubbles to bubble | |
public void updatePos(float time) { | |
float i = this.angle; //seed angle for the movement | |
float o = time*(noise(i*9, 9)-.5)/99+i; | |
this.x += cos(o); | |
this.y += 2*sin(o); | |
} | |
//check if this circle intersects another circle object | |
public boolean intersects(Circle other_circle) { | |
float xDist = Math.abs(other_circle.x - this.x); | |
float yDist = Math.abs(other_circle.y - this.y); | |
float rDist = this.r + other_circle.r; | |
//intersection happens when the distance from the circles centres is less than the sum of their radius | |
return rDist >= Math.sqrt(xDist*xDist + yDist*yDist); | |
} | |
public boolean equals(Circle other_circle) { | |
return this.x == other_circle.x && this.y == other_circle.y && this.r == other_circle.r; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//reference https://generativeartistry.com/tutorials/circle-packing/ | |
//reference https://twitter.com/SnowEsamosc/status/1367843669672292353 | |
//reference https://gist.github.com/volfegan/c65eac48249990dd87da86371eee98d1 | |
import java.util.Collections; | |
ArrayList<Circle> circles = new ArrayList<Circle>(); | |
int minRadius = 3; | |
int maxRadius = 100; | |
int totalCircles = 2000; | |
int createAttempts = 500; | |
void setup() { | |
size(1280, 720); | |
colorMode(HSB); | |
noFill(); | |
clear(); | |
for (int i=0; i < totalCircles; i++) { | |
float x, y; | |
if (random(1)<.7) { | |
//random position across the screen | |
x = random(width); | |
y = random(height); | |
} else { | |
//random position near the centre | |
x = (width/4) + randomGaussian(width /4, width / 8); | |
y = (height/4) + randomGaussian(height / 4, height / 8); | |
} | |
createCircle(x, y); | |
} | |
totalCircles = circles.size(); | |
println("Total circles: "+circles.size()); | |
} | |
void draw() { | |
int standby = 2000; | |
if (millis() > standby) { | |
clear(); | |
//update position/delete | |
float t = frameCount; | |
for (int j = circles.size()-1; j>=0; j--) { | |
Circle c = circles.get(j); | |
c.updatePos(t); | |
//remove circles which are outside the screen | |
if ((c.x > width) || (c.x < 0) || (c.y > height) || (c.y < 0)) { | |
circles.remove(j); | |
} | |
} | |
//bug where a circle in the beggining of the list becomes invisible [shuffle the list] | |
if (random(1)>.99) { | |
//for a true homogenious circle radius update, the circles array should be suffled every cycle | |
Collections.shuffle(circles); | |
} | |
//force an increase of the radius for some circles for better looking effect | |
for (int i=round(random(20)); i>=0; i--) { | |
Circle c = circles.get(i); | |
if (c.r < maxRadius) { | |
c.r += 2; | |
} | |
} | |
//update radius/delete | |
for (int j = circles.size()-1; j>0; j--) { | |
Circle c = circles.get(j); | |
float diameter = 2*updateCircleRadius(c); | |
//remove circles with zero radius or those near the maxRadius (pop the big bubbles) | |
if (c.r == 0 || c.r >= maxRadius-.2) { | |
circles.remove(j); | |
} else { | |
float dist = dist(width/2, height/2, c.x, c.y); | |
stroke(c.colour, map(dist, 0, height/2, 255, 99+c.r), height-dist); | |
circle(c.x, c.y, diameter); | |
} | |
} | |
//replace all removed circles | |
for (int n=0; n < totalCircles-circles.size(); n++) { | |
float x, y; | |
if (random(1)<.4) { | |
//random across the screen | |
x = random(width); | |
y = random(height); | |
} else { | |
//random near the centre | |
x = (width/4) + randomGaussian(width /4, width / 8); | |
y = (height/4) + randomGaussian(height / 4, height / 8); | |
} | |
createCircle(x, y); | |
} | |
} | |
//saveFrame("frame_######.png"); | |
} | |
public float randomGaussian(float min, float max) { | |
return min + randomGaussian() * (max - min); | |
} | |
float updateCircleRadius(Circle circle) { | |
//Grow radius of circle until a collision; | |
float inc = .1; | |
for (float radius = inc; radius <= maxRadius; radius += inc) { | |
circle.r = radius; | |
if (detectCollision(circle)) { | |
circle.r = radius-inc; | |
return circle.r; | |
} | |
} | |
return circle.r; | |
} | |
// Check if the circle collides with the others. | |
boolean detectCollision(Circle circle) { | |
for (Circle c : circles) { | |
if (circle.intersects(c) && circle.equals(c)==false) { | |
return true; | |
} | |
} | |
//check collision against screen size | |
if (circle.x+circle.r > width || circle.x-circle.r <= 0 || | |
circle.y+circle.r >= height || circle.y-circle.r <= 0) { | |
return true; | |
} | |
return false; | |
} | |
void createCircle(float x, float y) { | |
boolean safePosition = false; | |
float r = minRadius; | |
Circle circle = new Circle(x, y, r); | |
for (int i=0; i < createAttempts; i++) { | |
//check if circle collides with another and random walk it to a new postion if true | |
if (detectCollision(circle)) { | |
x+= random(-1, 1); | |
y+= random(-1, 1); | |
circle = new Circle(x, y, r); | |
continue; | |
} else { | |
safePosition = true; | |
break; | |
} | |
} | |
if (!safePosition) { | |
return; | |
} | |
//Grow radius of circle until a collision; | |
float inc = .1; | |
for (float radius = minRadius; radius < maxRadius; radius += inc) { | |
circle.r = radius; | |
if (detectCollision(circle)) { | |
circle.r -= inc; | |
break; | |
} | |
} | |
circles.add(circle); | |
//stroke(circle.colour, 255, 255); | |
float dist = dist(width/2, height/2, circle.x, circle.y); | |
stroke(circle.colour, map(dist, 0, height/2, 255, 99+circle.r), height-dist); | |
circle(circle.x, circle.y, circle.r*2);//Processing uses diameter | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment