Diamond-Shaped Hexagonal Waves

edited June 2014 in Share Your Work

Hi All,

I tried to model Reuben Margolin's 'Nebula'.
reubenmargolin.com/waves/index.html

Just copy, paste, and run from a sketch. You can modify certain characteristics of the wave like amplitude, frequency, etc... just see the code comments.

It's interactive if you have Peasycam library. Just make the following changes:

* Comment out   camera(2300,4200,2200,0,2600,0,0,0,-1);
** Uncomment   
** cam = new PeasyCam(this,100);   
** import peasy.*;
** PeasyCam cam;

Enjoy, Minh

//import peasy.*;
//PeasyCam cam;

Node[][] tree1;
GraphOp op1;
float theta = 0;      // Variable for controlling wave

int diamondSize = 16;    // Diamond size (How many hexes in diameter?)   RECOMMENDED RANGE: 7-20
float hexSize = 100;     // Length of sides of hexagon                   RECOMMENDED RANGE: Not recommended if not using Peasycam
float waveAmp = 300;     // Wave peak amplitude (pixels)                 RECOMMENDED RANGE: Peak Amplitude size-3000
float delTheta = .01;    // Rate of change of variable (radians); controls frequency  RECOMMENDED RANGE: .00-1
float waveLength = 3;    // How many waves displayed by mesh             RECOMMENDED RANGE: 1-10

void setup(){
  size(600,600,OPENGL);
  lights(); 
//  cam = new PeasyCam(this,100);  

  NodeManager manager1=new NodeManager(diamondSize,hexSize);  // Number of levels/Hex length
  manager1.seed(0,0,0);
  manager1.createTree();
  tree1=manager1.getCoords(); 

  op1 = new GraphOp(tree1,diamondSize,hexSize);
  op1.setAmplitude(waveAmp);
  op1.setWavelength(waveLength); 
  op1.initialize();
}

void draw(){
  background(0);
  drawAxes();
  camera(2300,4200,2200,0,2600,0,0,0,-1);

  op1.calcZ(theta);  // Calculate z coords
  op1.setZCoords(tree1,op1.getZ(),0);  // Set tree's z choords
  op1.drawTree();  // Draw tree 

  theta+=delTheta;
}


//======================================================
// Axes
//======================================================
void drawAxes(){
 textSize(15);
 strokeWeight(2);

 stroke(250,0,0);  // Red z-axis
 line(0,0,0,0,0,80);
 pushMatrix();
 text("Z",0,0,80);
 popMatrix();

 stroke(0,250,0);  // Green x-axis
 line(0,0,0,80,0,0);
 pushMatrix();
 text("X",80,0,0);
 popMatrix();

 stroke(0,0,250);  // Blue y-axis
 line(0,0,0,0,80,0);
 pushMatrix();
 text("Y",0,80,0);
 popMatrix();
 noStroke();
}

/** This class provides methods for drawing a matrix of nodes and creating wave. The nodes are assumed to be connected.
*/ 

class GraphOp{
  Node[][] twoD;  // Graph we're working with
  float[] z;
  float[] theta;
  int n;
  float rH; 
  float wvLength=1;

  float amplitude=300;  // Amplitude of wave, pixels 
  boolean debugPrint = false; 

  GraphOp(Node[][] newGraph, int nSize,float rHex){
     twoD=newGraph; 
     n=nSize;
     rH=rHex;
     z=new float[4*n];  // We're addressing all levels; 
     theta=new float[4*n];
  }

