Archimedes and logarithmic spirals
Posted September 30th, 2019
In his book "Climbing Mount Improbable", Richard Dawkins describes a museum containing every animal that has ever existed and every animal that could ever conceivably exist.
Every animal is located next to an animal that most closely resembles it. Every dimension in the museum corresponds to a dimension of variation within the animals - e.g sharpness of teeth is West to East and North to South is horn length.
Since there are more than 3 ways that animals can differ, this museum would have to have multiple dimensions. One way to visualise 4 dimensions is to imagine multiple 3 dimensional cubes, with animals occupying the same position relative to the cube are identical in the first 3 qualities but differ in the fourth (e.g coat hairiness). More dimensions can be visualised by making "families" of such cubes.
To illustrate his analogy more clearly, Dawkins draws attention to a subset of animals that can mostly be expressed with 3 variables - shells.
Many kinds of shells in nature can be modelled as logarithmic spirals.
In this post I will demonstrate how to draw different kinds of spirals using code. In a future post I will demonstrate how to model shells using spirals.
Archimedes Spiral
This is one of the most simple spirals to draw.
Let's start by creating 'archimedes.html'.
<!DOCTYPE html>
<html>
<head>
<title>Archimedes Spiral</title>
</head>
<body>
<canvas width="720" height="480" id="canvas">
</canvas>
<script type="text/javascript">
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
</script>
<style type="text/css">
canvas {
border: thin solid black;
}
</style>
</body>
</html>
Then define a center to begin from. As Dawkins describes, shells start small and grow at the margins. To model this with a spiral, we will draw starting from a center, and "spiral" outwards by an increasing distance.
const centerX = 200;
const centerY = 200;
let distance = 1;
for (let angle = 0; angle < 5 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.fillRect(x, y, 1, 1);
distance += 0.1;
}
This code is easier to write with degrees, but Math.sin
and Math.cos
expect radians, so we need to convert the angles.
Math.toRadians = function (number) {
return Math.PI * number / 180;
}
This results in a somewhat blurry shell.
I fix this by drawing a line between each point.
ctx.beginPath();
ctx.moveTo(centerX, centerY);
for (let angle = 0; angle < 5 * 360; angle++) {
left x = centerX + Math.sin(Math.toRadians(angle)) * distance;
left y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.lineTo(x, y);
distance += 0.1;
}
ctx.stroke();
This results in a clearer spiral.
Logarithmic Spiral
Unlike an archimedes spiral, a logarithmic spiral does not grow by a constant amount every iteration.
Instead they "open out" at a constant rate. For example, the gap between turns might double at every coil. The expansion rate of the spiral is actually one of the variables/dimenions used to model the different kinds of shells - Dawkins calls this "flare".
Logarithmic spirals are also known as equiangular spirals.
Often different types of shell are better modelled with logarithmic spirals.
Let's begin by copying some of the setup code from the archimedes spiral, in equiangular.html
.
<!DOCTYPE html>
<html>
<head>
<title>Logarithmic Spiral</title>
</head>
<body>
<canvas width="720" height="480" id="canvas">
</canvas>
<script type="text/javascript">
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
Math.toRadians = function (number) {
return Math.PI * number / 180;
}
const centerX = 200;
const centerY = 200;
let distance = 1;
</script>
<style type="text/css">
canvas {
border: thin solid black;
}
</style>
</body>
</html>
Like before, we will draw a point for every degree.
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.fillRect(x, y, 1, 1);
// increase distance here
}
How much do we increase distance?
For every full turn, we want to double the distance.
We can use logarithms to determine how much to grow.
We need to multiply the distance by some number 360 times (a full turn) to cause it to double.
To put it mathematically, if y is the distance and x is the multiplier then:
We can cancel out the distance to obtain:
To solve this, we can take log base 2 of two.
Which, is identical to
We can rearrange this to get
If is , then .
Substituting that in our code, we get:
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.fillRect(x, y, 1, 1);
distance *= 2**(1/360);
}
Like before, let's reduce blurring by using lines instead.
const centerX = 200;
const centerY = 200;
let distance = 1;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.lineTo(x, y);
distance *= (2**(1/360));
}
ctx.stroke();
This doesn't just work for doubling, we can substitue any growth factor we like.
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.lineTo(x, y);
distance *= 3**(1/360);
}
Real shells have tubes, not lines. To mimic this, we will draw circles instead of lines.
const ctx = canvas.getContext("2d");
ctx.drawCircle = function(x, y, radius) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2, false);
ctx.stroke();
}
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.drawCircle(x, y, 15);
distance *= 2**(1/360);
}
The tubes usually grow wider, we can model this by making it proportional to the distance from the centre.
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.drawCircle(x, y, distance/6);
distance *= 2**(1/360);
}
Some Cool Patterns
In a future tutorial I will demonstrate how to go from here to creating all sorts of different shell shapes, but here I'd like to demonstrate different kinds of patterns that can be obtained by playing around with a few variables.
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.drawCircle(x, y, distance%15);
distance *= 2**(1/360);
}
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.drawCircle(x, y, Math.abs(distance%15));
distance *= -(2**(1/360));
}
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.drawCircle(x, y, Math.abs(distance/6));
distance *= -(2**(1/360));
}
let distance = 2;
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.drawCircle(x, y, Math.abs(distance/6));
distance *= (1.5**(1/360));
}
let distance = 2;
for (let angle = 0; angle < 12 * 360; angle++) {
let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
ctx.drawCircle(x, y, Math.abs(distance/6));
distance *= -(1.5**(1/360));
}