Turtle spiral


I talked about spirals in previous documents, such as Golden spiral and Archimedean spiral. I used equations to figure out points on the spirals' path and then connect.

You can use turtle graphics to create spirals, too. Imagine you are forwarding and turning a turtle with different lengths and angles. The turtle will leave footprints on the path it crawled. Turtle Spiral Generator is such a customizer.

Turtle Spiral Generator

Forwarding and turning

Using a turtle to create a spiral is easy. Just forward it leng distance and turn angle continuously. If you remain leng unchanged and let angle = 90, the turtle will draw a square. If angle is 120, it will draw a triangle. What will it draw if angle is 144?

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);
}

// The above is the implementation of turtle graphics.

side_leng = 10;
angle = 144;
width = 1;

t1 = turtle(0, 0, 0);

t2 = forward(t1, side_leng);
polyline([get_xy(t1), get_xy(t2)], width);

t3 = forward(turn(t2, angle), side_leng);
polyline([get_xy(t2), get_xy(t3)], width);

t4 = forward(turn(t3, angle), side_leng);
polyline([get_xy(t3), get_xy(t4)], width);

t5 = forward(turn(t4, angle), side_leng);
polyline([get_xy(t4), get_xy(t5)], width);

t6 = forward(turn(t5, angle), side_leng);
polyline([get_xy(t5), get_xy(t6)], width);

The result is a star (pentagram). Its internal angle is 36 degrees. If you take 36 from 180, you get 144.

Turtle spiral

Recursion

Of course, writing the above code looks stupid. You cannot, however, use a loop here because OpenSCAD is Functional programming. If you need repetition, recursion is the solution.

// omitted..require turtle graphics implementation

module turtle_spiral(t_before, times, side_leng, angle, width) {
    if(times != 0) {
        t_after = forward(turn(t_before, angle), side_leng);
        polyline([get_xy(t_before), get_xy(t_after)], width);
        turtle_spiral(t_after, times - 1, side_leng, angle, width);
    }

}

side_leng = 10;
angle = 144;
width = 1;
times = 5;

turtle_spiral(turtle(0, 0, 0), times, side_leng, angle, width);

When seeing for the first time, you might think recursion troublesome or hard. In fact, nothing. If you start to feel annoying or hard, it's probably you are tracing too many variables. That might be a signal that you have too many tasks on each recursion. That's a common mistake when writing code in imperative languages.

It's easier to do recursion in Functional programming languages because variables and vectors are immutable. It forces you to do only one thing in the function so that you can observe repetitive tasks easily. Take the above pentagram for example. You can easily find the repetitive work in the code.

t_after = forward(turn(t_before, angle), side_leng);
polyline([get_xy(t_before), get_xy(t_after)], width);

You just have to do this task in the module. What's the next task? Don't care. Just invoke the same module again.

Of course, we still need a condition to stop the recursion. In the previous star code, we repeat similar tasks five times. The number of repetition is the stop condition, so the turtle_spiral module needs the times parameter. Every time the module complete its task, it subtracts one from times and invokes the same module again. That's why we execute turtle_spiral(t_after, times - 1, side_leng, angle, width) in the module.

Archimedean spiral

Besides forwarding a fixed length and turning a fixed angle, we can add some changes. For example, we can decrease step at next forwarding and stop the turtle when the length is smaller than a value.

// omitted..require turtle graphics implementation

module turtle_spiral(t_before, side_leng, d_step, min_leng, angle, width) {
    if(side_leng > min_leng) {
        t_after = forward(turn(t_before, angle), side_leng);
        polyline([get_xy(t_before), get_xy(t_after)], width);
        turtle_spiral(t_after, side_leng - d_step, d_step, min_leng, angle, width);
    }

}

side_leng = 50;
d_step = 1;
min_leng = 1;
angle = 90;
width = 1;

turtle_spiral(
    turtle(0, 0, 0), 
    side_leng, 
    d_step, 
    min_leng, 
    angle, 
    width
);

What will happen?

Turtle spiral

It looks like Archimedean spiral, right? In fact, using the following parameters will create an Archimedean spiral.

side_leng = 50;
d_step = 0.1;
min_leng = 1;
angle = 15;
width = 1;

Turtle spiral

As the first example shown in Archimedean spiral, the case here turns a fixed angle every time. However, why does the ray have the same length between two turnings? That's because we decrease a fixed length every time. It's easier to the relationship from the previous square model. It's shown below. You can see that the ray length between two turnings is always 2d.

Turtle spiral

So you can try to prove that the length between two turnings is always the same if the angle value is not less than 90.

You may also try to use different parameters to draw models shown in the featured picture of Turtle Spiral Generator.

Golden spiral

By the way, you can also use turtle graphics to draw a golden spiral. It's more troublesome to deal with the forwarded length and the turned angle. It's an exercise for you. I only provide my solution here.

// omitted..require turtle graphics implementation

side_leng = 30;
min_leng = 0.2;
angle = 15;
width = 1;

module turtle_spiral_by_times(t_before, times, side_leng, angle, width) {
    if(times != 0) {
        t_after = forward(turn(t_before, angle), side_leng * angle / 180 * 3.14159);
        polyline([get_xy(t_before), get_xy(t_after)], width);

        turtle_spiral_by_times(t_after, times - 1, side_leng, angle, width);
    } else {
        turtle_spiral(t_before, side_leng / 1.618, min_leng, angle, width);
    }

}

module turtle_spiral(t_before, side_leng, min_leng, angle, width) {
    if(side_leng > min_leng) {
        times = 90 / angle;
        turtle_spiral_by_times(t_before, 90 / angle, side_leng, angle, width);
    }
}

turtle_spiral(
    turtle(0, 0, 0), 
    side_leng, 
    min_leng, 
    angle, 
    width
);

It draws a golden spiral indeed.

Turtle spiral