  // Methods to create wave 
  public void initialize(){  // Map each level to a point of a circle
     int j=0;
     theta[0]=0;
     for(int i=1;i<6+4*(n-2);i+=2){
       theta[i]=((.5*rH)+(1.5*rH*j)); // .. .. .. .. .. ..
       theta[i+1]=((j+1)*(1.5*rH)); 
       j++;
     }   
     theta[4*n-1]=(theta[4*n-2]+(.5*rH));  // Anomally(corner case) requiring (2PI/((2*rH)+(n-1)*(3*rH))) to be done separately
     for(int i=1;i<theta.length;i++){
       theta[i]=theta[i]*(2*PI/(((2*rH)+(n-1)*(3*rH)))*wvLength); // .. .. .. .. .. ..
     }
     if(debugPrint){
        for(int k=0;k<theta.length;k++){
           println("["+k+"]"+degrees(theta[k])+" ");
        } 
     }
  }

  public void calcZ(float phi){  // Computes z-coords of each level -> z[]
    for(int i=0;i<z.length;i++){
      z[i]=amplitude*sin(phi+theta[i]);   // ADD PHI AFTER INITIALIZATION 
    }
  }

  public void setZCoords(Node[][] ar, float[] cords, float zOffset){  // zOffsets enables multiple waves to be animated
     for(int i=0;i<4*n;i++){                                          // Otherwise multiple nodes would be mapped to same z-height
       for(int j=0;j<n+1;j++){
        if(ar[i][j]!=null){
           ar[i][j].setZ(cords[i]+zOffset); 
        }
       } 
     }
  }

  public float[] getZ(){  // Outputs z-values for given phi
     return z; 
  }

  public void setAmplitude(float amp){
    amplitude=amp; 
  }  

  public void setWavelength(float wv){
    wvLength=wv;  
  }

  // Methods to draw graph
  public void drawTree(){  
    for(int i=0;i<4*n;i++){  // Row:Col
      for(int j=0;j<n+1;j++){
        if((twoD[i][j]!=null)&&(twoD[i][j].Left!=null)&&(twoD[i][j].Right!=null)){
          //twoD[i][j].drawN();
          twoD[i][j].drawTo("LR");
        }
      }    
    }
  }

} // Ends GraphOP

class Node{

float nodeSize = 10;  // Size of Node
float edgeWeight = 2; // Size of wire
color nodeColor = color(180); 
color edgeColor =  color(250); 

int level;       // Indexes nBox[]
float x,y,z; 
Node Left;       // If not branch, then L=R
Node Right; 
boolean isbNode; // Is node a branch?
boolean isLeaf;  // Is node a leaf?


Node(float xInit, float yInit, float zInit, boolean isB, boolean isL){
  x=xInit; 
  y=yInit;
  z=zInit;
  isbNode = isB;
  isLeaf = isL; 
  level = 0;
} 

void setX(float xNum){x=xNum;}
void setY(float yNum){y=yNum;}
void setZ(float zNum){z=zNum;}
void setL(Node lNode){Left=lNode;}
void setR(Node rNode){Right=rNode;}
void setIsBNode(boolean bNode){isbNode = bNode;}
void setIsLeaf(boolean leaf){isLeaf = leaf;}
void setNLvl(int lvl){level=lvl;}

void drawN(){ // Render node
  fill(nodeColor);
  stroke(edgeColor);
  translate(x,y,z);
  box(nodeSize);
  translate(-x,-y,-z); // Restore Coords
}

void drawTo(String cmd){ // Render line to L/R node pos
  strokeWeight(edgeWeight); 
  stroke(edgeColor); 
  if(cmd=="L"){
    line(this.x,this.y,this.z,Left.x,Left.y,Left.z); 
  }
  else if(cmd=="R"){
    line(this.x,this.y,this.z,Right.x,Right.y,Right.z); 
  }
  else if(cmd=="LR"){
    line(this.x,this.y,this.z,Left.x,Left.y,Left.z); 
    line(this.x,this.y,this.z,Right.x,Right.y,Right.z); 
  }
}

} // Ends Class

/**  This class creates nodes of one tier of a diamond shape matrix and stores each node in a 2D array. 

    1. Call seed() to place the first vertex of hex matrix. Tree will be drawn in the y-direction 
       in default coordinate axis.
    2. Call createTree() to populate Node[][] with coordinates of hex matrix. This will display the graph
       unless debugDraw is set false.
    3. Call getCoords() to get coordinates of each node 
       call getRoot() to get root node
*/

