We have been experimenting with ideas for a distributed structure. Normally we would use recursion to create a branching structure from scratch, but in this case we are creating the structure in reverse with a finite amount of points. Below is the logic we have developed along with the processing sketch above. For now we are using a flat grid, but something like this could also be used to support a complex surface as long as it his defined subdivision and that all subdivisions can be projected straight down without intersecting.

Branching SOFTlab

Given an original array of points a selection can be made of those points or a point anywhere on that coordinate system. The first step is to then group the points that are closest to the selected points.

Branching SOFTlab

In the case of a two-dimensional array of points and that the typical master branch will divide twice, you will need to account for odd or remainder points. This can be done in the first layer of branches by joining the “remainder” point to its nearest neighbor. When two points are joined the new point is the average between those two points. You will now have a new array of points that should be less in number than the first and you will run the same query on the new array.

Branching SOFTlab

Now that you have accounted for “remainder” points you should have a more even distribution of branches.

Branching SOFTlab

The branches on the left will end sooner that the branch on the right because the original array of point was smaller. In the case where the point array only has one value there is only one branch and the array keep that point. The branch on the right also has an odd number of points at this level. After the first layer you can institute a new rule where an odd number or “remainder” point joins with its two nearest neighbors.

Branching SOFTlab

The final iteration should be when all arrays are down to one point. In that case the “trunks” of smaller beginning arrays will be taller than others with a larger starting array of points.

Below is the code fro the processing sketch.

import controlP5.*; 

ControlP5 cp5;

int grid = 50;
int space = 10;

int gsize = grid*space;

float[][] grow = new float[grid][grid];
float[][] points = new float[grid][2];
int[] czone = new int[60];
int[][] cc = new int[grid][grid];

  
IntList xpoint = new IntList();
IntList ypoint = new IntList();

ArrayList zones = new ArrayList();


boolean generate = false;
boolean showgrid = true;
boolean showcolor = false;

int oldx = mouseX;
int oldy = mouseY;
float rotx = 0;
float roty = 0;
float zcam = 0;

float thickness = 3;

int z_limit = 10;

public void setup() {
    size(1600,900,P3D);
  
   cp5 = new ControlP5(this);
  
   cp5.addSlider("z_limit")
    .setPosition(50,50)
    .setRange(0,12)
    .setSize(240,20)
    .setValue(12)
    .setColorActive(color(150)) 
    .setColorBackground(color(50)) 
    .setColorForeground(color(100)) 
    .setNumberOfTickMarks(13)
  ;
  
  cp5.addSlider("thickness")
    .setPosition(50,90)
    .setRange(0,1)
    .setSize(240,20)
    .setValue(0)
    .setColorActive(color(150)) 
    .setColorBackground(color(50)) 
    .setColorForeground(color(100)) 
  ;
  
  cp5.addToggle("showcolor")
    .setPosition(50,120)
    .setSize(20,20)
    .setValue(false)
    .setColorBackground(color(50))
    .setColorForeground(color(100))
    .setColorActive(color(150))
  ;
  
  cp5.addToggle("generate")
    .setPosition(120,120)
    .setSize(20,20)
    .setValue(false)
    .setColorBackground(color(50))
    .setColorForeground(color(100))
    .setColorActive(color(150))
  ;
  
  cp5.addToggle("showgrid")
    .setPosition(190,120)
    .setSize(20,20)
    .setValue(true)
    .setColorBackground(color(50))
    .setColorForeground(color(100))
    .setColorActive(color(150))
  ;
  
  for (int i = 0; i <grid; i++) {
    for (int j = 0; j < grid; j++) {
      cc[i][j] = color(0, 0, 0);
      grow[i][j] = 0;
    }
  }
  for (int i = 0; i <60; i++) {
    czone[i] = color(random(255), random(255), random(255));
  }
}


