spoike
9/1/2011 - 11:16 AM

Quad Tree Visualization

Quad Tree Visualization

/**
 * Quad Tree implementation and visualization in Processing. Executes
 * with Processing 1.5.1
 *
 * Adds points to a quad tree and shows visually as the quad tree
 * splits and repushes the values as new values are added.
 *
 * Restart QuadTree with new values with a key press.
 */

int w = 512;
int h = 512;
int amountToPut = 64;
color backCol = color(255, 255, 255);
color borderCol = color(10, 50, 175);
color valueCol = color(240, 10, 50);
int delayTime = 10;
int delayRestartTime = 5000;
boolean logEnabled = false;

void setup() {
  size(w*2, h);
  buffer = createGraphics(w, h, JAVA2D);
  root = new QTreeNode(0, 0, w, h);
  background(backCol);
}

PGraphics buffer;
QTreeNode root;
int currentStep = 0;

ClusterPoint[] clusters = {
  new ClusterPoint(100, 50, 400, 200),
  new ClusterPoint(300, 400, 100, 400),
  new ClusterPoint(300, 150, 400, 400),
  new ClusterPoint(150, 390, 350, 100),
};

class ClusterPoint {
  float x;
  float y;
  float diffX;
  float diffY;
  public ClusterPoint(float x, float y, float diffX, float diffY) {
    this.x = x;
    this.y = y;
    this.diffX = diffX;
    this.diffY = diffY;
  }
  
  public QTreeVal getRandomPoint() {
    float rX = -1f;
    while (rX > w || rX < 0f) {
      rX = x + random(diffX) - (diffX/2f);      
    }
    float rY = -1f;
    while (rY > h || rY < 0f) {
      rY = y + random(diffY) - (diffY/2f);
    }
    return new QTreeVal(rX, rY);
  }
}

void addToRoot() {
  ClusterPoint cp = clusters[(int) random(clusters.length)];
  root.put(cp.getRandomPoint());
}

void draw() {
  if (currentStep < amountToPut) {
    currentStep++;
    addToRoot();
  }
  stroke(borderCol);
  noFill();
  buffer.beginDraw();
  buffer.background(backCol);
  buffer.pushMatrix();
  // Tree Graph
  drawTree(buffer);
  image(buffer, width/2, 0);

  // Draw Graph
  buffer.background(backCol);
  buffer.scale(0.95, 0.95);
  buffer.translate(w*0.025, h*0.025);
  drawGraph(buffer);
  buffer.popMatrix();
  buffer.endDraw();
  image(buffer, 0, 0);
  delay(delayTime);
}

void drawTree(PGraphics buffer) {
  buffer.beginDraw();
  buffer.smooth();
  buffer.background(backCol);
  drawTreeNode(buffer, root, width/4f, 20, 1);
  buffer.endDraw();
}

void drawTreeNode(PGraphics buffer, QTreeNode node, float x, float y, int depth) {
  float a = 255-((depth-1)*40);
  if (a < 1f) a = 1f;
  float r = 20 - ((depth-1)*5.5f);
  if (r < 1f) r = 1f;
  buffer.stroke(borderCol, a);
  float sWidth = 3.5 - (depth-1);
  if (sWidth < 1) sWidth = 1;
  buffer.strokeWeight(sWidth);
  if(!node.isSplit) {
    if(node.val == null) {
      // quad has no value, blue border
      buffer.stroke(borderCol, a);
      buffer.fill(255);      
    } else {
      // quad has a value, red border
      buffer.stroke(valueCol, a);
      buffer.fill(255);
    }
    buffer.ellipse(x, y, r*0.8, r*0.8);
  }
  else {
    float w = ((width/2f)/(pow(4f, depth)));
    float w2 = w/2f;
    float x1 = x - (w2 * 3);
    float x2 = x - (w2 * 1);
    float x3 = x + (w2 * 1);
    float x4 = x + (w2 * 3);
    float yfac = (depth*0.60);
    float y1 = y+((height/3f)/depth);
    float y2 = y1;
    float y3 = y2;
    float y4 = y3;
    buffer.line(x, y, x1, y1);
    buffer.line(x, y, x2, y2);
    buffer.line(x, y, x3, y3);
    buffer.line(x, y, x4, y4);
    drawTreeNode(buffer, node.ne, x1, y1, depth+1);
    drawTreeNode(buffer, node.nw, x2, y2, depth+1);
    drawTreeNode(buffer, node.sw, x3, y3, depth+1);
    drawTreeNode(buffer, node.se, x4, y4, depth+1);
    buffer.strokeWeight(sWidth);
    buffer.stroke(borderCol, a);
    buffer.fill(backCol);
    buffer.rect(x-(r/2), y-(r/2), r, r);
  }
}

