# 2D turtle graphics

Turtle Spiral Generator is a thing based on turtle graphics.

Imagine that a turtle is crawling and leaving footprints on a beach. It's a real turtle graphics. When leveraging turtle graphics, we just give commands such as `forward`, `turn` and so on. The turtle hide all the details about coordinates.

The concept behind Turtle graphics is not hard. Different programming languages have different implementations of turtle graphics because of their different paradigm. OpenSCAD has a Functional programming paradigm. Its variables and vectors are immutable. If you are not familiar with Functional programming, you might encounter some difficulties when realizing turtle graphics.

# Positions and angles

When commanding a turtle to forward or turn, we have to trace the current coordinate and angle at a low level. I use the vector `[[x, y], angle]` to represent these data. For convenience, I define a `turtle` function which returns `[[x, y], angle]` if you pass the values of parameters `x`, `y` and `angle`.

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

function get_x(turtle) = turtle[0][0];  // return x
function get_y(turtle) = turtle[0][1];  // return y
function get_xy(turtle) = turtle[0];    // return [x, y]
function get_angle(turtle) = turtle[1]; // return angle
``````

Once you have a turtle data, you might want to change its coordinate but leave its angle unchanged. Because a vector is immutable in OpenSCAD, you cannot write code as below.

``````t = turtle(0, 0, 0);
// change to [10, 10]
t[0][0] = 10;
t[0][1] = 10;
``````

What can we do? You can create a new vector including the new coordinate and original angle.

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

Then, you can get a new turtle like this.

``````t = turtle(0, 0, 0);
// get a new turtle
new_t = set_point(t, [10, 10]);
``````

The original data refered by `t` is unchanged. You have to command the new turtle referred by `new_t`. For convenience, you may also define `set_x`, `set_y` and `set_angle` functions.

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

# Forwarding a turtle

If you forward a turtle, it will draw a line on the path. In imperative languages, you can easily implement such a `forward(leng)` module which draws a line after forwarding. But, you'll soon realize that you cannot do it in OpenSCAD.

In OpenSCAD, drawing is an action with side effects. You should use `module` to define these actions, but a module cannot return a value. If you want to return a value, you should use `function`.

So you cannot define a `forward(leng)` module which forwards a turtle, draws a line and then return a new turtle data. How to solve this problem? Think about it. In imperative languages, how do you draw a line after forwarding a turtle? You will reserve the original turtle data, forward the turtle and use both turtle data to draw a line.

We can use the `polyline` module developed in Line to draw a line, so we only have to retrieve a new turtle after forwarding.

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

Now, drawing a line after forwarding a turtle requires two steps.

``````leng = 10;
width = 1;

t = turtle(0, 0, 0);

new_t = forword(t, leng);
polyline([get_xy(t), get_xy(new_t)], width);
``````

If you want to move the turtle to a new coordinate and draw a line, how to do it? Because the `set_point` function returns a new turtle, the `polyline` module can easily do this job.

``````width = 1;

t = turtle(0, 0, 0);

// move to [10, 10]
new_t = set_point(t, [10, 10]);
polyline([get_xy(t), get_xy(new_t)], width);
``````

# Turning a turtle

From the above, it should be easy for you to define a `turn` function.

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

Then, how about using our turtle to draw a triangle?

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

side_leng = 10;
angle = 120;
width = 1;

t = turtle(0, 0, 0);

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
``````

The triangle drawn by the turtle is as below.

It's different from the imperative paradigm, right? You might be not used to Functional programming in the beginning. Once you are used to the paradigm, however, you'll draw much inspiration from it.