• fullscreen
  • Circle.pde
  • Thermostats.pde
  • simulatedAnnealingWithCircleOverlaps.pde
  • 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);
        //line(loc.x, loc.y, target.x, target.y);
      }
    
      // returns: how many tries did I take this turn?
      int nudge(float temperature) {
    
        float maxDistanceForPointMass = 300.0 * distanceFromTarget();
    
        PVector origLoc = loc.get();
        int maxTries = ceil(100 * temperature * temperature);
        int tries = 0;
        while (tries < maxTries) {
          tries += 1;
          PVector nudge = getNudgeVector(temperature);
          float dist = maxDistanceForPointMass * random(temperature) / (rad * rad);
          nudge.mult(dist);
    
          loc.x = constrain(origLoc.x + nudge.x, 0, width);
          loc.y = constrain(origLoc.y + nudge.y, 0, height);
    
          if (!anyOverlaps(this, circles)) {
            return tries;
          }
          else {
            loc = origLoc.get();
          }
        }
        return tries;
      }
    
      PVector getNudgeVector(float temperature) {
        PVector dir = PVector.sub(target, loc);
        float angle = atan2(dir.y, dir.x);
    
        float dAngle = 1.2 * 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;
      }
    
      float distanceFromTarget() {
        return PVector.dist(loc, target);
      }
    }
    
    
    interface Thermostat {
      float currentTemp();
    }
    
    class Oscillator implements Thermostat {
      private float theta = 0;
      private float dTheta = 0.03;
      
      private float min;
      private float max;
      
      Oscillator(float min, float max) {
        this.min = min;
        this.max = max;
      }
      
      float currentTemp() {
        theta += dTheta;
        return map(sin(theta), -1, 1, min, max);
      }
    }
    
    class MouseThermostat implements Thermostat {
      float currentTemp() {
         return map(mouseY, height, 0, 0, 1);
      }
    }
    
    class LinearCool implements Thermostat {
      private float temperature;
      private float decrement;
    
      LinearCool(float initialTemp, float decrement) {
        this.temperature = initialTemp;
        this.decrement = decrement;
      }
      
      float currentTemp() {
        temperature = constrain(temperature - 0.005, 0, 1);
        return temperature;
      }
    }
    
    class AsymptoticCool implements Thermostat {
      private float temperature;
      private float dampening;
    
      AsymptoticCool(float initialTemp, float dampening) {
        this.temperature = initialTemp;
        this.dampening = dampening;
      }
      
      float currentTemp() {
        return temperature *= dampening;
      }
    }
    
    int n = 200;
    int minRadius = 10;
    int maxRadius = 30;
    
    Thermostat thermostat;
    int thermostatIndex = 0;
    
    ArrayList<Circle> circles;
    
    void setup() {
      size(600, 600);
      colorMode(HSB);
      ellipseMode(CENTER);
      background(30);
      smooth();
      //textFont(createFont("CharcoalCY", 15));
      textAlign(LEFT);
      
      makeRandomCircles();
      makeThermostat();
    }
    
    void makeRandomCircles() {
      circles = new ArrayList<Circle>();
      addRandomCircles(n);
    }
    
    void makeThermostat() {
      switch(thermostatIndex++ % 4) {
        case 0: thermostat = new AsymptoticCool(1.0, 0.99); break;
        case 1: thermostat = new LinearCool(1.0, 0.005); break;
        case 2: thermostat = new Oscillator(0.4, 1); break;
        case 3: thermostat = new MouseThermostat(); break;
      }
    }
    
    void addRandomCircles(int num) {
      for (int i = 0; i < num; i++) {
        Circle c = makeRandomCircle();
        circles.add(c);
      }
    }
    
    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 * height);
      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 draw() {
      background(30);
      
      float temperature = thermostat.currentTemp();
      nudgeCircles(temperature);
      drawCircles();
      drawTemperature(temperature);
      
      fill(200, 150);
      //drawFps();
      //drawElapsed();
      drawAvgDistanceFromTarget();
    }
    
    void mouseClicked() {
      makeRandomCircles();
      makeThermostat();
    }
    
    void keyPressed() {
      //addRandomCircles(floor(n * 0.5));
      //save("thumbnail.png");
    }
    
    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) {
      int numTries = 0;
      for (int i = 0; i < circles.size(); i++) {
        numTries += circles.get(i).nudge(temperature);
      }
      
      //fill(200, 150);
      //text("avg tries: " + round(numTries/circles.size()), 50, height-10);
    }
    
    void drawCircles() {
      for (int i = 0; i < circles.size(); i++) {
        circles.get(i).draw();
      }
    }
    
    void drawAvgDistanceFromTarget() {
      float sumDist = 0;
      for (int i = 0; i < circles.size(); i++) {
        sumDist += circles.get(i).distanceFromTarget();
      }
      float avgDist = sumDist / circles.size();
      
      text("avg dist from target: " + round(100 * avgDist / width) + "% (of sketch width)", 150, height-10);
    }
    
    void drawElapsed() {
      text(floor(millis() / 1000.0) + " secs", 200, height-30);
    }
    
    void drawFps() {
      text("fps: " + round(frameRate), width-80, height-10);
    }
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Dan Bernier

    Arrange circles with simulated annealing, #2

    Add to Faves Me Likey@!
    You must login/register to add this sketch to your favorites.

    A much improved version of this one: http://openprocessing.org/visuals/?visualID=53004

    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.

    Controls:
    - click the mouse to reset, and change the way the temperature changes: asymptotic cooling, linear cooling, oscillating, or controlled by the mouse

    Dan Bernier
    26 Jun 2012
    See http://www.openprocessing.org/sketch/64696 for an update that arranges words, not circles.
    You need to login/register to comment.