Sierpinski triangle (Fractal)


I have a thing Fern leaf stencil which has a customizable fern leaf.

Fern leaf stencil

The fern leaf is an example of fractals. It exhibits a repeating pattern displayed at every scale. The replication of a fern leaf at every scale is the same, so it is called a self-similar pattern.

You can do it too

If you ever observe the code about fractals, such as a Koch curve, a tree curve and so on, you might wonder how these fractals are figured out in the beginning.

Don't think too much. In fact, you can do it, too. A concentric circle is a fractal.

$fn = 48;
width = 1;

module frame(thickness) {
    difference() {
        children();
        offset(r = -thickness) children();
    }
}

frame(width) circle(40);
frame(width) circle(20);
frame(width) circle(10);
frame(width) circle(5);

Sierpinski triangle (Fractal)

Why is it a fractal? Why not? A big circle contains a small circle. If you look at the small circle, it contains a smaller circle. The replication is the same at every scale. It satisfies the fractal definition.

However, writing the above code is not efficient. We can use recursion.

radius = 40;
r_limit = 4;
width = 1;

module frame(thickness) {
    difference() {
        children();
        offset(r = -thickness) children();
    }
}

module concentric_circles(radius, r_limit, width) {
    $fn = 48;
    if(radius >= r_limit) {
        frame(width) circle(radius);
        concentric_circles(radius / 2, r_limit, width);
    }
}

concentric_circles(radius, r_limit, width);

The above code draws the same model as the previous one.

Using triangles

If we change the $fn value to 3, what will happen?

Sierpinski triangle (Fractal)

A big triangle contains a small triangle and the latter includes a smaller one, too. It is a fractal. If we change concentric_circles(radius / 2, r_limit, width) to rotate(60) concentric_circles(radius / 2, r_limit, width), that is rotating 60 degrees before drawing a smaller triangle, what will happen?

Sierpinski triangle (Fractal)

It is also a fractal.

Combining turtle graphics

Then, what I want to do is not only creating a concentric triangle but also including the pattern of the above picture in each smaller triangle.

I can certainly calculate the coordinate of the geometric center of each triangle and then draw the pattern again. But, it's troublesome.

Don't bother to calculate. You can use turtle graphics. The turtle hides all the details about coordinates. Still remember the triangle we draw in 2D turtle graphics? Let's define a triangle module for it.

// omitted...require turtle graphics

module triangle(t, side_leng, width) {    
    angle = 120; 
    t_p1 = forward(t, side_leng);                   // forward side_leng
    polyline([get_xy(t), get_xy(t_p1)], width);     // draw a line

    t_p2 = forward(turn(t_p1, angle), side_leng);   // turn angle and forward side_leng
    polyline([get_xy(t_p1), get_xy(t_p2)], width);  // draw a line

    t_p3 = forward(turn(t_p2, angle), side_leng);   // turn angle and forward side_leng
    polyline([get_xy(t_p2), get_xy(t_p3)], width);  // draw a line
}

Then, we use it to rewrite the concentric_circles. We rename it to concentric_triangles because we are drawing triangles now.

module concentric_triangles(t, side_len, len_limit, width) {
    if(side_len >= len_limit) {
        triangle(t, side_len, width);
        // forward a half of side_len and tunr 60 degrees
        next_t = turn(forward(t, side_len / 2), 60); 
        // draw concentric triangles 
        concentric_triangles(next_t, side_len / 2, len_limit, width);
    }
}

side_len = 50;
len_limit = 4;
width = 1;
t = turtle(0, 0, 0);

concentric_triangles(t, side_len, len_limit, width);

The drawn model is:

Sierpinski triangle (Fractal)

Evolving the pattern

Before evolving the pattern, ask ourselves a question: what is the replication at every scale? The answer is that a big triangle contains a small and reverse triangle, which can be obtained by shrinking and rotating the big triangle 60 degrees. We define a two_triangles module for the replication.

module triangle(t, side_leng, width) {    
    angle = 120; 
    t_p1 = forward(t, side_leng);                   // forward side_leng
    polyline([get_xy(t), get_xy(t_p1)], width);     // draw a line