class NodeManager{

  float rH;  // Hex side length
  int n;    // Number of levels
  Node rt;  // Root node
  Node[] bNodes;  // Working array of branch nodes
  Node[] nbNodes; // Working array of non-branch nodes
  Node[][] nBox;  // Holds all nodes in the array, for easy access, animating wave
  int c1=1; // Number of active bN in bNodes[], accounting for root
  int c2=2; // Number of active nbN in nbNodes[], account for root's first 2 children
  int nBuildLevel=1; // For indicating each newly created node's level

  boolean debugDrawN = false; // Draw nodes?
  boolean debugDrawTo = false; // Draw edges?
  boolean debugStatus = false; // Prints debug statements to terminal?

  NodeManager(int level, float hexLength){
    n=level;
    rH=hexLength; 
    bNodes = new Node[n];
    nbNodes = new Node[n+1];
    nBox = new Node[4*n][n+1];  // Should be as big as the diamond is big
  }

  void seed(float x, float y, float z){  // Creates first branching node
    rt = new Node(x,y,z,true,false);
    rt.setNLvl(nBuildLevel);   // Set lvl=1
    toBox(rt);       // Put in main array
    bNodes[0]=rt;    // Put in working array
    if(debugDrawN)rt.drawN();      // Display node
  }

  void createTree(){
     createTopMatrix();  // Populate main array
     createBotMatrix(); 
       if(debugDrawN){
        for(int j=0;j<4*n;j++){  // DEBUG: Draw every node from nBox[][]
          for(int i=0;i<n+1;i++){
             if(nBox[j][i]!=null){
               nBox[j][i].drawN();
             }
          }
        }
       }
  }

  void createTopMatrix(){      // Create first half 
    while(c1<=n){
      if(debugStatus){
        println("CYCLE "+c1+ "  c1="+c1+",c2="+c2+",Lvl="+nBuildLevel);
        print("Processing bN[]: ");
        dumpArray(bNodes);                              // Process branching Nodes in working array: Make next set of nodes
      }
        processbN();   
      if(debugStatus){
        print("Done proccessing bN. Updated nBN[]: ");
        dumpArray(nbNodes);
      }
      if(c1!=n){
        if(debugStatus){
          print("Processing nbN[]: ");
          dumpArray(nbNodes);
        }
        processnbN();                                // Process non-branching Nodes in working array: Make next set of nodes
        if(debugStatus){
          print("Done proccessing nbN. Updated bN[]: ");
          dumpArray(bNodes);
        }
      }
    c1++;            // Increment counter: c1 indicates how many non-branching Nodes to build
    c2++;            // Increment counter: c2 indicates how many branching Nodes ot build
    nBuildLevel+=2;  // Increment level counter
    if(debugStatus)dumpBox();          // DEBUG
    if(debugStatus)dumpArray(bNodes);  // DEBUG Since for c1=n, this doesn't get executed but we want to see it
    if(debugStatus)dumpBox();          // DEBUG
    }
  }

 void createBotMatrix(){    // Invert the y's and add offset from root pos. 
    int row=(nBuildLevel-2);       // Count down from mid row index, before nBuildLevel gets modified
    for(int i=row;i>=0; i--){
      genRow(i, nBuildLevel);       // Generate nodes based on nodes in row, put into nBox 
      nBuildLevel++;                  // Increment level counter
    }
    if(debugStatus)dumpBox();
    connectRows();   // Connect bottom rows
 }



//============================================================================================= 
// Methods to Create TOP Half 
//============================================================================================= 
  void processbN(){  // Process each branching node of level <c1>: Create nbN's
    gennbN(c1, nBuildLevel);                                           // Generate the number of nbN for this level
    setnbNCoords(bNodes[0].x, bNodes[0].y, bNodes[0].z);  // Set each coordinate of nbN based on position of left-most parent bN of level c1
    for(int i=0;i<c1;i++){  // Set each bN's left/R
      bNodes[i].setL(nbNodes[i]); 
      bNodes[i].setR(nbNodes[i+1]);
    }
    if(debugDrawTo){
      for(int j=0;j<c1;j++){          //DEBUG
        bNodes[j].drawTo("LR");  
      }
    }
  }

