Skip to content

Instantly share code, notes, and snippets.

@volfegan
Last active March 15, 2021 20:40
Show Gist options
  • Save volfegan/9f4e8f594eda34c292eedba4f3d51464 to your computer and use it in GitHub Desktop.
Save volfegan/9f4e8f594eda34c292eedba4f3d51464 to your computer and use it in GitHub Desktop.
Bubbles drifting with circle packing algorithm
// 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;
}
}
//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