fullscreen /*
* 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;
}
}
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!