Note: This tutorial still needs some more written explanations...

Triangle Subdivision

Another fun technique that uses recursion is very similar to our previous example, with the small difference that it uses a triangle instead of a rectangle as the base element to subdivide. I first learned about this method and its applications from Tyler Hobbs' blog. I highly recommend reading his article after you finish this tutorial, it shows a handful of thoughtful ways to take this technique even further.

So, we'll start with a triangle, and split it into two smaller triangles, by connecting one of the triangle's vertices to the mid-point of the opposite side.

Screen Shot 2021-12-04 at 5.48.35 PM.png

The triangleSubdivision function below takes a few arguments:

const triangleSubdivision = (Ax, Ay, Bx, By, Cx, Cy, depth, maxDepth, p) => {
  if (depth > maxDepth) {
    // Draw the triangle specified by our current three points (A, B and C)
    p.moveTo(Ax, Ay)
    p.lineTo(Bx, By)
    p.lineTo(Cx, Cy)
    p.lineTo(Ax, Ay)
  } else {
    // We draw a segment from point A to the mid-point of the BC segment. 
		// midX and midY are the coordinates of the BC segment mid-point, and are obtained
		// using the *lerp* function. *lerp* stands for *linear interpolation,* and is a function which takes as parameters
		// a range (two numbers, e.g. Bx and Cx below) and an *alpha* value between 0 and 1 (e.g. 0.5 below). 
		// If we think of drawing a line between the two numbers which define the range, alpha controls how far along that line
		// we want our return value to be.
		// In the example below, with alpha = 0.5, midX will be exactly halfway between Bx and Cx: the equivalent of (Bx + Cx) / 2
    let midX = math.lerp(Bx, Cx, 0.5)
    let midY = math.lerp(By, Cy, 0.5)
		
		// We call our function recursively, for the first triangle (points mid, A and B)
    triangleSubdivision(midX, midY, Ax, Ay, Bx, By, depth + 1, maxDepth, p)

		// We call our function recursively, for the second triangle (points mid, A and C)
    triangleSubdivision(midX, midY, Ax, Ay, Cx, Cy, depth + 1, maxDepth, p)
  }
}

And we need to call the triangleSubdivision function from our main sketch. In the example below, I am using the triangle above the second diagonal of the page as the starting point.

const paths = [];
const p = createPath()

// point A is (0, 0) -> top-left corner
// point B is (0, height) -> bottom-left corner
// point C is (width, 0) -> top-right corner
triangleSubdivision(0, 0, 0, height, width, 0,  0, 3, p)

paths.push(p)

2021.12.01-16.10.33-0-340975.png

As expected, the code above only fills half of the page with triangles, because we never told it to apply the subdivision to the triangle underneath the second diagonal. Let's do that as well:

// Above the diagonal
triangleSubdivision(0, 0, 0, height, width, 0,  0, 3, p)
// Under the diagonal
triangleSubdivision(width, height, 0, height, width, 0,  0, 3, p)

The image on the left shows the result of the code above, with both triangleSubdivision function calls having a maximum depth of 3. The image on the right uses a different maximum depth for the top triangle.