In 2D turtle graphics, a turtle crawls and leaves footprints on a beach. If the turtle dives into the ocean, it swims and leaves a trace, what will happen?
A simple thought?
We've realized 2D turtle graphics. When it comes to 3D turtle graphics, you might have a simple thought that we can just raise and record the angle of the turtle's head.
This benefit of this solution is, we can only add calculation about φ into our code of 2D turtle graphics. It looks easy, but we'll have only two degrees of freedom, turning around the z-axis and raising the head. The other problem is if you want to draw a triangle on a plane which has 30 degrees from the xy plane, how to command the turtle?
You might command the turtle to forward, turn 120 degrees, raise 30 degrees, forward and so on. Unfortunately, this is wrong. In fact, the turtle has to turn 125.264 at the first time. (You can prove it by yourself.) That is, this solution is workable but troublesome.
Thinking in vectors
Suppose we have a turtle which head is toward the positive direction of the x-axis. If you sit on the turtle, you have your coordinate system. From your viewpoint, the coordinates of something are given by x' and y'.
Suppose we use a vector (1, 0) to represent the unit vector in the positive direction of the x'-axis. Similarly, we use a vector (0, 1) to represent the unit vector in the positive direction of the y'-axis. Now the turtle turns θ degrees.
From the viewpoint of the xy-coordinate system, we can obtain the new unit vectors of (1, 0) and (1, 0) by rotating them around the z-axis respectively. Rotation of axes lists the equations defining the transformation in two dimensions.
A simple case is, suppose θ is 45 degrees, from the viewpoint of the xy-coordinate system, the unit vector of the x'-axis is (1 / sqrt(2), 1 / sqrt(2))
, and the unit vector of the y'-axis is (- 1 / sqrt(2), 1 / sqrt(2))
. If we forward the turtle a length of L
, the path vector is (L * 1 / sqrt(2), L * 1 / sqrt(2))
. That is, the unit vector of the x'-axis is multiplied by L
to obtain the orange vector in the following picture.
Therefore, when forwarding from a point (x1, y2), the new point is (x1 + L * 1 / sqrt(2), y1 + L * 1 / sqrt(2))
.
In 2D turtle graphics, we only forward the turtle in the x-axis and don't care about forwarding in the y-axis. The calculation is simplified, so we don't talk about vectors at that time.
Realizing 3D turtle graphics
We have a xyz-coordinate system. You sit on a turtle and have a x'y'z'-coordinate system from your viewpoint. The vector (1, 0, 0), (0, 1, 0), (0, 0, 1) represents the unit vectors of x', y' and z' from the viewpoint of the xyz-coordinate system.
Similarly, we can use the equations of Rotation of axes to obtain new unit vectors after rotating. If you forward the turtle a length of L
, you can multiply the unit vector of x' by L
to obtain the path vector. Combine the path vector with the start point of the turtle; you can get the new point after moving.
From the above, we implement the following code. To draw lines in 3D coordinates, we use the polyline3D
module developed in 3D line.
function x(pt) = pt[0];
function y(pt) = pt[1];
function z(pt) = pt[2];
function pt3D(x, y, z) = [x, y, z];
function turtle3D(pt, coordVt) = [pt, coordVt];
function getPt(turtle) = turtle[0];
function getCoordVt(turtle) = turtle[1];
function plus(pt, n) = pt3D(x(pt) + n, y(pt) + n, z(pt) + n);
function minus(pt, n) = pt3D(x(pt) - n, y(pt) - n, z(pt) + n);
function mlt(pt, n) = pt3D(x(pt) * n, y(pt) * n, z(pt) * n);
function div(pt, n) = pt3D(x(pt) / n, y(pt) / n, z(pt) / n);
function neg(pt, n) = mlt(pt, -1);
function ptPlus(pt1, pt2) = pt3D(x(pt1) + x(pt2), y(pt1) + y(pt2), z(pt1) + z(pt2));
// forward the turtle in the x' direction
function moveX(turtle, leng) = turtle3D(
ptPlus(getPt(turtle), mlt(getCoordVt(turtle)[0], leng)),
getCoordVt(turtle)
);
// forward the turtle in the y' direction
function moveY(turtle, leng) = turtle3D(
ptPlus(getPt(turtle), mlt(getCoordVt(turtle)[1], leng)),
getCoordVt(turtle)
);
// forward the turtle in the z' direction
function moveZ(turtle, leng) = turtle3D(
ptPlus(getPt(turtle), mlt(getCoordVt(turtle)[2], leng)),
getCoordVt(turtle)
);
// turn the turtle around the x'-axis
// return a new unit vector
function turnX(turtle, a) = turtle3D(
getPt(turtle),
[
getCoordVt(turtle)[0],
ptPlus(mlt(getCoordVt(turtle)[1], cos(a)), mlt(getCoordVt(turtle)[2], sin(a))),
ptPlus(mlt(neg(getCoordVt(turtle)[1]), sin(a)), mlt(getCoordVt(turtle)[2], cos(a)))
]
);
// turn the turtle around the y'-axis
// return a new unit vector
function turnY(turtle, a) = turtle3D(
getPt(turtle),
[
ptPlus(mlt(getCoordVt(turtle)[0], cos(a)), mlt(neg(getCoordVt(turtle)[2]), sin(a))),
getCoordVt(turtle)[1],
ptPlus(mlt(getCoordVt(turtle)[0], sin(a)), mlt(getCoordVt(turtle)[2],cos(a)))
]
);
// turn the turtle around the z'-axis
// return a new unit vector
function turnZ(turtle, a) = turtle3D(
getPt(turtle),
[
ptPlus(mlt(getCoordVt(turtle)[0], cos(a)), mlt(getCoordVt(turtle)[1],sin(a))),
ptPlus(mlt(neg(getCoordVt(turtle)[0]), sin(a)), mlt(getCoordVt(turtle)[1], cos(a))),
getCoordVt(turtle)[2],
]
);
// 3D lines
module line3D(p1, p2, thickness, fn = 24) {
$fn = fn;
hull() {
translate(p1) sphere(thickness / 2);
translate(p2) sphere(thickness / 2);
}
}
module polyline3D(points, thickness, fn) {
module polyline3D_inner(points, index) {
if(index < len(points)) {
line3D(points[index - 1], points[index], thickness, fn);
polyline3D_inner(points, index + 1);
}
}
polyline3D_inner(points, 1);
}
t = turtle3D(
pt3D(0, 0, 0),
[pt3D(1, 0, 0), pt3D(0, 1, 0), pt3D(0, 0, 1)] // unit vectors from the turtle's viewpoint
);
leng = 10;
t2 = moveX(t, leng);
polyline3D([getPt(t), getPt(t2)], 1 , 4);
t3 = moveX(turnZ(t2, 120), leng);
polyline3D([getPt(t2), getPt(t3)], 1 , 4);
t4 = moveX(turnZ(t3, 120), leng);
polyline3D([getPt(t3), getPt(t4)], 1 , 4);
The above code is the realization of 3D turtle graphics. It can, of course, draw 2D lines on the xy plane. The above code only draws a triangle on the xy plane.
If you want to do the task mentioned in the above: draw a triangle on a plane which has 30 degrees from the xy plane, how to command the turtle now? You just have to turn the turtle 30 degrees around the x'-axis. After that, the turtle has 30 degrees from the xy plain. Now, from the turtle's viewpoint, turn 120 degrees around the z'-axis and draw one side. Repeat the actions two times. You'll do it. Sound like drawing a triangle on the 2D plain? You are right.
// omitted...the same as the previous code
leng = 10;
// Just change the line below. Turn 30 degrees around the x'-axis first.
t2 = moveX(turnX(t, 30), leng);
polyline3D([getPt(t), getPt(t2)], 1 , 4);
t3 = moveX(turnZ(t2, 120), leng);
polyline3D([getPt(t2), getPt(t3)], 1 , 4);
t4 = moveX(turnZ(t3, 120), leng);
polyline3D([getPt(t3), getPt(t4)], 1 , 4);
Surely, it needs more mathematics to realizing 3D turtle graphics. However, it's worth the effort. You can draw something like Customizable tree curve. Can you figure out how to draw it? I'll talk about it later; however, you can give it a try first.