void drawGraph(PGraphics buffer) {
  buffer.noSmooth();
  buffer.strokeWeight(1);
  root.drawBorders(buffer, 1);
  buffer.strokeWeight(1);
  buffer.smooth();
  root.drawValues(buffer, 1);
}

class QTreeVal {
  float x;
  float y;
  QTreeVal(float x, float y){
    this.x = x;
    this.y = y;
  }
  public String toString() {
    return "value (" + x + "," + y + ")";
  }
}

class QTreeNode {
  float x, y, w, h, midw, midh, midx, midy;
  QTreeVal val;
  boolean isSplit = false;
  QTreeNode nw, ne, se, sw;
  
  QTreeNode(float x, float y, float w, float h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.midw = w/2f;
    this.midh = h/2f;
    this.midx = x+midw;
    this.midy = y+midh;
  }
  public void drawBorders(PGraphics pg, int depth) {
    float sW = 4.5f/(depth);
    if(sW < 0.5f) sW = 0.5f;
    pg.strokeWeight(sW);
    pg.stroke(borderCol);
    pg.noFill();
    pg.rect(x, y, w, h);
    if (isSplit) {
      int d = depth + 1;
      nw.drawBorders(pg, d);
      ne.drawBorders(pg, d);
      se.drawBorders(pg, d);
      sw.drawBorders(pg, d);
    }
  }
  public void drawValues(PGraphics pg, int depth) {
    float sW = 3f/(depth);
    if(sW < 1f) sW = 1f;
    pg.strokeWeight(sW);
    if (val != null) {
      pg.stroke(valueCol);
      pg.fill(255);
      //pg.fill(valueCol);
      pg.ellipse(val.x, val.y, 4, 4);
    }
    if (isSplit) {
      int d = depth+1;
      nw.drawValues(pg, d);
      ne.drawValues(pg, d);
      se.drawValues(pg, d);
      sw.drawValues(pg, d);
    }
  }
  public boolean isInside(QTreeVal val) {
    return val.x >= x && val.x <= x+w && val.y >= y && val.y <= y+h;
  }
  public void push(QTreeVal val) {
    this.val = val;
  }
  public void put(QTreeVal val) {
    if (isSplit) {
      if (ne.isInside(val)) ne.put(val);
      if (nw.isInside(val)) nw.put(val);
      if (sw.isInside(val)) sw.put(val);
      if (se.isInside(val)) se.put(val);
    }
    else if (this.val != null) {
      isSplit = true;
      nw = new QTreeNode(x, y, midw, midh);
      ne = new QTreeNode(x+midw, y, midw, midh);
      se = new QTreeNode(x+midw, y+midh, midw, midh);
      sw = new QTreeNode(x, y+midh, midw, midh);
      doprint("Repushing... ");
      put(this.val);
      this.val = null;
      put(val);
    }
    else {
      doprintln("Pushed " + val + " into " + this);
      push(val);
    }
  }
  public String toString() {
    return "quad (" + x + "," + y + "," + (x+w) + "," + (y+h) + ")";
  }
}

void restart() {
    currentStep = 0;
    root = new QTreeNode(0, 0, w, h);
}

void mousePressed() {
  restart();
}

void keyPressed() {
  restart();
}

public void doprint(String s) {
  if (logEnabled) {
    print(s);
  }
}

public void doprintln(String s) {
  if (logEnabled) {
    println(s);
  }
}