javascriptrecursiongraphicsp5.jsfractals

Fractal Tree in P5.js


I've been trying to create a simple fractal tree program using P5.js, for displaying on a website, but I seem to be getting unexpected behavior. Code and picture attached.

function setup() {
  createCanvas(1920, 1080);
}

function draw() {
  background(10);
  x = 1920/2
  y = 1080/2
  fractalDraw(x, y, 5, 0)
}

function fractalDraw(nodeX, nodeY, numNodes, sideFlag){
  offset = 10 * numNodes
  leftNodeX = nodeX - offset
  rightNodeX = nodeX + offset
  topNodeY = nodeY - offset
  botNodeY = nodeY + offset
  
  if(sideFlag === -1){                                 //Leftside draw
    line(nodeX, nodeY, leftNodeX, topNodeY)
    stroke(255,255,255)
    line(nodeX, nodeY, leftNodeX, botNodeY)
    stroke(255,255,255);
  }
  else if(sideFlag === 1){                            //Rightside draw
    line(nodeX, nodeY, rightNodeX, topNodeY)
    stroke(255,255,255);
    line(nodeX, nodeY, rightNodeX, botNodeY)
    stroke(255,255,255);
  }
  else{                                              //Starting draw
    line(nodeX, nodeY, leftNodeX, topNodeY)
    stroke(255,255,255)
    line(nodeX, nodeY, leftNodeX, botNodeY)
    stroke(255,255,255);
    line(nodeX, nodeY, rightNodeX, topNodeY)
    stroke(255,255,255);
    line(nodeX, nodeY, rightNodeX, botNodeY)
    stroke(255,255,255);
  }
  
  if(numNodes === 1){                              //Recursion Base Case
    return 1
  }
  else{                                            //Recursive calls
    fractalDraw(leftNodeX, topNodeY, numNodes-1, -1)
    fractalDraw(leftNodeX, botNodeY, numNodes-1, -1)
    fractalDraw(rightNodeX, topNodeY, numNodes-1, 1)
    fractalDraw(rightNodeX, botNodeY, numNodes-1, 1)
  }
}

Fractals not fractaling

I'm using recursive calls, and it seems only my first recursive call is running, then the entire draw loop restarts in a different starting position than intended once the first (and only the first) recursive call finishes. Also I had some weird behavior when using small number of branch layers (3 or less).


Solution

  • The main issue is that you don't define your variables with var, let or const, and so all your variables are implicitly declared as global variables, which is the cause of havoc. The values of leftNodeX and similar variables are modified by the recursive calls, and so when these recursive calls return, these variables no longer have their intended values, and the next recursive call will get arguments that no longer make sense.

    There are also a few other issues:

    Correction:

    function setup() {
      // Use browser information to set size (see also CSS settings for body)
      createCanvas(screen.width, screen.height);
      // Call stroke before drawing
      stroke(255,255,255);
    }
    
    function draw() {
      background(10);
      // Define variables with const 
      const x = width >> 1; // Use integer division
      const y = height >> 1;
      // Pass the initial offset instead of the number of nodes
      fractalDraw(x, y, width >> 2, 0);
      noLoop(); // Avoid repeating calls to draw()
    }
    
    function fractalDraw(nodeX, nodeY, offset, sideFlag){
      // Define variables with const (here was the most dramatic bug) 
      const leftNodeX = nodeX - offset;
      const rightNodeX = nodeX + offset;
      const topNodeY = nodeY - offset;
      const botNodeY = nodeY + offset;
      
      if(sideFlag === -1){
        line(nodeX, nodeY, leftNodeX, topNodeY);
        line(nodeX, nodeY, leftNodeX, botNodeY);
      }
      else if(sideFlag === 1){
        line(nodeX, nodeY, rightNodeX, topNodeY);
        line(nodeX, nodeY, rightNodeX, botNodeY);
      }
      else{
        line(nodeX, nodeY, leftNodeX, topNodeY);
        line(nodeX, nodeY, leftNodeX, botNodeY);
        line(nodeX, nodeY, rightNodeX, topNodeY);
        line(nodeX, nodeY, rightNodeX, botNodeY);
      }
      
      if(offset <= 1){
        return 1;
      }
      else{
        // The offset should decrease by a factor, not by a constant
        offset >>= 1; // For example: integer divide by 2
        fractalDraw(leftNodeX, topNodeY, offset, -1);
        fractalDraw(leftNodeX, botNodeY, offset, -1);
        fractalDraw(rightNodeX, topNodeY, offset, 1);
        fractalDraw(rightNodeX, botNodeY, offset, 1);
      }
    }
    html, body { margin: 0; height: 100%; overflow: hidden; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>

    There is also an issue with the "tree" shape your code draws. I assume that lines should all be connected, but in case the flag is -1 or 1 (so all cases except the initial call), two out of four of the recursive calls will pass coordinates that might not have been used by a previous line call, and so you sometimes get disconnected lines.

    I can only guess which shape you were going for, but here is an alternative you may want to consider:

    function setup() {
      createCanvas(screen.width, screen.height);
      stroke(255,255,255);
    }
    
    function draw() {
      background(10);
      const x = width >> 1
      const y = height >> 1;
      fractalDraw(x, y, x, y, width >> 2);
      noLoop();
    }
    
    function fractalDraw(fromX, fromY, nodeX, nodeY, offset){
      line(fromX, fromY, nodeX, nodeY);
      if (offset < 1) {
        return;
      }
      const leftNodeX = nodeX - offset;
      const rightNodeX = nodeX + offset;
      const topNodeY = nodeY - offset;
      const botNodeY = nodeY + offset;
    
      offset >>= 1;
      fractalDraw(nodeX, nodeY, leftNodeX, topNodeY, offset);
      fractalDraw(nodeX, nodeY, leftNodeX, botNodeY, offset);
      fractalDraw(nodeX, nodeY, rightNodeX, topNodeY, offset);
      fractalDraw(nodeX, nodeY, rightNodeX, botNodeY, offset);
    }
    html, body { margin: 0; height: 100%; overflow: hidden; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>