class Circle {
PVector loc;
float rad;
color col;
PVector target = new PVector(width/2, height/2);
Circle(float x, float y, float rad) {
this.loc = new PVector(x, y);
this.rad = rad;
this.col = color(220);
}
void draw() {
stroke(col);
noFill();
ellipse(loc.x, loc.y, rad, rad);
}
void nudge(float temperature) {
PVector origLoc = loc.get();
int tries = 100;
while (tries > 0) {
tries -= 1;
PVector d = getNudgeVector(temperature);
float dist = 1000.0 * random(temperature) / (rad * rad);
d.mult(dist);
loc.x = constrain(loc.x + d.x, 0, width);
loc.y = constrain(loc.y + d.y, 0, height);
if (!anyOverlaps(this, circles)) {
return;
}
else {
loc = origLoc.get();
}
}
}
PVector getNudgeVector(float temperature) {
PVector dir = PVector.sub(target, loc);
float angle = atan2(dir.y, dir.x);
float dAngle = 1.5 * PI * temperature * (random(1) - 0.5);
angle += dAngle;
return new PVector(cos(angle), sin(angle));
}
PVector getRandomNudgeVector(float dist) {
// Random vector
float dir = random(TWO_PI);
float dx = dist * cos(dir);
float dy = dist * sin(dir);
return new PVector(dx, dy);
}
boolean overlaps(Circle c) {
return PVector.dist(loc, c.loc) < (rad + c.rad) * 0.5;
}
}
class Oscillator {
private float theta = 0;
private float dTheta = 0.03;
float value() {
//theta = (theta + dTheta) % TWO_PI;
theta += dTheta;
return sin(theta);
}
}
/*
class QuadTree<T> {
ArrayList<T> items;
ArrayList<QuadTree<T>> kids;
int left, top, right, bottom;
QuadTree<T>(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
void add(T item) {
;
}
}
class CirclesMap {
ArrayList<Circle> circles = new ArrayList<Circle>();
TreeSet<Circle> byX = new TreeSet<Circle>(new ByX());
TreeSet<Circle> byY = new TreeSet<Circle>(new ByY());
void add(Circle c) {
circles.add(c);
byX.add(c);
byY.add(c);
}
ArrayList<Circle> bbSearch(float x, float y, float dist) {
HashSet<Circle> xMatches = new HashSet<Circle>();
Iterator<Circle> iter = byX.iterator();
while (iter.hasNext()) {
if (abs(iter.next().x - x) < dist) {
HashSet<Circle> yMatches = new HashSet<Circle>();
return new ArrayList<Circle>(xMatches.
}
}
class ByX implements Comparator<Circle> {
int compare(Circle a, Circle b) {
return a.x.compareTo(b.x);
}
}
class ByY implements Comparator<Circle> {
int compare(Circle a, Circle b) {
return a.y.compareTo(b.y);
}
}
*/
int n = 200;
int minRadius = 10;
int maxRadius = 30;
float temperature = 1.0;
ArrayList<Circle> circles;
void setup() {
size(600, 600);
colorMode(HSB);
ellipseMode(CENTER);
background(30);
smooth();
makeRandomCircles();
}
void makeRandomCircles() {
circles = new ArrayList<Circle>();
addRandomCircles(n);
}
Circle makeRandomCircle() {
Circle rand = new Circle(random(width), random(height), random(minRadius, maxRadius));
while (anyOverlaps(rand, circles)) {
rand = new Circle(random(width), random(height), random(minRadius, maxRadius));
}
float target = random(1);
rand.target = new PVector(target * width, target * width);
rand.col = color(target * 128, 255, 255);
return rand;
}
boolean anyOverlaps(Circle c, ArrayList<Circle> circles) {
for (int i = 0; i < circles.size(); i++) {
if (c != circles.get(i) && c.overlaps(circles.get(i))) {
return true;
}
}
return false;
}
void addRandomCircles(int num) {
for (int i = 0; i < num; i++) {
circles.add(makeRandomCircle());
}
}
Oscillator tempOsc = new Oscillator();
void draw() {
background(30);
//temperature = map(mouseY, height, 0, 0, 1);
//temperature *= 0.999;
//temperature = constrain(temperature - 0.005, 0, 1);
temperature = map(tempOsc.value(), -1, 1, 0.4, 1);
nudgeCircles(temperature);
drawCircles();
drawTemperature(temperature);
}
void mouseClicked() {
makeRandomCircles();
temperature = 1;
}
void keyPressed() {
addRandomCircles(floor(n * 0.5));
}
void drawTemperature(float temperature) {
pushStyle();
fill(0, 0, 255, 50);
noStroke();
float barHeight = temperature * height;
rect(0, height - barHeight, 30, barHeight);
popStyle();
}
void nudgeCircles(float temperature) {
for (int i = 0; i < circles.size(); i++) {
circles.get(i).nudge(temperature);
}
}
void nudgeOverlappingCircles(float temperature) {
for (int i = 0; i < circles.size(); i++) {
circles.get(i).col = color(220);
}
for (int i = 0; i < circles.size()-1; i++) {
Circle a = circles.get(i);
for (int j = i+1; j < circles.size(); j++) {
Circle b = circles.get(j);
if (a.overlaps(b)) {
b.nudge(temperature);
a.col = b.col = color(0, 220, 220);
}
}
}
}
void drawCircles() {
for (int i = 0; i < circles.size(); i++) {
circles.get(i).draw();
}
}
This sketch arranges circles along a diagonal - top-left (red) to bottom-right (blue).
It arranges them with simulated annealing. Each circle moves around a bit - more, if the temperature is high, and less, if the temperature is low. Smaller circles can move faster. The temperature is displayed as a transparent bar on the left, and currently oscillates between 0.4 and 1.0.
Controls:
- click the mouse to reset
- type a key to add more circles (too many will slow it down though)