  void processnbN(){  // Process each nbN of level <c2>: Create bN's
  genbN(c2,nBuildLevel);                                            // Generate new bNodes based on previous level
  setbNCoords(nbNodes[0].x,nbNodes[0].y,nbNodes[0].z);  // Set newly created bNode's coords
  for(int j=0;j<c2;j++){                                // Set each 00's L/R to newly created bN
    nbNodes[j].setL(bNodes[j]);
    nbNodes[j].setR(bNodes[j]);
  }
  if(debugDrawTo){
    for(int a=0;a<c2;a++){            //DEBUG
      nbNodes[a].drawTo("LR");  
    }
  }
}
//=============================================================================================  
  void gennbN(int quantity,int nBLvl){           // Make non-branching nodes based on c1
    for(int i=0;i<quantity+1;i++){
      Node newNode = new Node(0,0,0,false,false); // Create & initialize nodes
      newNode.setNLvl(nBLvl+1);                   // Set node's level
      toBox(newNode);                             // Put in main array
      nbNodes[i]=newNode;                         // Add node to working array
    }
  }

  void genbN(int quantity, int nBLvl){            // Make branching nodes based on c2
    for(int i=0;i<quantity;i++){
        Node newbNode = new Node(0,0,0,true,false);
        newbNode.setNLvl(nBLvl+2);                   // Set node's level
        toBox(newbNode);                             // Put in main array
        bNodes[i]=newbNode;                          // Add node to working array
    }
  }

