///// /////
// //
// F R A C T A L //
// //
///// /////
// draw a fractal graph
// using an iterated function system.
PMatrix2D[] mat;
///// f r a c t a l f u n c t i o n s /////
void fractalDraw() {
// set recursion and stroke
int d = mousePressed ? min(depth, dragdepth) : depth;
float o = opacity/2 * pow(2, 1f/d);
stroke(c2, o);
// get control points and IFS matrices
PMatrix2D mm = getUnitMatrix(k1.x, k1.y, k2.x, k2.y);
getPoints(mm);
getMatrices();
// draw the fractal
pushMatrix();
applyMatrix(mm);
fractaline(d);
popMatrix();
}
// recursive function
void fractaline(int d) {
if(d==0)
line(0, 0, 1, 0);
else {
for(int i=0; i<m; i++) {
pushMatrix();
applyMatrix(mat[i]);
fractaline(d-1);
popMatrix();
}
}
}
// transform knob positions (screen coordinates)
// to control points (norm coordinates)
void getPoints(PMatrix2D mm) {
PMatrix2D m2 = mm.get();
m2.invert();
for(int i=0; i<n; i++) {
Knob k = getKnob(i);
k.px = m2.multX(k.x, k.y);
k.py = m2.multY(k.x, k.y);
}
}
// precalculate transformation matrices
void getMatrices() {
mat = new PMatrix2D[m];
for(int i=0; i<m; i++) {
Link l = getLink(i);
Knob a = l.k1, b = l.k2;
mat[i] = getUnitMatrix(a.px, a.py, b.px, b.py);
}
}
// get a matrix that maps <0,0> <1,0> to <x1,y1> <x2,y2>
PMatrix2D getUnitMatrix(float x1, float y1, float x2, float y2) {
return getMatrix(x1, y1, x2, y2, true);
}
PMatrix2D getMatrix(float x1, float y1, float x2, float y2) {
return getMatrix( x1, y1, x2, y2, false);
}
PMatrix2D getMatrix(float x1, float y1, float x2, float y2, boolean scaled) {
float ang = atan2(y2-y1, x2-x1);
float d = dist(x1, y1, x2, y2);
PMatrix2D m = new PMatrix2D();
m.translate(x1, y1);
m.rotate(ang);
if(scaled) m.scale(d);
return m;
}
///// /////
// //
// C L I P P I N G //
// //
///// /////
// workaround for P2D clipping bug
void line(float x1, float y1, float x2, float y2) {
float sx1 = screenX(x1, y1);
float sy1 = screenY(x1, y1);
float sx2 = screenX(x2, y2);
float sy2 = screenY(x2, y2);
pushMatrix();
resetMatrix();
clipLine(sx1, sy1, sx2, sy2);
popMatrix();
}
// Liang-Barsky line clipping
void clipLine(float x1, float y1, float x2, float y2) {
float p, q, r;
float u1 = 0.0, u2 = 1.0;
float dx = (x2 - x1);
float dy = (y2 - y1);
float ap[] = { -1 * dx, dx, -1 * dy, dy };
float aq[] = { x1, width - x1, y1, height - y1 };
for( int i=0; i<4; i++) {
p = ap[i]; q = aq[i];
if( p==0 && q<0 ) return;
r = q/p;
if(p<0) u1 = max(u1, r);
if(p>0) u2 = min(u2, r);
if(u1>u2) return;
}
if(u2 < 1) { x2 = (x1 + u2*dx); y2 = (y1 + u2*dy);}
if(u1 > 0) { x1 = (x1 + u1*dx); y1 = (y1 + u1*dy);}
try {
super.line(x1, y1, x2, y2);
}
// catching a very weird processing bug...
catch (ArrayIndexOutOfBoundsException e) {
println("P2D sucks.");
}
}
///// /////
// //
// //
// Fractal Graph Designer //
// //
// //
///// /////
///// (c) Martin Schneider 2010 /////
int n0 = 5;
int depth = 6;
int dragdepth = 5;
int maxdepth = 8;
color c1 = #000000, c2 = #6666ff, c3 = #ff0000;
int opacity = 200;
int w, h;
int drawdepth;
boolean softLines;
void setup() {
size(800, 500, P2D);
w = width / 2;
h = height / 2;
resetKnobs();
resetLinks();
resetCursor();
noFill();
smooth();
}
void draw() {
background(255);
strokeWeight(softLines ? 1 : .5);
fractalDraw();
}
void mousePressed() {
// store mouse position
ppmouseX = mouseX;
ppmouseY = mouseY;
// create knob on the plane
if(mouseButton==LEFT && active == null)
active = createKnob(mouseX, mouseY);
// create knob on an edge
if(mouseButton==LEFT && activeLink != null && active != null) {
createLink(activeLink.k1, active);
createLink(active, activeLink.k2);
removeLink(activeLink);
activeLink = null;
}
}
void mouseReleased() {
// create connected knob
if(mouseButton==CENTER && active != null) {
createLink(active, active = createKnob(mouseX, mouseY));
}
// deactivate knots and links
active = null;
activeLink = null;
}
void keyPressed() {
switch(key) {
case ' ' : resetKnobs(); resetLinks(); break;
case 'x' : showKnobs = !showKnobs; break;
case '-' : showLinks = !showLinks; break;
case 'v' : showDirection = !showDirection; break;
case 's' : softLines = !softLines; break;
case CODED :
switch(keyCode) {
case UP : depth++; break;
case DOWN : depth-- ; break;
case LEFT : dragdepth--; break;
case RIGHT : dragdepth++; break;
}
break;
}
// limit iterations
depth = constrain(depth, 1, maxdepth);
dragdepth = constrain(dragdepth, 1, maxdepth);
}
///// /////
// //
// L I N K S //
// //
///// /////
// interactive edges of the graph
int m; // number of links
boolean showLinks = true;
boolean showDirection = true;
ArrayList links = new ArrayList();
Link activeLink;
///// l i n k c l a s s /////
public class Link {
Knob k1, k2;
Link(Knob _k1, Knob _k2) {
k1 = _k1;
k2 = _k2;
}
void draw() {
stroke(c1, opacity);
if(!showLinks) return;
if(showDirection)
arrow(k1.x, k1.y, k2.x, k2.y);
else
line(k1.x, k1.y, k2.x, k2.y);
}
// check if the mouse position lies within some
// distance from the line (and from its endpoints)
boolean inside() {
float t = 3; // max distance
float d = dist(k1.x, k1.y, k2.x, k2.y);
PMatrix2D m = getMatrix(k1.x, k1.y, k2.x, k2.y);
m.invert();
float x = m.multX(mouseX, mouseY);
float y = m.multY(mouseX, mouseY);
return (x > -t && x <(d+t) && y>-t && y<t);
}
void mouseEvent(MouseEvent event) {
int e = event.getID();
if(e == MouseEvent.MOUSE_PRESSED && inside() && active != k1 && active != k2) {
switch(mouseButton) {
case LEFT :
activeLink = this;
break;
case RIGHT :
deleteLink(this);
break;
}
}
}
}
///// l i n k f u n c t i o n s /////
void resetLinks() {
while(m>0) removeLink(getLink(0));
createLinks();
}
void createLinks() {
int m0 = n0-1;
for(int i=0; i<m0; i++) createLink(getKnob(i), getKnob(i+1));
}
void createLink(Knob a, Knob b) {
if(a==b) return; // avoid selvedges
if(getLink(a, b) != null) return; // avoid multiple edges
Link l = new Link(a, b);
links.add(l);
registerDraw(l);
registerMouseEvent(l);
resetCursor();
m++;
}
// delete a link taking care of the connected knobs
void deleteLink(Link l) {
removeLink(l);
if (countLinks(l.k2) == 0) deleteKnob(l.k2);
if (countLinks(l.k1) == 0) deleteKnob(l.k1);
}
void removeLink(Link l) {
links.remove(l);
unregisterDraw(l);
unregisterMouseEvent(l);
m--;
}
Link getLink(int i) {
return (Link) links.get(i);
}
Link getLink(Knob a, Knob b) {
for(int i=0; i<m; i++) {
Link l = getLink(i);
if(l.k1==a && l.k2 == b) return l;
}
return null;
}
int countLinks(Knob k) {
return inLinks(k) + outLinks(k);
}
int inLinks(Knob k) {
int in = 0;
for(int i=0; i<m; i++) if (getLink(i).k1 == k) in++;
return in;
}
int outLinks(Knob k) {
int out = 0;
for(int i=0; i<m; i++) if (getLink(i).k2 == k) out++;
return out;
}
// draw an arrow to indicate link direction
void arrow(float x1, float y1, float x2, float y2) {
PMatrix2D m = getMatrix(x1, y1, x2, y2);
float w = 12, d = dist(x1, y1, x2, y2) - w;
if(d<0) { w = d+w; d = 0; } // extremely small distances
float h = w * .4;
pushMatrix();
applyMatrix(m);
fill(255);
line(0, 0, d, 0);
beginShape();
vertex(d, +h);
vertex(d+w, 0);
vertex(d, -h);
endShape(CLOSE);
popMatrix();
}
///// /////
// //
// K N O B S //
// //
///// /////
// interactive nodes of the graph
int n; // number of knobs
boolean showKnobs = true;
ArrayList knobs = new ArrayList();
Knob active, active2, k1, k2;
///// k n o b c l a s s /////
public class Knob {
float x, y, r = 5;
float px, py;
void move(int _x, int _y) {
x = _x; y = _y;
}
void draw() {
if(!showKnobs) return;
stroke(this == active || this == active2 ? c3 : c1, opacity);
// draw regular knobs as x-shape
line(x-r, y-r, x+r, y+r);
line(x-r, y+r, x+r, y-r);
// draw anchor knobs as asterisk
if(this == k1 || this == k2) {
float r2=r*1.4;
line(x, y-r2, x, y+r2);
line(x-r2, y, x+r2, y);
}
}
// check if the mouse is inside the knob
boolean inside() {
return dist(mouseX, mouseY, x, y) <= 1.4 * r;
}
void mouseEvent(MouseEvent event) {
int e = event.getID();
// activate the knob
if (e == MouseEvent.MOUSE_PRESSED && inside()) {
active = this;
}
/// single knob interaction ///
else if(active==this) {
switch(mouseButton) {
case LEFT:
switch(e) {
case MouseEvent.MOUSE_DRAGGED : // move knob
x = mouseX;
y = mouseY;
break;
}
break;
case RIGHT:
switch(e) {
case MouseEvent.MOUSE_DRAGGED : // deactivate knob
active = null;
mouseEvent(event);
break;
case MouseEvent.MOUSE_RELEASED : // delete knob
if(inside()) deleteKnob(this);
break;
}
break;
}
}
/// collective knob interaction ///
else if (active==null) {
if(e == MouseEvent.MOUSE_DRAGGED && activeLink==null)
switch(mouseButton) {
case RIGHT : // move knob positions
x += mouseX - pmouseX;
y += mouseY - pmouseY;
break;
case CENTER : // scale knob positions
x -= (x - ppmouseX) * (mouseY - pmouseY) / h;
y -= (y - ppmouseY) * (mouseY - pmouseY) / h;
break;
}
}
/// interaction involving two knobs ///
else if(mouseButton == CENTER) {
switch(e) {
case MouseEvent.MOUSE_DRAGGED : // pick the second active knob
active2 = inside() ? this : active2==this ? null : active2;
break;
case MouseEvent.MOUSE_RELEASED : // connect the two active knobs
if (inside()) {
createLink(active, this);
active = null; active2 = null; // prevent additional activity
}
break;
}
}
}
}
///// k n o b f u n c t i o n s /////
void resetKnobs() {
while(n>0) removeKnob(getKnob(0));
createKnobs(n0);
for(int i=0; i<n; i++) getKnob(i).move(width*(i+1)/(n+1), (int) random(h*.5, h*1.5));
}
void createKnobs(int n) {
for(int i=0; i<n; i++) createKnob();
k1 = getKnob(0);
k2 = getKnob(n-1);
}
Knob getKnob(int i) {
return (Knob) knobs.get(i);
}
Knob createKnob() {
Knob k = new Knob();
knobs.add(k);
registerDraw(k);
registerMouseEvent(k);
resetCursor();
n++;
return k;
}
// delete a knob taking care of the connected links
void deleteKnob(Knob k) {
int in = inLinks(k);
int out = outLinks(k);
if( k==k1 || k==k2 ) return; // do not remove anchor knobs
if(in > 1 || out >1) return; // do not remove multiply connected knobs
rewireLinks(k);
removeKnob(k);
}
void removeKnob(Knob k) {
knobs.remove(k);
unregisterDraw(k);
unregisterMouseEvent(k);
n--;
}
Knob createKnob(int x, int y) {
Knob k = createKnob();
k.move(x, y);
return k;
}
void rewireLinks(Knob k) {
Link a = null, b = null;
for(int i=0; i<m; i++) {
Link l = getLink(i);
if (l.k2 == k) a = l; // incoming link
if (l.k1 == k) b = l; // outgoing link
}
if(a!=null && b!=null) createLink(a.k1, b.k2); // rewire
if(b!=null) removeLink(b);
if(a!=null) removeLink(a);
}
///// /////
// //
// C U R S O R //
// //
///// /////
// provide visual feedback
// for graph manipulation.
Cursor cur = new Cursor();
int ppmouseX, ppmouseY; // previous mouse pressed position
///// c u r s o r c l a s s /////
public class Cursor {
void draw() {
stroke(c3, opacity);
if (active != null) {
if(mousePressed && mouseButton == CENTER) {
arrow(active.x, active.y, mouseX, mouseY);
}
}
else if (mousePressed) {
int x = mouseButton == CENTER ? ppmouseX : mouseX;
int y = mouseButton == CENTER ? ppmouseY : mouseY;
line(0, y, width, y);
line(x, 0, x, height);
}
}
}
///// c u r s o r f u n c t i o n s /////
void resetCursor() {
unregisterDraw(cur);
registerDraw(cur);
}