public void draw(){
  background(0);

  pushMatrix();
  cam();


  /////draw intiial grid//////////////
  rectMode(CENTER);
  noFill();
  for (int i = 0; i <grid; i++) {
    for (int j = 0; j < grid; j++) {
      stroke(50);
      strokeWeight(1);
      if(showcolor == true){
        fill(cc[i][j]);
        rect(i*space-(space*grid/2),j*space-(space*grid/2),space,space);
      }
      if(showgrid == true){
        noFill();
        rect(i*space-(space*grid/2),j*space-(space*grid/2),space,space);
      }
    }
  }
  
  ////draw selected points//////////////
  for (int i = 0; i <grid; i++) {
    for (int j = 0; j < grid; j++) {
      if(grow[i][j] == 1){
        noStroke();
        fill(255);
        ellipse(i*space-(space*grid/2),j*space-(space*grid/2),space/2,space/2);
      }
    }
  }
  
  ////highlight grid position when moused over////////
  int xx = mouseX - (width - gsize)/2;
  int yy = mouseY - (height - gsize)/2;
  if(mouseX > width/2 - gsize/2 && mouseX < width/2 + gsize/2 && mouseY > height/2 - gsize/2 && mouseY < width/2 + gsize/2){
    stroke(50);
    strokeWeight(1);
    fill(0, 255, 255);
    rect((xx/space)*space-gsize/2,(yy/space)*space-gsize/2,space,space);
  }
  
  /////find closest slected point ot each grid position/////
  strokeWeight(1);
  for (int i = 0; i <grid; i++) {
      for (int j = 0; j < grid; j++) {
        int num = 0;
        float dd = 6000;
        for (int p = 0; p < zones.size(); p++) {
          PVector  v1 = new PVector(i, j, 0);
          PVector  v2 = (PVector) zones.get(p);
          float d = PVector.dist(v1, v2);
          if(d < dd){
            dd = d;
            num = p;
          }
        }
        cc[i][j] = czone[num];
      }
  }
  /////create branching structure//////
  if(generate == true){
    for (int p = 0; p < zones.size(); p++) {
      ArrayList newb = new ArrayList();
      ArrayList bb = new ArrayList();
      IntList ch = new IntList();
      stroke(czone[p]);
      for (int xc = 0; xc < grid; xc++) {
        for (int yc = 0; yc < grid; yc++) {
          if(cc[xc][yc] == czone[p]){
            PVector  v1 = new PVector(xc, yc, 0);
            newb.add(v1);
            v1 = new PVector(xc, yc, -20);
            bb.add(v1);
            ch.append(1);
          }
        }
      }
      int depthc = 0;
      for (int zc = 0; zc < z_limit; zc++) {
        for (int pp = 0; pp < newb.size(); pp++) {
          PVector cl = new PVector(0, 0, 0);
          float dm = 999;
          PVector  newv = (PVector) newb.get(pp);
          int checker = 0;
          for (int pp2 = 0; pp2 < bb.size(); pp2++) {
            PVector  bbv = (PVector) bb.get(pp2);
            float dd = PVector.dist(newv, bbv);
            if ( dd < dm){
              dm = dd;
              cl = bbv;
              checker = pp2;
            }
          }
          stroke(czone[p]);
          ch.set(checker,1);
          strokeWeight((zc*thickness)+1);
          line(newv.x*space-(space*grid/2),newv.y*space-(space*grid/2),newv.z,cl.x*space-(space*grid/2),cl.y*space-(space*grid/2),cl.z);
        }
        for (int pp2 = 0; pp2 < bb.size(); pp2++) {
            if(ch.get(pp2) < 1 && zc > 0){
              ch.remove(pp2);
              bb.remove(pp2);
            }
        }
        newb.clear();
        ArrayList reduce = new ArrayList();
        for(int i = 0; i < bb.size(); i++){
          PVector  mv = (PVector) bb.get(i);
          reduce.add(mv);
          newb.add(mv);
        }
        bb.clear();
        ch.clear();
        for(int ii = 0; ii < reduce.size(); ii++){
          PVector  v1 = (PVector) reduce.get(ii);
          if(v1.x != 999){
            int closest = 0;
            float d = 999;
            //println(v1);
            for(int jj = 0; jj < reduce.size(); jj++){
              if(ii != jj){
                PVector  v2 = (PVector) reduce.get(jj);
                float dd = PVector.dist(v1, v2);
                if(dd < d){
                  d = dd;
                  closest = jj;
                }
              }
            }
            PVector  v3 = (PVector) reduce.get(closest);
            float midx = (v1.x+v3.x)/2;
            float midy = (v1.y+v3.y)/2;
            PVector  add1 = new PVector(midx, midy, (depthc+1)*-20-20);
            if(midx < grid){
              bb.add(add1);
              ch.append(0);
            }
            PVector  vfar = new PVector(999, 999, 999);
            reduce.set(closest,vfar);
            reduce.set(ii,vfar);
          }
        }
        depthc = depthc +2;
      }
    }
  }

  popMatrix();
  
}

public void cam() {
  int newx = mouseX;
  int newy = mouseY;
  
  translate(width/2, height/2,zcam);
  rotateY(rotx);
  rotateX(roty);
  if ((mousePressed == true) && (mouseY > 200)) {
    rotx = rotx + (oldx-newx)/50.0f;
    roty = roty + (oldy-newy)/50.0f;
  }
  oldx = newx;
  oldy = newy;
  translate(0,0,100);
}

public void mouseClicked() {
  if(mouseX > width/2 - gsize/2 && mouseX < width/2 + gsize/2 && mouseY > height/2 - gsize/2 && mouseY < width/2 + gsize/2){
    int xx = mouseX - (width - gsize)/2;
    int yy = mouseY - (height - gsize)/2;
    xx = PApplet.parseInt(xx/space);
    yy = PApplet.parseInt(yy/space);
    grow[xx][yy] = 1;
    zones.add(new PVector(xx,yy,0));
  } 
}


public void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  zcam = zcam - e*5;
}