  void setnbNCoords(float x, float y, float z){  // Set coordinates of nodes in work array; Used with gennbN()
    for(int i=0;i<c1+1;i++){
      nbNodes[i].setX(x-rH*sin(radians(60))*(1-2*i)); // Given by geometry 
      nbNodes[i].setY(y+rH*cos(radians(60)));         // No need to set Z for now
      nbNodes[i].setZ(z);
    }
    if(debugDrawN){  
      for(int j=0;j<c1+1;j++){      //DEBUG
        nbNodes[j].drawN(); 
      }
    }
  }

void setbNCoords(float x, float y, float z){
  for(int i=0;i<c2;i++){
     bNodes[i].setX(x+2*i*rH*sin(radians(60)));
     bNodes[i].setY(y+rH);   
     bNodes[i].setZ(z);
  }
  if(debugDrawN){
    for(int i=0;i<c2;i++){           //DEBUG
       bNodes[i].drawN(); 
    }
  }
}
//============================================================================================= 
// Methods to Create BOT Half 
//============================================================================================= 
void genRow(int row, int nLvl){      // Generate nodes based on nodes in row, put into nBox 
  float toAOS = nBox[2*n-1][0].y+(rH/2);    // Distance from root to axis of symmetry ** Could be an issue if not nicely placed **, r/2+distance from rt to y-coord of nodes in last lv
  for(int i=0;i<n+1;i++){     // Check all array slots for node(cols)
    if(nBox[row][i]!=null){   // If row contains node
      float frAOStoN = dist(rt.x,toAOS,rt.z,rt.x,nBox[row][i].y,rt.z);   // Distance from AOS to node indexed by row
      Node newNode = new Node(nBox[row][i].x,toAOS+frAOStoN,nBox[row][i].z,nBox[row][i].isbNode,nBox[row][i].isLeaf);  // Make new node
      newNode.setNLvl(nLvl);  // Set node's level
      toBox(newNode);         // Store newly created node in main array
    }
  }
}

void connectRows(){
  Node[] prev = new Node[n+1];
  Node[] next = new Node[n+1]; 
  int cP=n+1;   // Node counter for prev

  // CONNECTING LEVEL ABOVE AOS TO LEVEL BELOW AOS
  prev = nbNodes; // Initalize prev array; prev is immediate level of nodes above AOS
  for(int i=0;i<n+1;i++) next[i]=nBox[2*n][i];  // Initialize next
  for(int i=0;i<n+1;i++){
     prev[i].setL(next[i]);
     prev[i].setR(next[i]);
     if(debugDrawTo)prev[i].drawTo("LR");  // DEBUG draw
  }
  for(int i=0;i<n+1;i++) prev[i] = next[i];  // Prev gets immediate level below AoS    
  // CONNECTING LEVELS BELOW AOS
    for(int i=(2*n+1);i<4*n;i++){  // i indexes row in nBox
      int cN=0;   // Node counter for next
      for(int j=0;j<n+1;j++){ // Populate next
        next[j]=nBox[i][j];
        if(nBox[i][j]!=null){ // Add to node counter if node actually exits
        cN++;
        }
      }
      if(cP==cN){  // Num nodes in prev = next
        for(int z=0;z<cP;z++){
           prev[z].setL(next[z]);
           prev[z].setR(next[z]);
           if(debugDrawTo)prev[z].drawTo("LR"); 
        }
      }
      if(cP>cN){    // Draw connections between uneven-levels
         prev[0].setL(next[0]);
         prev[cP-1].setR(next[cN-1]);
         for(int m=0;m<cP-1;m++){ 
           prev[m].setR(next[m]);
           //prev[m].drawTo("R");    // DEBUG  Note: Can't do drawTo("LR") here because L hasn't been connected yet...
         }
         for(int n=cP-1;n>0;n--){ 
           prev[n].setL(next[n-1]);
           //prev[n].drawTo("R");    // DEBUG
         }
         if(debugDrawTo){              // DEBUG draw
           for(int h=0;h<cP;h++){
              prev[h].drawTo("LR"); 
           }
         }
      }
      for(int y=0;y<n+1;y++) prev[y] = next[y];  // Prev gets immediate level below AoS
      cP=cN;
    } 
}


//============================================================================================= 
// nBox[][] Insert/Retrieval Methods
//============================================================================================= 
void toBox(Node n){  // Level denotes a row the node should be stored
  int index = 0; 
  while(nBox[n.level-1][index]!=null){index++;}  // Find next index with no objects
  nBox[n.level-1][index]=n; 
}

//============================================================================================= 
// Debug Methods
//============================================================================================= 
void dumpBox(){    // Prints the coordinates of all nodes store in main array
  println("MAIN ARRAY CONTAINS:");
  for(int i=0;i<(4*n);i++){ 
  print("["+i+"] = ");
    for(int j=0;j<(n+1);j++){
      if(nBox[i][j]!=null){  // If contains node
      print((j+1)+"."+"("+nBox[i][j].x+","+nBox[i][j].y+","+nBox[i][j].z+","+nBox[i][j].level+") ");
      } 
    }
    println(); 
  }  
  println();
}

void dumpArray(Node[] tar){  // Prints out the coordinates of each Node in each the array
  for(int i=0;i<tar.length;i++){
    if(tar[i]!=null){
    print("("+tar[i].x+","+tar[i].y+","+tar[i].z+","+tar[i].level+") "); 
    }
  }
  println();
}

Node[][] getCoords(){return nBox;}
Node getRoot(){return rt;}
int getN(){return n;}

} // Ends NodeManager class

Comments

  • edited June 2014

    I formatted the lines about PeasCam (good idea to make it optional), and applied the trick given by GoToLoop: block comments invade all the code below, unless we use doc comments (which are appropriate there, anyway) /**

Sign In or Register to comment.