    t_p2 = forward(turn(t_p1, angle), side_leng);   // turn angle and forward side_leng
    polyline([get_xy(t_p1), get_xy(t_p2)], width);  // draw a line

    t_p3 = forward(turn(t_p2, angle), side_leng);   // turn angle and forward side_leng
    polyline([get_xy(t_p2), get_xy(t_p3)], width);  // draw a line
}

module two_triangles(t, side_len, len_limit, width) {
    angle = 60;
    triangle(t, side_len, width);
    // forward a half of side_len and turn 60 degrees
    next_t = turn(forward(t, side_len / 2), angle); 
    // draw a small and reverse triangle
    triangle(next_t, side_len / 2, width);
}

module concentric_triangles(t, side_len, len_limit, width) {
    if(side_len >= len_limit) {
        two_triangles(t, side_len, len_limit, width);

        next_t = turn(forward(t, side_len / 2), 60); 
        concentric_triangles(next_t, side_len / 2, len_limit, width);
    }
}

side_len = 50;
len_limit = 4;
width = 1;
t = turtle(0, 0, 0);

concentric_triangles(t, side_len, len_limit, width);

We draw the same model. You might find that some lines draw repeatedly. We can avoid the little problem, however, here we just ignore it for simplification.

Now let's focus on the left triangle. We want to draw concentric triangles in it. In fact, we just have to use side_len / 2 to invoke concentric_triangles. Therefore, after invoking two_triangles, we invoke concentric_triangles immediately.

module concentric_triangles(t, side_len, len_limit, width) {
    if(side_len >= len_limit) {
        two_triangles(t, side_len, len_limit, width);

        // concentric triangles at the left triangle
        concentric_triangles(t, side_len / 2, len_limit, width);

        next_t = turn(forward(t, side_len / 2), 60); 
        concentric_triangles(next_t, side_len / 2, len_limit, width);
    }
}

We have the following model.

Sierpinski triangle (Fractal)

Then, let's deal with the concentric triangles at the right triangle. This can be down by forwarding the turtle side_len / 2 and using side_len / 2 to invoke concentric_triangles again.

module concentric_triangles(t, side_len, len_limit, width) {
    if(side_len >= len_limit) {
        two_triangles(t, side_len, len_limit, width);

        // concentric triangles at the left triangle
        concentric_triangles(t, side_len / 2, len_limit, width);

        // concentric triangles at the right triangle
        concentric_triangles(forward(t, side_len / 2), side_len / 2, len_limit, width);

        next_t = turn(forward(t, side_len / 2), 60); 
        concentric_triangles(next_t, side_len / 2, len_limit, width);
    }
}

The result is:

Sierpinski triangle (Fractal)

How about the top triangle? Turn the turtle 60 degrees, forward side_len / 2, and use side_len / 2 to invoke concentric_triangles again.

module concentric_triangles(t, side_len, len_limit, width) {
    if(side_len >= len_limit) {
        two_triangles(t, side_len, len_limit, width);

        // concentric triangles at the left triangle
        concentric_triangles(t, side_len / 2, len_limit, width);

        // concentric triangles at the right triangle
        concentric_triangles(forward(t, side_len / 2), side_len / 2, len_limit, width);

        // concentric triangles at the top triangle
        concentric_triangles(turn(forward(turn(t, 60), side_len / 2), -60), side_len / 2, len_limit, width);

        next_t = turn(forward(t, side_len / 2), 60); 
        concentric_triangles(next_t, side_len / 2, len_limit, width);
    }
}

We have a bunch of triangles.

Sierpinski triangle (Fractal)

WTF? Be passion! Because it's full of triangles, you cannot see the repetition. Let's comment the code of the concentric triangles at the center.

module concentric_triangles(t, side_len, len_limit, width) {
    if(side_len >= len_limit) {
        two_triangles(t, side_len, len_limit, width);

        // concentric triangles at the left triangle
        concentric_triangles(t, side_len / 2, len_limit, width);

        // concentric triangles at the right triangle
        concentric_triangles(forward(t, side_len / 2), side_len / 2, len_limit, width);

        // concentric triangles at the top triangle
        concentric_triangles(turn(forward(turn(t, 60), side_len / 2), -60), side_len / 2, len_limit, width);

        // the concentric triangles at the center.
        // next_t = turn(forward(t, side_len / 2), 60); 
        // concentric_triangles(next_t, side_len / 2, len_limit, width);
    }
}

