• fullscreen
  • Circle.pde
  • drawHelpers.pde
  • sa.pde
  • simulatedAnnealingWithCircleOverlaps_Shapes.pde
  • Thermostats.pde
  • /*
     * Oops! A circle is actually a Word Shape. Sorry! =p
     */
    
    WordShaper wordShaper = new WordShaper();
    PFont font = createFont("sans", 1);
    BBTreeBuilder bbTreeBuilder = new BBTreeBuilder();
    
    int numNulls = 0;
    
    class Circle {
      PVector loc;
      float rad;
      Shape wordShape;
      BBTree bbTree;
      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;
        
        float randomSize = 5 + random(60);
        float randomAngle = random(TWO_PI);
        int minShapeSize = 2;
        
        this.wordShape = wordShaper.getShapeFor("hello", font, randomSize, randomAngle, minShapeSize);
        
        this.bbTree = bbTreeBuilder.makeTree(this.wordShape, 1); // last arg = swelling
        this.col = color(220);
      }
    
      void draw() {
        
        Shape placedShape = AffineTransform.getTranslateInstance(loc.x, loc.y).createTransformedShape(wordShape);
        
        GeneralPath path2d = new GeneralPath(placedShape);
    
        Graphics2D g2 = ((PGraphicsJava2D)g).g2;
    
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setPaint(new Color(col, true));
        g2.fill(path2d);
      }
    
      // 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 temperature) {
        // Random vector
        float dist = 300;
        float dir = random(TWO_PI);
        float dx = dist * cos(dir);
        float dy = dist * sin(dir);
        return new PVector(dx, dy);
      }
    
      boolean overlaps(Circle c) {
        wordcram.Timer setLoc = wordcram.Timer.start("setLocation");
        //return PVector.dist(loc, c.loc) < (rad + c.rad) * 0.5;
        this.bbTree.setLocation(round(loc.x), round(loc.y));
        c.bbTree.setLocation(round(c.loc.x), round(c.loc.y));  // TODO ick. for now, whatever.
        setLoc.stop();
        
        wordcram.Timer anyOverlaps = wordcram.Timer.start("overlaps");
        boolean thisOverlapsOther = this.bbTree.overlaps(c.bbTree);
        anyOverlaps.stop();
        
        return thisOverlapsOther;
      }
    
      float distanceFromTarget() {
        return PVector.dist(loc, target);
      }
    }
    
    
    
    void drawTemperature(float temperature) {
      pushStyle();
      fill(0, 0, 255, 50);
      noStroke();
      float barHeight = temperature * height;
      rect(0, height - barHeight, 30, barHeight);
      popStyle();
    }
    
    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);
    }
    
    
    void makeRandomCircles() {
      circles = new ArrayList<Circle>();
      addRandomCircles(n);
    }
    
    void addRandomCircles(int num) {
      for (int i = 0; i < num; i++) {
        Circle c = makeRandomCircle();
        if (c.wordShape != null) {
          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);
      //rand.loc = rand.target;
      
      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;
    }
    
    import wordcram.*;
    import wordcram.text.*;
    
    import java.awt.*;
    import java.awt.font.*;
    import java.awt.geom.*;
    
    int n = 50;
    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();
      println("made random circles");
      makeThermostat();
    }
    
    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 draw() {
      background(30);
      
      float temperature = thermostat.currentTemp();
      drawCircles();
      nudgeCircles(temperature);
      drawTemperature(temperature);
      
      fill(200, 150);
      //drawFps();
      //drawElapsed();
      drawAvgDistanceFromTarget();
    }
    
    void mouseClicked() {
      makeRandomCircles();
      makeThermostat();
    }
    
    void keyPressed() {
      //addRandomCircles(floor(n * 0.5));
      //save("thumbnail.png");
      wordcram.Timer.report();
    }
    
    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);
    }
    
    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;
      }
    }
    

    code

    tweaks (0)

    about this sketch

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

    license

    advertisement


    Dan Bernier

    Arrange words with simulated annealing

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

    An update of http://www.openprocessing.org/sketch/53214 but now with word shapes, using WordCram core classes. I think this shows that WordCram can use simulated annealing as a better layout/packing algorithm. Stay tuned!

    You need to login/register to comment.