I have a thing Fern leaf stencil which has a customizable fern leaf.
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);
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?
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?
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:
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.
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:
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.
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.
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.
Then, how to draw the fern leaf shown in the Fern leaf stencil? It's an exercise for you.