As I said in the summary of Spiral moving fish, when I published Moving fish, someone said: “Could you coil up the fish to allow more segments?” After attempting for a period, I created the work.

Coiling up the fish or the heart chain is similar.

From both works, you might have found. We need a spiral whose any ray from the origin intersects successive turnings of the spiral in points with a constant separation distance. The Archimedean spiral is what we want.

# Archimedean spiral

As it said in Archimedean spiral, it can be described by the equation `r = a + bθ`

and the constant separation distance is equal to `2πb`

if we measure `θ`

in radians. The `a`

and `b`

are real numbers. Changing the parameter `a`

will turn the spiral, while `b`

controls the distance between successive turnings. We don't have to turn the spiral so here let `a = 0`

for simplification.

Once you know the spiral equation is `r = bθ`

, it's not hard to draw an Archimedean spiral. Just one thing. The trigonometric functions of OpenSCAD accept degrees, not radians, so you have to map radians to degrees by yourself before invoking these functions.

```
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);
}
PI = 3.14159;
step = 0.25;
circles = 5;
arm_len = 10;
b = arm_len / 2 / PI;
// one radian is almost 57.2958 degrees
points = [for(theta = [0:step:2 * PI * circles])
[b * theta * cos(theta * 57.2958), b * theta * sin(theta * 57.2958)]
];
polyline(points, 1);
```

It seems no problem.

But if you increase the `step`

value to 1 or more, what will happen?

What? The distance between two successive points increases when you increase the radian. Why does it matter? If you place hearts on these points, the distance between hearts will also increase. For simplification, we just use circles here.

```
PI = 3.14159;
step = 0.2;
circles = 5;
arm_len = 10;
b = arm_len / 2 / PI;
for(theta = [0:step:2 * PI * circles]) {
rotate(theta * 57.2958)
translate([b * theta, 0, 0])
circle(1, $fn = 24);
}
```

The result is not what we want. We not only need a constant separation distance between two turnings but also require a steady distance between two points whose angle are equal. Only when theses conditions are satisfied, things like a heart chain can fit together.

Simply put, you cannot only increase the radian with a constant increment. A bigger `θ`

causes a longer `r`

, and a longer `r`

creates a greater arc in the condition of a constant increment of the radian. That's why the distance between points are longer and longer.

# Finding the increment

To have a constant distance between points, we have to find every increment on the radian. Let's list the relationships we've known currently.

Suppose the current angle is `Ai`

radians, the length of a ray is `Ri = b * Ai`

. If it needs to increase `ad`

radians to make the distance `L`

between points, the length of the ray is `R = b * (Ai + ad)`

. According to law of cosines, we have `L * L = R * R + Ri * Ri - 2 * R * Ri * cos(ad * 180 / π)`

.

Theoretically, using `b * (Ai + ad)`

to substitute `R`

can finally get what the `ad`

is.

But, it's not easy to evaluate the `ad`

value. What can we do now?

I use approximation here. Because the `L`

value is small when coiling up the fish or the heart, the `ad`

value is also small. That is, we can approximate `R`

to `Ri`

, so we can simplify the equation to `L * L = Ri * Ri + Ri * Ri - 2 * Ri * Ri * cos(ad * 180 / π)`

.

Then, we can get `ad = acos((2 * Ri * Ri - L * L) / (2 * Ri * Ri)) / 180 * π`

.

Of course, the `ad`

is not a constant value. In order to have a constant `L`

value, the `ad`

value decreases every time. Once we know the current `ad`

, we can get the next `Ri = b * (Ai + ad)`

. After knowing the next `Ri`

, we can get the next `ad = acos((2 * Ri * Ri - L * L) / (2 * Ri * Ri)) / 180 * π`

.

When modeling, we have to evaluate all radians. It is done recursively by the `find_radians`

function.

```
PI = 3.14159;
dots = 100; // number of dots
dot_dist = 5; // distance between points
arm_len = 5; // ray length
init_radian = PI * 4; // initial angle
b = arm_len / 2 / PI;
function r(b, theta) = b * theta;
function radian_step(b, theta, l) =
acos((2 * pow(r(b, theta), 2) - pow(l, 2)) / (2 * pow(r(b, theta), 2))) / 180 * PI;
function find_radians(b, l, radians, n, count = 1) =
count == n ? radians : (
find_radians(
b,
l,
concat(
radians, // current angle
[radians[count - 1] + radian_step(b, radians[count - 1], l)] // angle after rotating
),
n,
count + 1)
);
for(theta = find_radians(b, dot_dist, [init_radian], dots)) {
rotate(theta * 57.2958)
translate([b * theta, 0, 0])
circle(1, $fn = 24);
}
```

Because we use the approximation, the initial value should be small to avoid a significant error. This approach is enough for coiling up the fish or the heart chain.

Let's see how the model is.

Now, you can use your models to replace those circles. After adjusting some parameters, you can coil up anything you want. How about a snake?