It's clear now.

Sierpinski triangle (Fractal)

This triangle has a cool name, Sierpinski triangle. You may have different ways to create a Sierpinski triangle. Here is only one of them. From the above process, you should understand that a fractal is not created just in one step. You may start with a simple thought or a known pattern, whether it's simple or complex, evolve it step by step until you get what you want.

The complete code is listed below.

function turtle(x, y, angle) = [[x, y], angle];

function get_x(turtle) = turtle[0][0];
function get_y(turtle) = turtle[0][1];
function get_xy(turtle) = turtle[0];
function get_angle(turtle) = turtle[1];

function set_point(turtle, point) = [point, get_angle(turtle)];

function set_x(turtle, x) = [[x, get_y(turtle)], get_angle(turtle)];
function set_y(turtle, y) = [[get_x(turtle), y], get_angle(turtle)];
function set_angle(turtle, angle) = [get_xy(turtle), angle];

function forward(turtle, leng) = 
    turtle(
        get_x(turtle) + leng * cos(get_angle(turtle)), 
        get_y(turtle) + leng * sin(get_angle(turtle)), 
        get_angle(turtle)
    );

function turn(turtle, angle) = [get_xy(turtle), get_angle(turtle) + angle];

module line(point1, point2, width = 1, cap_round = true) {
    angle = 90 - atan((point2[1] - point1[1]) / (point2[0] - point1[0]));
    offset_x = 0.5 * width * cos(angle);
    offset_y = 0.5 * width * sin(angle);

    offset1 = [-offset_x, offset_y];
    offset2 = [offset_x, -offset_y];

    if(cap_round) {
        translate(point1) circle(d = width, $fn = 24);
        translate(point2) circle(d = width, $fn = 24);
    }

    polygon(points=[
        point1 + offset1, point2 + offset1,  
        point2 + offset2, point1 + offset2
    ]);
}

module polyline(points, width = 1) {
    module polyline_inner(points, index) {
        if(index < len(points)) {
            line(points[index - 1], points[index], width);
            polyline_inner(points, index + 1);
        }
    }

    polyline_inner(points, 1);
}

module triangle(t, side_leng, width) {    
    angle = 120; 
    t_p1 = forward(t, side_leng);                   // forward side_leng
    polyline([get_xy(t), get_xy(t_p1)], width);     // draw a line

    t_p2 = forward(turn(t_p1, angle), side_leng);   // turn angle and forward side_leng
    polyline([get_xy(t_p1), get_xy(t_p2)], width);  // draw a line

    t_p3 = forward(turn(t_p2, angle), side_leng);   // turn angle and forward side_leng
    polyline([get_xy(t_p2), get_xy(t_p3)], width);  // draw a line
}

module two_triangles(t, side_len, len_limit, width) {
    angle = 60;
    triangle(t, side_len, width);
    // forward a half of side_len and turn 60 degrees
    next_t = turn(forward(t, side_len / 2), angle); 
    // draw a small and reverse triangle
    triangle(next_t, side_len / 2, width);
}

module sierpinski_triangle(t, side_len, len_limit, width) {
    if(side_len >= len_limit) {
        two_triangles(t, side_len, len_limit, width);

        // left triangles
        sierpinski_triangle(t, side_len / 2, len_limit, width);

        // right triangles
        sierpinski_triangle(forward(t, side_len / 2), side_len / 2, len_limit, width);

        // top triangles
        sierpinski_triangle(turn(forward(turn(t, 60), side_len / 2), -60), 
            side_len / 2, len_limit, width);
    }
}

side_len = 150;
len_limit = 4;
width = 0.5;
t = turtle(0, 0, 0);

sierpinski_triangle(t, side_len, len_limit, width);

It creates a bigger Sierpinski triangle.

Sierpinski triangle (Fractal)

Then, how to draw the fern leaf shown in the Fern leaf stencil? It's an exercise for you.