Coding Curves 14: Miscellaneous Curves

coding curves

Chapter 14 of Coding Curves

This is the last planned chapter of this series. I might add another one here or there in the future if I find a new interesting curve to write about. There were also a couple of topics on my original list which I decided to hold back. I might change my mind about them someday. Any future additions will be added to the index.

For this “final” installment, I thought I’d cover a few random curves that probably wouldn’t be worth a full chapter in themselves. And I thought it would be good to kind of walk through the process I actually take when I go to code up some formula I discover.

The Cannabis Curve

Weisstein, Eric W. “Cannabis Curve.” From MathWorld–A Wolfram Web resource. https://mathworld.wolfram.com/CannabisCurve.html

Wolfram Mathworld is a great place to find interesting formulas by the way. If you want to dig up more 2d curves, the section on Plane Curves is a great place to dive in. But there’s all kinds of other stuff on the site too.

I’m not trying to make any statement by choosing the cannabis curve. I just thought it was pretty cool that you could draw something complex like that with relatively simple math.

So we get this as a formula:

OK, it’s a bit long, but it’s just multiplication, addition and some sines and cosines. We can do this.

This is defined as a polar curve, which means rather than defining x, y values, we’ll be dealing with an angle and a radius. We have a function, r(θ), where θ is the Greek letter, theta. This usually represents an angle. And we can guess that r stands for radius. So we have a function where we pass in an angle and get a radius.

With an angle and a radius, we can easily get an x, y point to draw a segment to. This might look sort of like this:

for (t = 0; t < 2 * PI; t += 0.01) {
  radius = r(t)
  x = cos(t) * radius
  y = sin(t) * radius
  lineTo(x, y)
}
stroke()

We use t to get the radius and then radius and t to get the next point to draw a line to.

Practically speaking though, I’ll never use that r(θ) function anywhere but in this for loop, so I’ll just hard code it all right there.

The only other thing we see here that isn’t a number or a bracket or a trig formula or θ, is the variable a. We’re multiplying the whole rest of the formula by a to get the final radius for a given t, so it seems like a will just represent the overall radius we want this curve to be drawn at. So a will probably be a good parameter to pass into our cannabis function, and I’ll probably rename that parameter radius for clarity’s sake. We’ll also probably want a center x, y point to locate the curve, so we’ll make those parameters too (xc and yc for x and y center).

We come up with something like this for starters

function cannabis(xc, yc, radius) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    r = radius * ... // that whole formula. we'll get to it.
    x = cos(t) * r
    y = sin(t) * r
    lineTo(xc + x, yc + y)
  }
  closePath()
}

Now we just need to code up all the stuff that comes after a. This is really pretty simple now that we’ve figured out how it’s all going to fit together. For the fractional constants, I’ll just use decimals: 0.1 instead of 1/10 and 0.9 instead of 9/10. Let’s go!

function cannabis(xc, yc, radius) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    r = radius * (1 + 0.9 * cos(8 * t)) * (1 + 0.1 * cos(24 * t)) * (0.9 + 0.1 * cos(200 * t)) * (1 + sin(t))
    x = cos(t) * r
    y = sin(t) * r
    lineTo(xc + x, yc + y)
  }
  closePath()
}

Now I’ll try to run this by putting up something like so:

canvas(600, 600)
cannabis(300, 300, 140)
stroke()

That gives me this image:

Ah, OK. This tells us a few things.

First, this formula is using Cartesian coordinates and I’m using upside-down screen coordinates. So I’ll have to flip the y-axis. No problem.

Next, the center is where all the “leaves” join. So after flipping, I can probably set the center more towards the bottom of the canvas.

Finally, I guessed that 140 would be a good value for radius, as it would keep it well within the 600×600 size of the canvas. In fact, I expected it would only be about half the size of the canvas. But we actually go well beyond the canvas edge for the larger leaves. We could correct that in the code, maybe multiplying radius by some fraction to bring the largest leaf down to the radius the user passed in. I’m going to skip that part and just pass in a smaller value, but feel free to do what you want with the function.

Here’s my final version:

function cannabis(xc, yc, radius) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    r = radius * (1 + 0.9 * cos(8 * t)) * (1 + 0.1 * cos(24 * t)) * (0.9 + 0.1 * cos(200 * t)) * (1 + sin(t))
    x = cos(t) * r
    y = sin(t) * r
    lineTo(xc + x, yc - y)
  }
  closePath()
}

All I did was change the lineTo function to use yc - y instead of yc + y.

Then when I call it, I just change the parameters a bit (with a bit of trial and error to get it just right):

canvas(600, 600)
cannabis(300, 520, 120)
stroke()

There we go!

Side note. I seriously considered changing the size of the canvas so that the yc parameter could be 420. You’ll either get that or you won’t. 🙂

Of course, now I’m curious about what that formula is doing. There’s basically four parts to it after the a, each in parentheses – three with cosines, one with sine. The first one has a hard coded 8 in there.

... (1 + 0.9 * cos(8 * t)) ...

Since there are seven visible leaves there, I’m guessing those are related – there’s probably actually eight leaves, but the bottom one is too small to see. I’ll change that 8 to a 12…

Yup, theory validated. Eleven leaves plus an invisible one.

In the second section, the 24 is a bit less obvious.

... (1 + 0.1 * cos(24 * t)) ...

If I revert back to original and then change the 24 to 0, I get very rounded leaves.

Doubling 24 to 48 gives us:

It’s kind of making three levels for each leaf. Let’s put it back to 24 and change the multiplier:

... (0.7 + 0.3 * cos(24 * t)) ...

Again, we see three levels, which make sense, as 24 = 8 * 3. So this section is using a very small multiplier, 0.1, to make a subtle change to each leaf – making it just a bit less round. Cool. We’ll revert that and look at the next one.

... (0.9 + 0.1 * cos(200 * t)) ...

The 200 there makes me guess it’s creating all the jagged edges. If I change it to 100, it’s less jagged.

But now I’m seeing some blockiness. I can try increasing the resolution by changing the for loop increment from 0.01 to 0.005:

Mmmm… smooth.

I’ll revert that and look at the final sine block.

... (1 + sin(t))

My guess was that this was affecting the orientation of the curve. I thought that if I removed that section, the leaf would be sideways. But I was wrong. Here’s what happens if I remove that section and set yc back to 300, the center of the canvas:

That was a pleasant surprise! And it gives me a lot of ideas of new curves I can create from this. Also, this makes that missing eighth leaf visible!

A Heart Curve

Weisstein, Eric W. “Heart Curve.” From MathWorld–A Wolfram Web Resource. https://mathworld.wolfram.com/HeartCurve.html

Again, Wolfram Mathworld to the rescue. As you can see, there is no single formula to draw a heart shaped curve. This page shows eight different ones. Personally I like the last one in the second row.

Rather than a polar formula like last time (and some of these other examples), this just has us computing the x and y directly. But we’re still going to loop a t value from 0 to 2 * PI.

For the y part of the formula, there are four different calculations. It’s not entirely clear how these calculations are supposed to be combined, but if you look further down in the text, you find that you’re supposed to subtract them. I’m also a bit concerned that we have so many hard coded numbers in there and no way to change the final size of the heart. But I’m sure we can figure that out.

Once again, this is really a pretty straightforward formula, so let’s jump in and code it up.

function heart(xc, yc) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    x = 16 * pow(sin(t), 3)
    y = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)
    lineTo(xc + x, yc + y)
  }
  closePath()
}

We can run this like so:

canvas(600, 600)
heart(300, 300)
stroke()

And we’ll get:

Generally right, but we need to flip it like before, and we need to allow for changing the size. Right now it’s about 32 pixels wide. That’s the hard coded 16, times 2.

For flipping we can again just say yc - y.

For size, let’s first get rid of all of those hard-coded numbers by dividing them all by 16.

x = pow(sin(t), 3)
y = 0.8125 * cos(t) - 0.3125 * cos(2 * t) - 0.125 * cos(3 * t) - 0.0625 * cos(4 * t)

If we left it like that, we’d get a heart that would be two pixels wide (1 * 2). Now we can add a size parameter and multiply both values by that.

function heart(xc, yc, size) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    x = size * pow(sin(t), 3)
    y = size * (0.8125 * cos(t) - 0.3125 * cos(2 * t) - 0.125 * cos(3 * t) - 0.0625 * cos(4 * t))
    lineTo(xc + x, yc - y)
  }
  closePath()
}

Now we call it like so:

canvas(600, 600)
heart(300, 300, 280)
stroke()

And get:

Not too difficult.

I’m not going to dive in to all the different ways you can mess with this, but just try changing the various constants in the formula and see what happens. Can you make it better? Come up with something completely different?

An Egg

I first looked into drawing egg shapes a couple of years ago. This post showed the result, but none of the thought process behind it: https://www.bit-101.com/blog/2021/06/how-to-draw-an-egg/

The page I got my formula from was here: http://www.mathematische-basteleien.de/eggcurves.htm

There are actually a truckload of different egg formulas on that page. Like the heart curve, I was surprised that there was no single standard egg curve formula.

But I homed in on the one on that page under the section “From the Oval to the Egg Shape”. This takes a general formula for an oval or ellipse and alters the “y radius” based on the current value at each point. If x is to the right of center, it will make the y value a little bit bigger. If x is to the left, it will make the y value a bit smaller. That seemed sensible.

So we’ll start with an ellipse formula. We covered that back in Chapter 3.

function ellipse(x, y, rx, ry) {
  res = 4.0 / max(rx, ry)
  for (t = 0; t < 2 * PI; t += res) {
    lineTo(x + cos(t) * rx, y + sin(t) * ry)
  }
  closePath()
}

That’s nice and concise, but I’m gonna break it up a bit so we can mess with the raw x and y coords right out of the trig functions before translating and scaling them. I’m also going to ignore the res variable and just hard code the 0.01 in there. Just for simplicity and clarity. Keep it if you want.

function egg(xc, yc, rx, ry) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    x = cos(t)
    y = sin(t)
    lineTo(xc + x * rx, yc + y * ry)
  }
  closePath()
}

This will just draw an ellipse, but let’s make sure it works with the changes.

canvas(600, 600)
egg(300, 300, 280, 190)
stroke()

Yup, that’s an ellipse. Where did I get the 280 and 190 from? Well, 280 is a bit less than half of the width of the canvas, so that’s the rx. And I wanted ry to be somewhat less than that. It was just trial and error and 190 looked about right.

Now let’s make this ellipse into an egg. The article gives three formulas:

t1(x) = 1 + 0.2 * x

t2(x) = 1 / (1 - 0.2 * x)

t3(x) = e^(0.2 * x)

Those t functions just give us something we need to multiply the y value by. I’m not going to make new functions though. I’m just going to do the multiplication right in the for loop. Let’s try the first one…

function egg(xc, yc, rx, ry) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    x = cos(t)
    y = sin(t)
    y *= (1 + 0.2 * x)
    lineTo(xc + x * rx, yc + y * ry)
  }
  closePath()
}

Woo! An egg!

Now we can mess with that a bit. The 0.2 value there is probably going to be the best source of experimentation. Let’s try 0.3.

OK, it got a bit pointy. How about 0.5?

Even pointier. So we know where that’s going. Let’s go down. To 0.1.

That’s barely distinguishable from the original ellipse. Which makes sense, because if that value was 0, than that line would do nothing and we’d be back to an ellipse. Let’s go back to 0.2, which probably did look the most egg-like, and try changing the yr value we’re passing in. We’ve been using 190. Here’s 220:

A nice fat egg. And 150:

I’m gonna stand by my choice of 190, but some minor tweaks might work better. Go for it. Let’s try the other formulas. We’ll go back to the ry of 190 first. Then change that line 5 to:

y *= 1 / (1 - 0.2*x)

This gives us:

And then the third formula:

y *= exp(0.2 * x)

Remember from Chapter 5 that most math libraries have an exp function that is e raised to a given power. That’s what we are doing here. The result of that:

These all look suspiciously the same, so I drew them all at once, red, green and blue…

Yeah, those are all virtually the same. Maybe a pixel off here and there. Looking back at the original web site, they are talking about a different ellipse formula and multiplying y2 by that value:

The equation of the ellipse e.g. x²/9+y²/4=1 change to x²/9+y²/4*t(x)=1.

This is also hard coding 9 and 4 as divisors. If you unsquare those you get 3 and 2. And it just so happens that 2/3 of 280 is 186ish. So we pretty much agreed on my choice of 190 for ry!

At any rate, we have a formula that draws a fairly convincing egg no matter which algorithm we use. So I’m going to leave it there. Here I was literally writing the article as I was writing the code, so you really got to see my process, along with my glossing over of details in the source article. But it all came out just fine!

Summary

Hopefully this gives you some insight on how to find formulas in various places and convert them into code that draws something interesting – if you haven’t already done stuff like this.

And that wraps up this series on coding curves. At least for now. I have another series in mind though, so watch this space!

Coding Curves 13: Superellipses and Superformulas

coding curves

Chapter 13 of Coding Curves

In this chapter we’ll be talking about some interesting shapes. Superellipses are quite useful in design and UI work, especially a specific superellipse we’ve come to know affectionately as the “squircle”. These are basically rounded rectangles, but with some neat properties. Superformulas are an extension of superellipses. They are more complicated, probably less useful, but interesting in their own right. Let’s dive in.

Superellipses

Sometimes you need to draw a rectangle for something. We’ve all been there. Drawing a rectangle is easy enough in almost any graphics package. So you draw a rectangle.

But that’s kind of boring. Those square corners are just so… square. So you come up with a rounded rectangle. Some drawing apis have this built in, but it’s not too hard to hand code. You draw a 90-degree arc at each corner and a line between each arc. This is ok.

In fact, you can make those corners have a relatively small radius, or you can make it quite round:

But you might not love this. There’s kind of a break in continuity in the edges. It’s going along nice and straight and then it abruptly turns into an arc. Maybe you want something that smoothly transitions into that round corner. Enter the superellipse.

You can think of a superellipse as a blend between an ellipse and a rectangle. Like an ellipse, and many of the other curves we’ve drawn in this series, we’ll consider this shape as a center point and a varying radius drawn from a t of 0 to 2 * PI. This is different from a lot of rectangle functions which take an x, y location (usually top, left of the shape) and a width and height. But you can adjust the function as you want.

While there are different ways to express a superellipse mathematically, we’ll dig around until we find one that is parameterized with that 0 to 2 PI t value. And here is is:

x = pow(abs(cos(t)), 2 / n) * a * sign(cos(t))
y = pow(abs(sin(t)), 2/ n) * b * sign(sin(t))

Hmm… more complicated than you might have expected?

There is a somewhat simpler formula that looks like this:

x = pow(cos(t), 2 / n) * a
y = pow(sin(t), 2 / n) * b

But the problem with this is that it only handles one quadrant: 0 to PI / 2. Rather than running a for loop four times, once for each corner, and figuring out the signs of x and y, we do some fancy math with abs and sign.

Your language probably has an abs function, but it just returns the absolute value of a value. You could easily write your own like:

function abs(val) {
  if (val < 0) {
    return -val
  }
  return val
}

This will always return zero or a positive value.

You might not have a sign function though. This just returns -1 if the given value is negative, and +1 if it’s positive. Returns 0 for 0. You can write this like so:

function sign(val) {
  if (val < 0) {
    return -1
  }
  if (val > 0) {
    return 1
  }
  return 0
}

You might be tempted to get fancy and do something like:

function sign(val) {
  return val / abs(val)
}

This works wonderfully. Until val is 0. Then it crashes. So you’ll still need a conditional in there to catch that case.

Now in the formula I gave above, there’s some duplication and some not very clear variables, and there’s no way to position the shape. So here’s an actual function that’s more usable:

function superellipse(xc, yc, rx, ry, n) {
  for (t = 0; t < PI * 2; t += 0.01) {
    c = cos(t)
    s = sin(t)
    x = pow(abs(c), 2 / n) * rx * sign(c)
    y = pow(abs(s), 2 / n) * ry * sign(s)
    lineTo(xc + x, yc + y)
  }
  closePath()
}

Most path drawing apis have some sort of closePath function that draws a final line back to the starting point.

And there you go. Let’s recreate our above rounded rectangle using a superellipse.

width = 600
height = 400
canvas(width, height)

// set color to orange however you do that...
superellipse(300, 200, 250, 150, 10)
fill()

You might or might not like that better than the simple rounded rectangle, but let’s explore it some more. That last parameter, n, controls how curved the corners are. The higher the value, the closer you get to a rectangle. Here is an n of 20:

And here is an n of 4:

This looks like the screen shape of an old fashioned TV set.

And this is a good time to bring up squircles.

Squircle

A superellipse where the x and y radii equal is sometimes also known as a supercircle. And has also been called a squircle. A combination of a square and circle. Some definitions of squircle state that it must have an n of 4. It looks like this:

This shape looks very satisfying, and has become very popular recently in user interface design. It’s often used for icons, especially for those used to launch apps on mobile devices. Sometimes the n value might be not quite 4 as above, but close enough.

There is actually an alternate formula for a squircle which John Cook wrote about at https://www.johndcook.com/blog/2022/10/27/variant-squircle/. His site is a great resource by the way. I can’t say I always understand all of what he is talking about, but he’s sent me down many very interesting paths of exploration.

Back to Superellipses

Let’s explore that n parameter a bit more. We already saw that as we make it larger, the corners get tighter. When we get an n of 2, something interesting happens. We just get an ellipse (or a circle if the height and width are equal).

When we go from 2 down to 1, we see the straight edges turn to corners, and the corners become more straight. Here’s 1.5:

When we get down to n = 1, corners and edges have completely reversed and we get a diamond shape.

Then as we go below 1, the corners start curving inwards. Here’s 0.75:

At 0.2, the superellipse is almost disappearing.

It will be essentially invisible with an n just a bit lower than that.

And when you go negative, things get really weird. Here’s n at -4. I had to reduce the size a bit so you could see what’s going on – we get a filled rect and some inverted superellipses extending out of each corner.

Not sure there’s any use for that last part, but there it is.

And that’s about it for superellipses. A great shape to have in your graphics toolbox. But let’s move on.

Superformulas

A superformula is a generalization or extension of s superellipse. Again, we have a radius that differs as we go from 0 to 2 * PI. The formula for that radius is:

That’s straight from the Wikipedia article on superformulas. It was easier to copy and paste that than it would be to try and type out the formula in a way that made sense. We’ll convert that to pseudocode shortly, but you should see some similarities between this and the superellipse formula. You see that we take the cosine and sine of something, divide it by a value, then take a power of the absolute value of that. The squiggly symbol there is the Greek letter phi, and it’s what we will call t. So we’re saying the radius at angle t is …

Then we have some other variables, m, n1, n2 and n3 as well as a and b which influence the x and y radii. In my implementation I’m going simplify it by just having a single radius. So I’ll set a and b to 1, which means we can just ignore them in the code and multiply the result by the radius we want.

Since this is so complicated, I’ll break down some of these parts into separate variables and then combine them for the final result.

First we’re taking the cosine and sine of the same value, so we can precalculate that. And I’ll call m symmetry.

angle = symmetry * t / 4

Then in between the big parentheses we have two terms we are adding together. We’ll calculate each of those:

term1 = pow(abs(cos(angle), n2)
term2 = pow(abs(sin(angle), n3)

Again, we’re considering a and b to be 1, so we can ignore them here.

Next we can plug these terms into the rest of the formula to get the final radius. Remember, we multiply that by the overall radius we want:

r = pow(term1 + term2, -1/n1) * radius

And finally, use the radius and angle to get the next point to draw to:

x = xc + cos(t) * r
y = yc + sin(t) * r

Put all together, this is our function:

function superformula(xc, yc, radius, symmetry, n1, n2, n3) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    angle = symmetry * t / 4
    term1 = pow(abs(cos(angle), n2)
    term2 = pow(abs(sin(angle), n3)
    r = pow(term1 + term2, -1/n1) * radius
    x = xc + cos(t) * r
    y = yc + sin(t) * r
    lineTo(x, y)
  }
  closePath()
}

OK, now what the heck can we do with this? And what are all those parameters supposed to control?

When I’m faced with something like this, I usually start by finding some parameters that create some relatively simple, stable configuration and then start tweaking just one of the parameters to see what it changes. Then move on to another, and eventually look at how various parameters interact. Fortunately, the Wikipedia page on superformulas gives us this nice chart as a starting point:

The numbers on top of each image are the parameters m (what we call symmetry), n1, n2 and n3.

So we can immediately see that the symmetry parameter controls how many “nodes” will be in the shape.

So here’s a symmetry of 3, with all the n parameters set to 1:

And symmetry 5:

And 8:

Now, keeping symmetry at 8, let’s change n1 to 10:

Then down to 3:

And then below 1, down to 0.2:

OK, so we get that n1 makes the sides of the shape bulge in or out.

Alright, now we’ll stick with symmetry 8 and put n1 back to 1. Then we’ll up n2 to 1.5:

We still have 8 nodes, but every other one is a bit rounder, and the in-between nodes are a bit sharper. This gets more obvious at n2 = 2:

At n2 = 5, the rounder nodes have started to double, and the sharper ones are receding:

And here we are at n2 = 10. I had to reduce the radius because the shape was outgrowing the canvas:

Finally, setting n1 and n2 back to 1, we can try increasing n3 to 3:

This has a similar effect to that of n2 but changes alternate nodes, as if the whole shape was rotated 45 degrees.

At this point, I have a pretty good idea of what’s going on and I can just start trying some random parameters:

Symmetry 16, n1=0.5, n2=0.75, n3=2

Symmetry 32, n1=0.9, n2=0.2, n3=-0.3

Anyway, you don’t need me to choose random numbers for you at this point. Play with it. Use big numbers, small numbers, fractions, negatives, primes, numbers that are divisible by each other or not. And see what happens.

Remember, too, that we removed the a and b values from the original formula and used a single radius. You might want to try putting those back in and create some elliptical superformulas. The Wikipedia page also gives an alternate formula where the m parameter is split into two separate values, y and z:

This can allow for even more complex shapes.

I played around with this a little and came up with shapes like this, which I quite like:

But I’ll leave the coding of that up to an exercise for the reader.

Coding Curves 12: Guilloche Patterns

coding curves, misc

Chapter 12 of Coding Curves

Guilloche patterns are very intricate and fascinating patterns. You’ll often find them on bank notes and other official documents as well as watches and other intricate machinery. Because they are so intricate and complex, and often engraved into metal, they are usually done by machines themselves. Imagine a high end spirograph machine with a metal etching tool rather than a ball point pen. The name “Guilloche pattern” is rather vague and can apply to all kinds of similar patterns. I’m going to explain how to make a pattern like the one you see here:

three ringed guilloche pattern

This is a lot like the kind of thing you’d see on some kind of certificate or bank note, and once you understand what’s going on, you can adapt the code to make other similar patterns.

Step One – A Simple Ring

We’ll start by making a single, simple ring that looks like this:

a circle composed of many looping waves

This is very much like a trochoid or a rose curve. In fact, you could probably make this with those formulas, but I’m going to do it a bit differently to set it up for the more complex stuff we’ll do later on.

This is essentially a sine wave that’s wrapped around a circle. Note that it has an inner radius and an outer radius. The sine wave has 80 nodes, but it overlaps itself. To simplify what’s happening here, I’ll increase the inner radius and make it so it doesn’t overlap:

a circle drawn with a sine wave instead of a single curve

Now you can see more clearly that this is, as I said, a sine wave wrapped around a circle. Now I’ll add a little bit of overlap:

a circle drawn with an overlapping sine wave

Here, you should still be able to see the sine wave even with the overlap. The first image is the same idea, but with a lower inner radius and more overlap. Now let’s look how to draw this.

We start with an inner and outer radius. With a bit of easy math we can figure out a “mid” radius. This will be the the zero-point of the sine wave. And we’ll need a range, which is how much the sine wave will have to extend to either side of this radius to hit the inner and outer limits. You might also call this the amplitude of the wave.

width = 600
height = 600
canvas(width, height)

translate(width / 2, height / 2)

inner = 50
outer = 250
range = (outer - inner) * 0.5
mid = inner + range

Next, we need values for how many cycles in the wave, and how much it overlaps. I’ll call these nodes and div. These should be whole numbers and should not be evenly divisible. This is very similar to the n and d parameters in the rose curves we made in the last chapter, but I’ll name them differently to keep things clear.

nodes = 80
div = 11

Now we can loop t from 0 to 2 * PI * div and draw some line segments. The angle of each new point for the next segment is simply t and the radius will be computed as shown. We need to multiply 2 * PI by div to make sure we go around the circle enough times to meet back up correctly.

for (t = 0; t < 2 * PI * div; t += 0.01) {
  radius = mid + sin(t * nodes / div) * range
  x = cos(t) * radius
  y = sin(t) * radius
  lineTo(x, y)
}
stroke()

If you look back at the roses chapter, you’ll see this is very similar to how we got the radius there, but rather than figuring the radius as a sine and single multiplication, we’re using mid and range to fit the radius between inner and outer.

You can play with this a bit. Try different inner and outer radii and number of nodes and values for div. For a good Guilloche pattern, you probably want to keep nodes rather high, and div lower. But Most importantly, they should not be evenly divisible. An easy formula for decent patterns is to make div a small prime number and make sure nodes is not a multiple of div.

For example, if div is 17, you wouldn’t want to use 170 for nodes, or you’ll get this:

a rounded star kind of shape with 10 nodes

But changing nodes to 171 gives you a much better pattern:

a circle drawn with many overlapping loops

If you want to play with an interactive version of this, I made this a few years back:

https://bit101.github.io/lab/dailies/170120.html

Step 2 – A Complex Ring

Next, we’ll add some complexity to the mix. Rather than having our sine wave go back and forth between fixed inner and outer radii, we’ll have these radii themselves vary with a separate sine wave each! The result will look like this:

a single ring of a guilloche pattern

We’ll need a few more parameters for this. To calculate the final outer radius at any point, we’ll need the base outer radius, how many cycles of that sine wave and how far the final radius will differ from the base outer radius. And we’ll need the same three values for the inner radius. And of course we’ll still need nodes and div. The values I used for the above image are:

inner = 100.0
n0 = 7.0
h0 = 10.0

outer = 250.0
n1 = 17.0
h1 = 20.0

nodes = 142.0
div = 89.0

So the inner radius will have 7 nodes, and the radius will vary from 90 to 110, which means a base of 100, plus or minus 10. Similarly the outer radius will have 17 nodes and vary between 230 and 270.

All the code we did early to find the “mid radius” and range for the sine wave will have to be redone for each iteration of the for loop now, using these values and the dynamic inner and outer radii.

for (t = 0; t < 2 * PI * div; t += 0.01) {
  r0 = inner + sin(t * n0) * h0
  r1 = outer + sin(t * n1) * h1

  range = (r1 - r0) * 0.5
  mid = r0 + range

  radius = mid + sin(t * nodes / div) * range
  x = cos(t) * radius
  y = sin(t) * radius
  lineTo(x, y)
}
stroke()

You can surely simplify this code, but I wanted to write it all out explicitly for the sake of clarity.

Again this is a good stopping point to play with some of the parameters and see the different types of shapes you can make. We have one more level of complexity to tackle next.

Here’s an interactive version of this step:

https://bit101.github.io/lab/dailies/170121.html

Step 3 – Multiple Rings

The next step is to create multiple rings that will exactly fit together, like the image that appears at the start of this chapter:

a three ringed guilloche pattern

This is easier than it might seem. If you draw one smaller ring, and then one larger one around that, you just have to make sure that the parameters for the larger ring’s inner radius match the parameters for the smaller ring’s outer radius. Since we’ll be calling the same code multiple times, this would be a good time to turn it into a reusable function. For the most part, this just means wrapping the for loop and stroke call in a function with all the parameters passed in. I also added x and y parameters, so you can center the pattern anywhere on the canvas.

function guilloche(x, y, ir, n0, h0, or, n1, h1, nodes, div) {
  for (t = 0; t < 2 * PI * div; t += 0.01) {
    r0 = ir + sin(t * n0) * h0
    r1 = or + sin(t * n1) * h1

    range = (r1 - r0) * 0.5
    mid = r0 + range

    radius = mid + sin(t * nodes / div) * range
    lineTo(x +cos(t) * radius, y +sin(t) * radius)
  }
  stroke()
}

I upped the canvas size to 800×800 and then used this set of function calls:

guillloche(400, 400, 50, 6, 10, 120, 12, 20, 137, 37)
guillloche(400, 400, 120, 12, 20, 220, 18, 30, 141, 41)
guillloche(400, 400, 220, 18, 30, 350, 24, 20, 164, 53)

This resulted in the following image:

another three ringed guilloche pattern

Note that in the first call, the outer radius params are 120, 12 and 20. These correspond to the inner radius params in the next call. And the outer radius params for the second call, 220, 18 and 30, correspond to the inner radius params on the final call. This way, each ring lines up perfectly.

As usual, play around with this. You can make as many ring as you want. You might want to set up a custom data type that encapsulates all of the radius parameters to make that part more reusable. I’ll leave it to you.

Another thing you might want to try is making each ring a different color:

a colored three ringed guilloche pattern

Here’s an interactive version of the final product:

https://bit101.github.io/lab/dailies/170122.html

Note that there’s no rule that says the rings have to match exactly. Try putting some space between them or letting the overlap and see what kind of interesting patterns you can create that way. This next example has the first and last rings the same as in the previous example, but the middle ring is defined quite differently:

guillloche(400, 400, 20, 4, 5, 120, 8, 10, 137, 37)
guillloche(400, 400, 160, 5, 24, 160, 11, 24, 80, 17)
guillloche(400, 400, 220, 18, 16, 350, 10, 20, 164, 53)

Here, the inner and outer radius is the same, 160, which makes some interesting blobs rather than a discrete ring. All is fair in Guilloche patterns!

a three ringed guilloche pattern. the inner ring does not tightly interlock with the others

And of course, don’t forget to animate!

an animated guilloche pattern

Summary

Do an image search for Guilloche patterns and you’ll find things that are much different, and often more complex than what I’ve presented here. This is just one take on them.

You might try altering the code do draw something other than circles. Ellipses would be a good place to start. And then move onto other shapes. But all that is a bit beyond the scope of this chapter.

Coding Curves 11: Roses

coding curves

Chapter 11 of Coding Curves

Now we come to another one of my favorite types of curves – roses or rose curves. To me, these look a lot like circular Lissajous curves, or very regular harmonographs. In fact, they are a special instance of hypotrochoids, but special enough to look at on their own. Just to give you some instant visuals, here’s a rose curve:

Like many other curves we’ve looked at, we can get a parameterized formula that will take a t value that goes from 0 to 2 * PI and give us back a value that will let us plot the curve. Let’s look back at the formula for a circle first…

function circle(x, y, r) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    x1 = x + cos(t) * r
    y1 = y + sin(t) * r
    lineTo(x1, y1)
  }
}

You could simplify that into a single line within the for loop, but I wanted to spread it out for clarity.

A rose curve uses the same strategy, but instead of a fixed radius, that radius is constantly changing, also based on the t value, as well as another parameter. Here’s the formula for the radius:

r = a * cos(n * t)

So we have two new variables here. a is the overall radius of the rose, and n controls the number of petals in the rose (although the petal count gets a bit complicated, so we’ll come back to that shortly). So we can make a rose function like this:

function rose(x, y, a, n) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    r = a * cos(n * t)
    x1 = x + cos(t) * r
    y1 = y + sin(t) * r
    lineTo(x1, y1)
  }
}

And now, if you want to, you can clean this up a bit:

function rose(x, y, a, n) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    r = a * cos(n * t)
    lineTo(x + cos(t) * r, y + sin(t) * r)
  }
}

For now, let’s just say that n should be a positive whole number. But we’ll explore ranges beyond that of course.

Now we can draw our first rose like so:

width = 800
height = 800
canvas(800, 800)

rose(width / 2, height / 2, width * 0.45, 5)
stroke()

I’ll be using the width * 0.45 a lot here. It just makes the radius a bit less than half the size of the canvas, so the curve will go almost to the edge of the canvas, but never hit it.

And this gives us a 5-petal rose:

The first example at the top of this page used an n of 7. And here is a rose with an n of 11:

So far we’re seeing a good correlation between n and the number of petals. At least for odd values of n. But what if we use an n of 4?

Interesting. This gives us eight petals. This holds true for any value of n. Odd values create n petals. Even values create 2 * n petals. Just to go way out in one direction, here’s one with n = 40, which gives 80 petals. I had to up the resolution – incrementing t in the for loop by 0.001 to keep it from getting jagged.

In the opposite direction, going down to n = 1, gives you a single node:

A bit strange, but it works out mathematically. You’ll find that for negative values, the rose looks the same as for positive values of n. Here’s 5 on the left and -5 on the right:

Unsurprisingly, n = 0 gives us nothing. And so that covers all the whole number roses. If that’s all there was to roses, it would be nice, but there’s a lot more to go.

An Alternate Rose

Actually, before I move beyond whole numbers of n, I want to just mention an alternate rose formula. Instead of using cosine in the radius formula, you can use sine instead:

r = a * sin(n * t)

This gives you the same roses as the original, but rotated. Here’s a 5-petal rose using the original cosine on the left and sine on the right:

And the same for a 8-petal rose (n = 4):

The actual amount of rotation is PI / (2 * n) radians, or 90 / n degrees. For odd values of n, this always has the visual effect of rotating the rose by 90 degrees (the actual rotation may be different, but due to rotational symmetry, it appears to rotate 90 degrees). For even values of n, it rotates the rose so the petals will now be where the spaces between the petals were in the original version.

Fractional values of n

Things start to get more interesting when we start using fractional values for n. We can try it generating a rose with:

rose(width/2, height/2, width * 0.45, 5.0 / 4.0)
stroke()

But this gives us, rather disappointingly, the following:

The problem is that it’s going to have to go beyond 2 * PI to finish it’s cycle. How far beyond? Well, to figure that out programatically, we’ll need to first ensure that the n value is rational. If it’s an irrational number, the rose will continue forever without reaching its exact starting point. We’ll also need to know both the numerator and denominator of that fraction. We can adjust the rose function to take an extra value, so we have n and d for numerator and denominator.

rose(x, y, a, n, d) {
  for (t = 0; t < 2 * PI; t += 0.01) {
    r = a * cos(n / d * t)
    lineTo(x + cos(t) * r, y + sin(t) * r)
  }
}

This doesn’t solve the problem yet, but gets us the first step. If you want you can enforce n and d to be integers to make sure you’re getting a rational fraction, but make sure you convert them so the division in line 3 returns a floating point value.

Now we need to change the for loop limit from 2 * PI to the actual value we need. That limit value is:

limit = PI * d * m

But what is this new m value there? Well, m should be equal to 1 if d * n is odd. And m should be 2 if d * n is even. Woo! A bit complex. But we can simplify it.

We usually test for evenness by taking a number modulo 2. If the result is 0, that means the number is even. If the result is 1, the original number is odd. So we want:

m = 1 when d * n % 2 == 1

and

m = 2 when d * n % 2 == 0

So we can say:

m = 2 - d * n % 2

This gives us:

rose(x, y, a, n, d) {
  m = 2 - d * n % 2
  limit = PI * d * m
  for (t = 0; t < limit; t += 0.01) {
    r = a * cos(n / d * t)
    lineTo(x + cos(t) * r, y + sin(t) * r)
  }
}

Remember, if you are enforcing integers for n and d, you might need to do some casting or conversion to make everything work correctly. I’ll leave that to you. Now we can redo the fractional one like so:

rose(width/2, height/2, width * 0.45, 5, 4)
stroke()

And now we get something much nicer:

This time, the rose continued all the way around and completed itself.

Now you can go to town trying different fractions. I find that things get really interesting when you use higher numbers that are very close to each other. For example, n = 22, d = 21:

Or even 81 and 80:

Roses with fractions less than 1

Things become a whole different type of interesting when you get fractions that are less 1.0. For example, here are roses with n and d of 1,2 on the left, 1,3 in the middle, and 1,4 on the right.

A trick to find interesting patterns is to take a pair of numbers that would usually reduce down, like 17 / 51 will reduce to 1 / 3, giving us the middle figure above. But then shift one of the values a bit. Here’s 17 and 52:

A big difference for just a shift of 1.

Named Roses

Some of these rose curves have special names. I’ll share some of them.

Limaçon Trisectrix

This has a ratio of 1 / 3. We already saw this one above.

Dürer Folium

With a ratio of 1 / 2. Also seen previously.

Quadrifolium

Ratio is 2 / 1

Trifolium

Ratio of 3 / 1

Maurer Roses

If you thought we were almost done, wrong! There’s a whole other type of rose curve to explore – Maurer roses!

Maurer roses start with the basic rose function, but instead of just drawing the curve all the way around, it draws a series of line segments to points along the rose curve. Although it doesn’t have to be so, this is often done with 360 segments and the angles used are specified in degrees. We construct a rose, here using a ratio of 4 / 1, and then pick a degree value to step by. In this case, I chose 49. Then we loop t from 0 to 360 and multiply t by that degree value. So the degrees goes from 0, to 49, 98, 147, 196 and so on. We use that value in our rose (converting to radians of course) and use that at the next point. Here’s what it looks like in action for the first 30 iterations:

To put it a different way, in a normal rose curve, we are incrementing in very tiny increments, so we get a very smooth curve. Here, we are incrementing in gigantic jumps, so we get what looks like is going to be a chaotic mess. But, if we let it finish its full path through to 360 iterations, we get…

Aha! Not a chaotic mess after all! In fact, quite nice. Actually, above I’ve drawn the regular rose on top of the Maurer rose. Here is the Maurer all by itself:

I think the two combined look really nice.

So how do we do this?

Well, again, we start out with the basic rose function. But in this case, we’ll just stick to a single integer value. So just n rather than n and d. But we also want to specify how many degrees to jump on each iteration. To avoid confusion with the earlier d parameter, I’ll call this deg. So the signature is:

function maurer(x, y, a, n, deg) 

Again, we want to loop from 0 to 360 for our initial t value. And then we want to get that value that is t multiplied by deg. This is the degree value shown in the animation above. We’ll call it k but at this point we’re done with degrees, so we’ll convert it to radians by multiplying by PI and dividing by 180

function maurer(x, y, a, n, deg) {
  for (t = 0; t < 360; t++) {
    k = t * deg * PI / 180
    r = a * cos(n * k)
    lineTo(x + cos(k) * r, y + sin(k) * r)
  }
}

We’ll then just execute the rose algorithm, but using k instead of t.

Now we can set something up like the following.

width = 800
height = 800
canvas(800, 800)

maurer(width / 2, height / 2, width * 0.45, 5, 37)
stroke()

// drawing the regular rose is optional
rose(width / 2, height / 2, width * 0.45, 5, 1)
stroke()

And get this:

Play around with different values for n and deg. You’ll find that n works the same way it did for regular roses. But minor variations in deg can create radically different images. For example here is n = 7 and deg = 23:

But moving deg up to 24 gives you this:

Not nearly as nice.

Generally, you’ll find that even numbers for deg will have a lower chance of being interesting than odd numbers (with exceptions).

And anything that divides evenly into 360 is not going to be great. For example, here’s 4, 120:

I drew the full rose too, but the Maurer is just the triangle on the right hand side. Increase that to 121 though, and you get this beauty:

Also, lower prime numbers usually always work pretty well. I’ve noticed that the lower values of n let you get away with higher prime numbers for deg. But it’s something I haven’t tested very thoroughly. Something to play around with.

One more thing you might want to try is fractional Maurer roses. You don’t even have to alter the code at this point. You can just enter the fraction. Because we are always looping from 0 to 360, we don’t need to adjust for a different number of loops. Here’s one to start with. Make sure you put both fraction values into the rose function separately, if you are using that.

maurer(0, 0, width * 0.45, 5.0 / 4.0, 229)
stroke()
rose(0, 0, width * 0.45, 5, 4)
stroke

See what you can find among all the possible variations.

Coding Curves 10: Spirals

coding curves

Chapter 10 of Coding Curves

OK, let’s talk spirals.

Spirals are a lot like circles, in that they are a set of points with a distance relationship to a fixed center point. But unlike circles, where that distance is fixed, with spirals, that distance varies. The distance from a given point to the center point is generally a function based on the angle between those two points. So you’ll usually have some function that takes in an angle and returns a radius. Then you can use the radius and angle to find the x, y position of the point at that angle. There are many different spiral formulas, which give you spirals that have a different look and feel. Let’s start with one of the most basic spirals.

Side note: a circle is sometimes called a “degenerate” spiral. No disrespect intended. It just means that a circle follows the “rules” of spirals but isn’t really what we would think of as a spiral. Like a triangle where one side has a length of zero. All the triangle math generally works fine, but it’s really just a line to our eyes.

The Archimedean Spiral

As you can see, on each cycle, the radius grows by a fixed amount. Here’s the formula for this spiral:

r = a * t

Here, t is the angle and a is some constant, in the case of the above image, it’s 5. The product of these two is the radius at the angle t. If we increase t to 10, we get this:

Now you can see that the a constant determines the spacing between each cycle of the spiral. In this case, it’s grown outside the bounds of the canvas.

Now when we are drawing spirals, we have to decide how many cycles we’ll be drawing. How many times are we going to go around? If t goes from 0 to 2 * PI, we have one cycle:

That’s one cycle. As you can see, for this spiral, the curve starts in the center and expands out from there. Three cycles means t goes from 0 to 2 * PI * 3.

Given all that, we can start to put together a spiral playground, like so:

width = 800
height = 800
canvas(width, height)
translate(width / 2, height / 2)

cycles = 10
res = 0.01

for (t = res; t < 2 * PI * cycles; t += res) {
  r = archimedean(5, t)
  x = cos(t) * r
  y = sin(t) * r
  lineTo(x, y)
}
stroke()

function archimedean(a, t) {
  r = a * t
  return r
}

We say we want 10 cycles, so the for loop goes to 2 * PI * cycles. We call the archimedean spiral function, passing in 5 as the constant, and t as the angle. That gives us a radius, which we use with the angle to find an x, y point and draw a line to it.

Throughout the chapter, I’ll be showing you other functions for different spirals. You can just replace the call to archimedean with the other functions.

The above code gives us this image:

In the drawing api I’m using, angles that increase positively go around in a clockwise direction. So this spiral is going around clockwise from the center. This may be different for you. It depends on how your drawing api handles angles. To reverse the direction, you have a few options:

A. Use scale to flip the canvas

scale(1, -1)

B. Change the code that creates the points to make one axis negative. For example:

  x = cos(t) * r
  y = -sin(t) * r

C. Change the for loop to go backwards:

for (t = -res; t > -2 * PI * cycles; t -= res) {

Doing any of those should get you going around the other way.

One last thing…

One thing to note in the code, is that I defined the for loop to start with res, not 0.

for (t = res; t < 2 * PI * cycles; t += res) {

To see why I did that, let’s move to our next spiral.

The Hyperbolic Spiral

This spiral looks quite different from the first one. Each cycle is not a fixed distance from the past one. You’d be tempted to say that the distance increases on each cycle, but hold that thought.

Here’s the function for the this spiral:

function hyperbolic(a, t) {
  r = a / t
  return r
}

Not very different at all. We’re just dividing a by t rather than multiplying. For the above image, I passed in an a value of 1000. If I bring a down to 10, we get a tiny little spiral like this:

It should also now be obvious why I started the for loop with res rather than 0. If t is 0, then we’d be in a divide-by-zero situation here, which would cause some problems. This might cause a crash, or might just give you a radius of NaN (not a number), which would not be very useful. So we start with a number we know will not be zero.

Another thing to note about this spiral is the direction it goes. I drew the above spiral with 20 cycles. If I move that down to 5 (moving a back up to 1000), we get:

Maybe you can now see that the spiral starts large and grows smaller as t increases. With 100 cycles, the spiral starts to jam up in the center.

So, as I said earlier, at first glance the radius seems to be growing on each cycle, but now you can see that it’s actually starting out large and getting smaller on each cycle.

Let’s move on.

The Fermat Spiral

This is a nice one. Here’s the formula:

function fermat(a, t)  {
	r = a * pow(t, 0.5)
	return r
}

Here we are multiplying a by t to the power of 0.5. This assumes you have a function called pow that raises it’s first argument to the power of the second argument. Depending on your language, you might also be able to say:

  r = a * (t^0.5)

or

  r = a * (t**0.5)

The above image was drawn with 20 cycles and an a of 20. Drawn with 10 cycles, we get:

So you can see this spiral draws from the center outwards. It starts out with a relatively large space between each cycle, but that space reduces slightly the further you go out.

Going back to 20 cycles and changing a to 40:

We see that the distance between cycles has increased.

Here we are with 100 cycles and an a of 15.

At the end, there is hardly any space at all between each cycle. And we have some interesting moiré patterns going on there.

The Lituus Spiral

This looks a lot like the Hyperbolic spiral, but the formula is a lot closer to the Fermat spiral:

function fermat(a, t)  {
	r = a * pow(t, -0.5)
	return r
}

We just used a power of -0.5 rather than 0.5. The above spiral was drawn with 20 cycles and an a of 500. Here it is with 10 cycles:

So you can see that this is another one that draws in towards the center. Going back to 20 cycles, if we lower a to 50, we get:

The word “lituus” originally meant a curved staff, wand or horn. The above image explains best how this spiral got its name.

If we raise a to 1000, we get:

So you could say a lower a causes the spiral to get sucked in to the center more quickly.

So many spirals! Let’s do some more!

The Logarithmic Spiral

This has a feel of being the opposite of the Fermat spiral. The spacing starts quite small, and increases the further it goes out. The formula:

function logarithmic(a, k, t)  {
	r = a * exp(k * t)
	return r
}

This one is a bit more complex than the others we’ve seen. First off, it has two parameters besides the t angle. And we’ll have to get into the exp function.

We actually touched on exp in the Harmonographs chapter. To recap, we have a mathematical constant, e known as Euler’s Number. It’s value is roughly 2.71828. When working with logarithms, it’s quite common to raise e to a power. So some math libraries have included a function to do that directly, often called exp. For a concrete example, let’s take a look at Javascript. It has e as a constant, Math.E. So to get e to the power of 2, you could say:

Math.pow(math.E, 2)

But it also has an exp function, so you can do the exact same thing by saying:

Math.exp(2)

So with this line,

r = a * exp(k * t)

we are multiplying a by e raised to the power of k * t. In the above image, I had a set to 0.5 and k at 0.05.

If we raise a to 1, we get a much larger spiral, though it looks pretty much the same.

Bringing a down to 0.25 gives us:

Again, similar look and feel, but smaller overall.

I’ll reset a back to 0.5 and move k up to 0.1.

Now you can see it expands much faster. Bringing k down to merely 0.04 has a much bigger effect than I expected:

The Golden Spiral

This spiral increases by a rate equal to the “golden ratio”, approximately 1.618. Here’s the formula:

function golden(t) {
	r = pow(PHI, 2 * t / PI)
	return r
}

First, note that this function has no parameters other then t. It’s hard-coded with the value PHI, which is the golden ratio. Many math libraries have that value built in as a constant. If yours doesn’t, you can approximate it by saying

PHI = 1.61803

Or, if you want to be exact, you can use the following to get the exact value:

PHI = (1 + sqrt(5) ) / 2

Then save that as a constant somewhere and use it as needed.

You’ve probably seen this spiral in images like this:

By Romain – Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=114415511

This is actually the Fibonacci spiral. The size of each square the sum of the next two next smaller squares and each curve is made up by a 90-degree arc centered in one corner of each square. It isn’t precisely the same as the Golden spiral, but very close.

… and more

Peruse this list of spirals if you want to find some other interesting ones to try out:

https://en.wikipedia.org/wiki/List_of_spirals

Here’s another good one:

https://mathworld.wolfram.com/topics/Spirals.html

Spirangles

I ran across this term while writing this chapter. Essentially it’s a spiral made by straight line segments. By changing the angle between each segment, you can form different shapes.

These are very easy to make with our existing setup. The above one was made with the archimedean function, an a of 3, and 20 cycles. The trick is reducing the resolution. For this one, I set:

res = PI * 2 / 3

Now on each step of the for loop, t will increase by one-third of a circle. Here are some others, dividing by 4 and 5.

You get the idea. You might want to experiment with some of the other functions too. Some of them are quite satisfying.

Sunflowers

No discussion of spirals would be complete without talking about sunflowers. Just do a search on some combination of the terms, sunflowers, fibonacci, and spiral and you’ll get a ton of reading material and pretty pictures. What it all means, I’ll leave to others, but it’s fun to draw the kind of spiral you find looking at sunflowers. The golden ratio is built right into this pattern, and here’s what it looks like:

You can see not one, but many, many spirals going in and out at different angles. Here’s the code I used to make this (i.e. the pseudocode that represents the actual code I used):

width = 800
height = 800
canvas(width, height)
translate(width / 2, height / 2)

count = 1000
for (i = 0; i < count; i++) {
  percent = i / count
  size = 14.0 * percent
  r = 380.0 * percent
  t = i * PI * 2 * PHI
  x = cos(t) * r
  y = sin(t) * r
  circle(x, y, size)
  fill()
}

We choose a count for how many “sunflower seeds” we want to draw. Here it’s 1000.

Then we loop through using i and get a percent value that it i / count.

The size value is the radius of each “seed”. As i approaches count, percent will approach 1, so size will approach a maximum of 14.

Similarly r is the radius at which to place each seed. It will max out at 380, just shy of half the width of the canvas.

We calculate t using line 11 there, which is just a magical sunflower Fibonacci formula. Seriously though, read up on it if you want to know more. With an angle and radius, we can get an x and y and draw the seed there with the current size.

And that’s about all I have to say about spirals for now. Catch you next time!

Coding Curves 09: Roulette Curves

coding curves

Chapter 9 of Coding Curves

Initially I was going to title this chapter “Trochoids and Cycloids”. I thought they were two different, but related things. As I got into it, I realized I was very confused about what each thing was. Actually a cycloid is just a very specific type of trochoid. I’ll forgive myself though. Here are the definitions of each on Wikipedia:

In geometry, a trochoid (from Greek trochos ‘wheel’) is a roulette curve formed by a circle rolling along a line.

In geometry, a cycloid is the curve traced by a point on a circle as it rolls along a straight line without slipping.

Well, not only are they not two different things, they sound almost identical from those descriptions. The devil is in the details. So let’s explore.

There are three different types of trochoids:

  • Common trochoids (also called cycloids!)
  • Prolate trochoids
  • Curtate trochoids

Beyond those, there are a number of related curves, most of which we’ll cover here:

  • Epitrochoids
  • Hypotrochoids
  • Epicycloids
  • Hypocycloids
  • Involutes

All together, these make up the family of roulette curves. We’ll cover all but Involutes in this chapter.

Now that I’ve thrown a whole bunch of meaningless words at you, let’s figure out what all these things are. Starting with trochoids.

Trochoids

So, as described above, a trochoid is the curve formed by rolling a circle on a line. Let’s visualize that before we get into coding:

If we trace the path of that black dot on the edge of the circle…

…that new curve is a trochoid. In fact, because that drawing point lies exactly on the edge of the circle, it’s a “common” trochoid, also called a cycloid.

If we extend that point out beyond the edge of the circle, we get what’s called a prolate trochoid.

And if the point is inside, the circle, it’s a curtate trochoid.

To make these animations, I moved the circle along from left to right, figured out what its rotation should be at each location, and used sine and cosine to figure out where that point would be based on the position of the circle and its rotation. Then drew lines to that point. But there’s a somewhat more direct formula for trochoids:

x = a * t - b * sin(t)
y = a - b * cos(t)

Here, t is the angle of rotation of the circle, and can just increase infinitely, a is the radius of the circle, and b is the distance from the center of the circle to the drawing point – you could say the radius of that point. Let’s try it out.

width = 800
height = 300
canvas(width, height)

translate(0, height/2)
scale(1, -1)
moveTo(0, 0)
lineTo(width, 0)
stroke()

a = 20.0
b = 20.0
res = 0.05

for (t = 0.0; t < width; t += res) {
	x = a * t - b * sin(t)
	y = a - b * cos(t)
	lineTo(x, y)
}
stroke()

Since this formula is written for Cartesian coordinates, we’ll translate the y-axis to the center of the canvas and flip it.

Then we’ll draw a horizontal line through the center to represent the “floor” that the circle is rolling on. That part is optional.

We set a and b both to 20, so well get a cycloid here. We loop through from 0 to the width of the canvas as t, apply our formulas to that value, and draw a line to the resulting point.

If we raise b to 60, we get prolation.

And if we lower it to 10, we see significant curtation.

I don’t know if prolation and curtation are actual words, but they sound pretty cool.

Anyway, that’s about all there really is to trochoids. Try different values for a and b, or whatever else you want to mix it up with, but I don’t have much more to say about them. But there are several other roulette curves we have left to discuss here.

Centered Trochoids

The next four curves we’ll look at are called centered troichoids. The difference is that rather than the circle rolling along a line, it’s rolling along another circle, either inside or outside that other circle.

The four curves are:

  • Epicycloid – a curve formed by the path of a point exactly on the edge of a circle which is rolling around the outside another circle
  • Epitrochoid – same as above, but the point is within or outside of the moving circle, not on the edge, the same as a curtate or prolate trochoid.
  • Hypocycloid – a curve formed by the path of a point exactly on the edge of a circle which is rolling around the inside another circle
  • Hypotrochoid – same as above, but the point is within or outside of the moving circle, not on the edge

In addition to these there are a number of named curves that are just one of the above with a particular ratio between the radii of the two circles. We’ll take a look at a few of them.

I find these curves to be a lot more interesting than regular trochoids, with a lot more variety. So let’s dig in.

Epitrochoids

First, let’s visualize a circle rolling around the outside of another circle.

And let’s see what we get if we trace the path that black point is drawing.

There’s your epitrochoid!

And in fact, because that point is on the edge of the moving circles, it’s also an epicycloid!

If we move the point out a bit…

And we can move it in a bit.

Again, I made these animations by figuring out where the circle would be, drawing the circle, figuring out what it’s rotation would be at that stage of its journey and drawing the dot, and then connecting the paths of where that dot was on each frame into a curve. It was worth doing in order to show everything at work like that, but there’s a (relatively) simple formula you can just apply:

The formula for an epitrochoid:

x = (r0 + r1) * cos(t) - d * cos(((r0 + r1) * t) / r1)
y = (r0 + r1) * sin(t) - d * sin(((r0 + r1) * t) / r1)

The parameters:

  • r0 is the radius of the fixed circle
  • r1 is the radius of the moving circle
  • t is the increasing angle
  • d is the distance from the center of the moving circle to the drawing point (the same as r1 for an epicycloid).

We can use this in some code to draw an epitrochoid in one shot.

width = 800
height = 800
canvas(width, height)

translate(width/2, height/2)

r0 = 180
r1 = 60
d = 60
res = 0.01

circle(0, 0, r0)
stroke()

for (t = 0.0; t < PI * 2; t += res) {
  x = (r0 + r1) * cos(t) - d * cos(((r0 + r1) * t) / r1)
  y = (r0 + r1) * sin(t) - d * sin(((r0 + r1) * t) / r1)	lineTo(x, y)
}
stroke()

We set r0, r1 and d, and then draw the fixed circle just for reference.

Then we just loop t from 0 to 2 * PI, get an x,y point, and draw a line to that point.

And this gives us:

One thing to note here is the ratio of r0 to r1. 180:60 or 3:1. And we got three nodes. If we change r1 to 45, making the ratio 4:1, we get four nodes. (I changed d to 45 as well to match r1.)

And here, the ratio is 12:1:

If the second number in the ratio is not one, what happens? Let’s set r0 to 150 and r1 to 100. Now the ratio is 3:2.

Not surprisingly, we get one and a half nodes. To finish this curve, we’d have to go around again, having t go from 0 to PI * 4. Then we get:

If you use whole numbers, the cycle will always complete eventually. In the next example, the ratio was 11:7, so I had to go to PI * 14:

And 111:70, meaning I had to go up to 140 * PI:

It’s somewhat of a pain to work out the math manually, but you can create a function to simplify the fraction and use the denominator * PI * 2 as your for loop count. Here’s a couple of functions that will help you:

function gcd(x, y) {
  result min(x, y)
  while (result > 0) {
    if (x % result == 0 && y % result == 0) {
      break
    }
    result--
  }
  return result
}

function simplify(x, y) {
  g = gcd(x, y)
  return x / g, y / g
}

Just put r0 and r1 into the simplify method, to get the simplified ratio. Take the second number times 2 * PI for your for loop limit. Here’s an example…

width = 800
height = 800
canvas(width, height)

translate(width/2, height/2)

r0 = 111
r1 = 70
d = 70
res = 0.01
num, den = simplify(r0, r1)

circle(0, 0, r0)
stroke()

for (t = 0.0; t < PI * 2 * den; t += res) {
  x = (r0 + r1) * cos(t) - d * cos(((r0 + r1) * t) / r1)
  y = (r0 + r1) * sin(t) - d * sin(((r0 + r1) * t) / r1)	lineTo(x, y)
}
stroke()

In line 11, I get the simplified fraction and use the denominator in line 16 to make sure we loop enough times to complete the curve.

So far, all of these have been epicycloids. Let’s change that d parameter to make some other epitrochoids.

Making d larger than r1:

And smaller:

I stopped drawing the inner circle in these cases.

Well, you don’t need me to supply you with more examples. Just change the numbers and see what comes up.

Special Epitrochoids

A Limaçon is an epitrochoid where the two circles have the same radius. If the points is exactly on the circle, it’s a special Limaçon called a Cardioid.

Here’s a Limaçon where both radii are 80 and the value of d is 160.

And a cardioid where all three are 80.

A nephroid is an epitrochoid where the fixed circle’s radius is twice the radius of the moving circle and the point is on the edge of the moving circle. Here’s a nephroid:

Of course you can make the point distance more or less than the radius, but it’s no longer technically a nephroid at that point. Still a nice curve though:

Now let’s move on to hypotrochoids!

Hypotrochoids

As mentioned, this is when the moving circles is rolling along the inside of the fixed circle.

And if we trace the curve that dot draws, we have a hypotrochoid:

In fact, because the point is on the edge of the circle, we also have a hypocycloid.

When the point moves out, we get something like this:

And when we move it further in…

As before, the animation was made in a brute force way with multiple steps, but there is a one-shot formula.

The formula for a hypotrochoid:

x = (r0 - r1) * cos(t) + d * cos(((r0 - r1) * t) / r1)
y = (r0 - r1) * sin(t) - d * sin(((r0 - r1) * t) / r1)

The parameters are the same as for epitrochoids. In fact the formula is almost exactly the same, just a different sign on some of the terms.

Here’s how we can use it.

width = 800
height = 800
canvas(width, height)

translate(width/2, height/2)

r0 = 300
r1 = 50
d = 50
res = 0.01
num, den = simplify(r0, r1)

for (t = 0.0; t < PI * 2 * den; t += res) {
  x = (r0 - r1) * cos(t) + d * cos(((r0 - r1) * t) / r1)
  y = (r0 - r1) * sin(t) - d * sin(((r0 - r1) * t) / r1)
}
stroke()

Running this code gives us:

Note that the ratio of r0 to r1 is 6:1, and we have six points. This works the same way as it did for epitrochoids.

Here’s one with the radii of 312 and 76:

Here’s the same one with the d point moved out:

And moved in…

I’m sure you can build something fun to explore these with.

Special Hypotrochoids

I’ll mention just a couple of special, named hypotrochoids. Again, they have to do with ratios between the two circles.

A deltoid is a hypocycloid with a circle ratio of 3:1.

And an astroid has a ratio of 4:1.

There’s one more interesting named ratio, that of 2:1. This is called a Tusi couple, named after the 13th century Persian astronomer who first described it. Here’s an animation of it.

As you can see, the path formed is a straight line.

If you make a bunch of these, each a bit out of phase with the last one, you get a picture like this:

Each dot is plainly moving back and forth in a straight line. But if you remove all those lines and inner circles, you get quite an illusion of a rotating circle. In fact, it looks like yet another hypocycloid!

Spirograph!

If you were a nerdy kid like me, growing up you had one of these in your house:

This one I bought brand new a few weeks ago in preparation for this chapter. It came in a very cool tin box with some drawing paper and an instruction/inspiration booklet.

It’s got one large gear which is fixed to the paper. In the old days with small pins, and now with some sticky-tack stuff (a great improvement!) And a bunch of smaller gears with holes in them. You put a pen in one of the holes and trace it around in a circle till you are back where you started.

Voila! A hypotrochoid!

Or, you put the smaller gear on the outside and get an epitrochoid. Great fun, though to be honest, I enjoy doing it in code a lot more! Here, the pen slipped and messed up my epitrochoid before I could finish it. Also, the pen was rather skippy.

As I was playing with this, I realized that you can only make “curtate” epi- and hypotrochoids. The drawing point is always less than the radius of the moving gear.

Coding Curves 07: Parabolas

coding curves

Chapter 7 of the Coding Curves Series

I’ll admit that when I started brainstorming this chapter, I began to second guess the decision to include it at all. The curve we’ll cover here, the parabola, is fairly simple, basic, even plain compared to the crazy curves we’ve been generating in the last three chapters. But as I got into it I realized that this curve is pretty cool and has lots of interesting properties. In fact, I was going to cover another curve, the hyperbola, in this chapter as well, but I got so deep into parabolas that I decided to save hyperbolas for another time.

Parabolas

Just to make sure we’re on the same page here, this is what we’re talking about when we’re talking about parabolas:

One of the first things you’ll learn about parabolas is that they are a type of conic section. OK. But since we’re just doing two-dimensional curves here, their relation to a three-dimensional form is a bit irrelevant. But I’ll throw this image in here anyway and be done with the subject:

From https://en.wikipedia.org/wiki/Parabola

Since we’ve already covered circles and ellipses, this chapter and the next will fill out all of the conic sections. That was never a goal, but I’ll never pass up an achievement gained.

In the first picture above, note that the parabola is mirrored across the y-axis, and that the vertex (the highest or lowest point, depending on which way it opens) is just touching the x-axis.

As with most of these geometric shapes, there are various formulas used to describe them. Here’s a fairly simple and usable one:

y = a * x * x

In this case, a is a parameter that controls how wide the parabola opens up – and whether it opens on the top or bottom. So that’s pretty simple. Let’s draw one. But first, let’s set things up correctly and even create a couple of utility functions that will help us do so.

Set Up

As you can see in the initial image, we want the origin of the space to be in the center of the canvas so we can clearly see the parabola. So we’ll want to translate the canvas there.

Also, there’s a very good chance that your drawing api will be drawing things upside down, compared to normal Cartesian coordinates. In other words, y values will increase positively towards the bottom of the screen and negatively towards the top. So it will be good to flip it the other way around.

Finally, it will be nice if we can actually see the axes sometimes. We can create a function to draw them easily.

To center the canvas, you’ll use something like the following:

translate(width / 2, height / 2)

This is assuming your drawing api has transformation functions built in. It’s possible to do everything here without them, but it would be cumbersome to try to explain things twice, so I’m just going to assume that your drawing api, like most decent ones, does have transformation built in.

For the above, we can simplify that by making a center function:

function center() {
  translate(width / 2, height / 2)
}

As always, this is making the pseudocode assumption that your canvas or surface or whatever object you draw things on is a global object and you can just call these functions like that. Processing works that way, but for may other systems, these functions will be methods on a canvas object, so it might be more like

canvas.translate(canvas.width / 2, canvas.height / 2)

But I’m sure you can figure all that out.

To flip the y-axis, just do a scale:

scale(1, -1)

This keeps the x-axis as is, and flips the y-axis. I know, I know. Sometimes in this series I flip the canvas, other times I do not. Once again, these posts are meant to be practical drawing tutorials, not necessarily mathematically rigorous. So you should know how to flip the canvas and do it when and if you think you should.

Finally, I like to have a function around to draw the axes. For the purposes here, we can do something like:

function drawAxes() {
  lineWidth = 0.25
  moveTo(-width, 0)
  lineTo(width, 0)
  moveTo(0, -height)
  lineTo(0, height)
  stroke()
  lineWidth = 1
} 

This draws horizontal and vertical lines well beyond the bounds of the canvas, but that’s usually ok. It also sets the line width very low so it’s just a hint of a line, and then resets it to 1 when it’s done. In your actual function, you’ll probably want to do something like pushing and popping or saving and restoring the state of the canvas, so that your line width goes back to what it was before calling the function, which may not have been 1.

OK, now we can set up our canvas like so:

width = 800
height = 800
canvas(width, height)

center()
scale(1, -1)
drawAxes()

That gets you here, which is a good start:

You might want to combine all that stuff into a more comprehensive setup function. Up to you.

Drawing the Parabola

Building on the last code we can now loop x from -width/2 to width/2 to go across the canvas from left to right. We’ll use a low value of a like 0.003 because we are operating on pixel values that are in the several hundreds. I just chose that value because it worked visually.

a = 0.003
for (x = -width / 2; x <= width / 2; x++) {
  y = a * x * x
  lineTo(x, y)
}
stroke()

And here’s our parabola:

If we change a to something larger, like 0.3, we get a much narrower parabola:

And something smaller like 0.0003, we get a much wider one:

Technically, a should not be 0 in the parabola formula. But if you do try it, you’ll just get a line. Then when a goes negative, the parabola opens up on the opposite side. Here’s -0.003:

And that’s all there is to parabolas.

Oh, no, wait. There’s a few more things!

Focus and Directrix

Parabolas have what is known as a focus point. That point is defined as:

x = 0
y = 1 / (4 * a)

Let’s draw that point:

a = 0.003
for (x = -width / 2; x <= width / 2; x++) {
  y = a * x * x
  lineTo(x, y)
}
stroke()

// draw focus
focusX = 0
focusY = 1/(4 * a)
circle(focusX, focusY, 4)
fill()

And it has another property called the directrix. This is a horizontal line where the y value is:

y = -1 / (4 * a)

We can draw that:

a = 0.003
for (x = -width / 2; x <= width / 2; x++) {
  y = a * x * x
  lineTo(x, y)
}
stroke()

// draw focus
focusX = 0
focusY = 1/(4 * a)
circle(focusX, focusY, 4)
fill()

// draw directrix
directrixY = -1/(4 * a)
moveTo(-width / 2, directrixY)
lineTo(width / 2, directrixY)
stroke()

What’s obvious here is that the distance from that the vertex to the focus is the same as the distance from the vertex to the directrix. Obvious because the formula for the y value of both is the same but reversed in sign.

But here’s an interesting fact – that statement of equal distance holds true for any point on the parabola! We can show that. Building on top of the last code sample…

lineWidth = 0.5
for (x = -width / 2; x <= width/2; x += 40) {
    // find a point on the parabola
	y = a * x * x
	circle(x, y, 4)

    // draw a vertical line from the directrix to that point
    // then from the that point to the focus
	moveTo(x, directrixY)
	lineTo(x, y)
	lineTo(0, focusY)
}

We sample the parabola at a number of points. We draw the point and then draw a line up from the directrix, and then over to the focus. Both lines emanating from each point will be the same length. You may not have a practical use for this straight off, but it looks neat anyway!

Tangent Line

Another thing you can do is find the tangent line at any point on the parabola. This will represent the slope of the curve at that point. The formula for the tangent at point x0, y0 is:

y = 2 * a * x0 * x - a * x0 * x0

This might be a bit confusing because we have x and x0. Again, x0 is a fixed point on the parabola. And x is one of the points the defines the tangent line. The formula gives you the y for that x. Let’s code that up for a single point on the curve. We’ll draw the parabola, then choose an x0, y0 point and use the formula to find two other points that make up the line.

// draw the parabola
a = 0.003
for (x = -width / 2; x <= width / 2; x++) {
  y = a * x * x
  lineTo(x, y)
}
stroke()

// find a point on the parabola
x = -80
y = a * x * x
circle(x, y, 4)
fill()

// find a point on the far left of the canvas
x1 = -width / 2
y1 = 2 * a * x0 * x1 - a * x0 * x0

// and one on the far right
x2 = width / 2
y2 = 2 * a * x0 * x2 - a * x0 * x0

// and draw a line
moveTo(x1, y1)
lineTo(x2, y2)
stroke()

And that gives us this point and line:

Now we can sample various points on the parabola (like we did above) and draw the tangent line for each.

lineWidth = 0.5
for (x0 = -width / 2; x0 <= width/2; x0 += 40) {
    // find a point on the parabola
	y0 = a * x0 * x0
	circle(x0, y0, 4)
    fill()

    // find a point on the far left of the canvas
    x1 = -width / 2
    y1 = 2 * a * x0 * x1 - a * x0 * x0

    // and one on the far right
    x2 = width / 2
    y2 = 2 * a * x0 * x2 - a * x0 * x0

    // and draw a line
    moveTo(x1, y1)
    lineTo(x2, y2)
    stroke()
}

This is pretty much all the same code just moved inside the loop. But then we get this:

You could remove the original parabola and tighten up the interval and have some nice pseudo-string-art.

Parabolic Mirrors

One thing you’ll always read about with parabolas is that rays coming straight in to the parabola will all reflect onto a single point. This is used in antennas and radio telescopes, to focus the received signal on the receiver, and in various solar devices to focus the rays of the sun onto a single point (which becomes incredibly hot). Not surprisingly, the point they are focused on is the focus point.

https://en.wikipedia.org/wiki/Parabolic_antenna#/media/File:Erdfunkstelle_Raisting_2.jpg

In fact, I remember when I was a kid, my step-father had a little solar cigarette lighter like this:

While more of a novelty than something you’d use day to day, it actually worked.

If you wanted to do all the math, you could find out the point where an incoming ray hits the parabola, find the tangent line for that point. Then find the normal at that point (the vector perpendicular to the tangent line) and reflect the incoming ray across that normal. If you did all that, you’d see that the reflected ray hits the focus point. I’m not going to do that exercise with you, but let’s draw some of these rays and you should be able to see that it looks correct. We’ll just draw some rays from the top of the canvas, down to where they hit the parabola and then over to the focus point.

a = 0.003

// assume code for drawing the parabola is here...

// draw the focus
focusX = 0
focusY = 1/(4 * a)
circle(focusX, focusY, 4)
fill()

lineWidth = 0.5
for (x = -width / 2; x <= width/2; x += 40) {
    // find a point on the parabola
	y = a * x * x
	
    moveTo(x, -height / 2)
    lineTo(x, y)
    lineTo(focusX, focusY)
    stroke()
}

You can follow any ray down to the parabola and from there to the focus point. And you can see it reflects off the curve at a believable angle. Again, I cheated and just drew it directly, but you could work out the physics and get the same thing.

Note that this only works for rays that are parallel to the y-axis in this configuration. Rays coming in at any other angle will not converge on the focus. This is why you have to carefully aim the cigarette lighter at the sun to get enough heat, or aim the satellite dish at the satellite to get a good signal.

Another Formula

Of course, parabolas are not always centered on the y-axis and just touching the x-axis. They are so far because we’re using a very simplified formula. Here’s one that we can do more with:

y = a * x * x + b * x + c

We’ve added a couple new parameters here. It’s pretty clear that the c parameter just gets added on to the rest, so has a direct influence on the final y position of the vertex and the rest of the curve. The b parameter is a bit more complex, so let’s code it up and see what it does. We’ll start by making a parabola function

function parabola(a, b, c, x0, x1) {
	for (x = x0; x <= x1; x++) {
		y = a*x*x + b*x + c
		lineTo(x, y)
	}
}

You might want to improve on this, but this will work for our purposes here. We just loop through from x0 to x1, find a y for each x, and draw a line to it.

Let’s see what different values give us.

parabola(0.01, 0, 0, -width/2, width/2)

This gives us what we’re already used to:

Here’s two more with a c of -200 and +200.

parabola(0.01, 0, -200, -width/2, width/2)
parabola(0.01, 0, 200, -width/2, width/2)

Nothing surprising. Now let’s set c back to 0 and give b a positive value.

parabola(0.01, 3, 0, -width/2, width/2)

And a negative value…

parabola(0.01, -3, 0, -width/2, width/2)

So for a positive a, changing b moves the parabola down and to the left or right. What about for negative a?

parabola(-0.01, -3, 0, -width/2, width/2)
parabola(-0.01, 3, 0, -width/2, width/2)

Not too surprising that they move up now.

Note that for any of the formulas we’ve used so far, we can swap the x’s and y’s and get a parabola that opens to the left or the right.

In this case, the simple formula is:
x = a * y * y

And the deluxe version would be:

x = a * y * y + b * y + c

Summary

Well, that’s all we’ll cover about parabolas for now. I don’t know if you’ll ever have the need to draw a parabola with code, but if you do, you will now be well equipped!

Coding Curves 06: Pintographs

coding curves

Chapter 6 of the Coding Curves Series

Another physical device which renders complex curves that we can simulate!

This one is called a Pintograph, and I’ve actually built one of these myself.

We’ll start with a video – this is literally the first video that came up when I searched Youtube for “pintograph”, but it does the job.

A pintograph can be considered to be a type of harmonograph, but rather than being based on pendulums, pintographs are usually driven by electric motors (though some are hand-cranked). There are disks attached to the motors and arms attached to the disks and a pen attached to the arms. You can change the size of the disks and where the arms are attached, the length of the arms and where they pivot, and the relative speed and offset of the motors to create a bunch of different types of curves.

For a long time I didn’t know where the word “pintograph” came from. I finally discovered it from the person who coined the term. Actually, their daughter coined the term. It comes from a pantograph, and the idea that the spinning wheels look like a Ford Pinto. Read more here: http://www.fxmtech.com/harmonog.html

In case you are not familiar with a pantograph, it’s a device that is often used for copying drawings. It has a few pivoting arms. You pin one of the pivot points so it doesn’t move, then put a pointer in one of the points and a pen in another. As you move the pointer along the original drawing, it moves the pen along the same shape. You can configure it to copy the drawing at the same size, or scale it up or down.

From https://en.wikipedia.org/wiki/Pantograph

The Simulation

The pintograph we’ll simulate will be pretty simple. It will have two rotating disks with one arm attached to each. The arms will be attached to each other on the opposite end and that’s where the virtual pen will be.

First of all, we’ll need to simulate the two disks. They’ll each have an x, y position, a radius, a speed and a phase. We’ll make these visible to start with so you can get a feeling for what’s going on. Otherwise it would just be a long complicated formula.

We’ll create two circles and rotate them and show the point where the arms will be attached.

Each disk will be represented by this kind of structure:

disk: {
  x,
  y,
  radius,
  speed,
  phase,
}

Like last time, it doesn’t matter if this is a generic object, a struct, a class or what. I’m going to assume that you’ll also have a function that will create one of these disks like this:

d0 = disk(100, 200, 100, 2, 0.5)
d1 = disk(400, 200, 60, 3, 0.0)

Now we can set up an animation like so:

width = 600
height = 400
canvas = (width, height)
t = 0

d0 = disk(100, 200, 100, 2, 0.5)
d1 = disk(400, 200, 60, 3, 0.0)

function loop() {
  clearScreen()

  circle(d0.x, d0.y, d0.radius)
  stroke()
  x0 = circle.d0.x + cos(t * d0.speed + d0.phase) * d0.radius
  y0 = circle.d0.y + cos(t * d0.speed + d0.phase) * d0.radius
  circle(x0, y0, 4)
  fill()

  circle(d0.x, d0.y, d0.radius)
  stroke()
  x1 = circle.d1.x + cos(t * d1.speed + d1.phase) * d1.radius
  y1 = circle.d1.y + cos(t * d1.speed + d1.phase) * d1.radius
  circle(x1, y1, 4)
  fill()

  t += 0.1
}

Again, loop is a theoritical function that will be fun over and over again so that we get an animation. I’ve set mine up to create the frames for an animated gif, but this can be done as a real time animation as well. Here’s what I got:

This doesn’t smoothly loop, but that’s ok. You can see that we have two disks of different sizes moving at different speeds. The code itself shouldn’t be too complex. We clear the screen and for each disk we draw a circle at its position and with its radius. Then we take an offset from that circle to calculate the point where the arm will be attached. This uses basic trig: x = cos(angle) * radius, y = sin(angle) * radius. Here, the angle is t times the disk’s speed, plus it’s phase. When we get that point, we fill a smaller circle there.

Attaching the Arms and Finding the Pen

OK. Now things get a bit more mathy. We’re going to have two arms. each one is going to be attached to one of those spinning points at one and, and they’ll be attached to each other on the other end. At this point, a sketch is in order:

We have our two disks. Through their radii and rotation and position, we know the positions of points p0 and p1. That’s what we just did above. We can also define the lengths of those arms. They’ll be the same for now, but they could be different. We’ll call them a0 and a1. (I know, it looks like 90 and 91 in the sketch. Sorry!) What we need to do is get the position of that top point where the two arms join.

We can easily get the distance between p0 and p1 with the Pythagorean theorem. We can call that d. It’s represented by the dotted line in the diagram.

Now we have a triangle whose sides are a0, a1 and d.

There’s a trigonometric rule called the “law of cosines” that will help us here. With it, if you you know the lengths of all three sides of a triangle, a, b, and c, you can get any angle of that triangle. Usually the way it’s written is as follows though:

c = sqrt(a*a + b*b - 2*a*b*cos(y))

… where y is the angle opposite side c. So if you know the length of two sides and the angle between them, you can find the length of the opposite side. Also useful in some cases, but not what we need here.

But we can rearrange that formula and put the unknown variable by itself on one side, and the other side will be the formula you need to calculate that value.

What I want is to know is one of those angles so I can use it to find the location of the pen. Here’s what I came up with, showing the formula, some hand-wavey algebra and the resulting formula we’ll need.

In our case, the sides we have correspond to the sides in the formula like so:

a = a0
b = d
c = a1

So if we apply this formula, we’ll get the angle between the p1, p0, and the pen.

We can also use atan2 to get the angle from p0 to p1. And if we subtract them, we get the actual angle that goes from p0 to the pen.

Again, a drawing is in order:

The big angle that goes from p1 to p0 to the pen, which we get with the law of cosines, we call p1_p0_pen. The smaller angle we get with atan2, we call p0toP1. Subtract them and we have the angle we have to go towards to get the location of the pen. I’m sure there are different ways to do this, and probably much better ones, but this one works. Once you have all the steps, you can further simplify it to something more concise, but I wanted to show the steps to hopefully have it make some sense.

Anyway, we can now code this up:

width = 600
height = 600
canvas = (width, height)
t = 0

d0 = disk(150, 450, 100, 2, 0.5)
d1 = disk(450, 450, 60, 3, 0.0)

function loop() {
  clearScreen()

  circle(d0.x, d0.y, d0.radius)
  stroke()
  x0 = d0.x + cos(t * d0.speed + d0.phase) * d0.radius
  y0 = d0.y + sin(t * d0.speed + d0.phase) * d0.radius
  circle(x0, y0, 4)
  fill()

  circle(d0.x, d0.y, d0.radius)
  stroke()
  x1 = d1.x + cos(t * d1.speed + d1.phase) * d1.radius
  y1 = d1.y + sin(t * d1.speed + d1.phase) * d1.radius
  circle(x1, y1, 4)
  fill()

  // the length of the arms
  a0 = 350
  a1 = 350

  // get the distance between p0 and p1
  dx = x1 - x0
  dy = y1 - y0
  d = sqrt(dx * dx + dy * dy)

  // find the two key angles and subtract them
  p1_p0_pen = acos((a1 * a1 - a0 * a0 - d * d) / (-2 * a0 * d))
  p0toP1 = atan2(y1 - y0, x1 - x0)
  angle = p0toP1 - p1_p0_pen

  // find the pen point
  pX = x0 + cos(angle) * a0
  pY = y0 + sin(angle) * a0
  
  // draw the arms
  moveTo(x0, y0)
  lineTo(pX, pY)
  lineTo(x1, y1)
  stroke()

  t += 0.1
}

With that, you should be able to render something like what you see below.

Again, this is a non-cleanly looping gif, but shows the idea at work. I’ve commented the code to explain what’s going on in each step. Hopefully that helps. Note that I also changed the size of the canvas and moved the disks towards the bottom to make room for the arms.

One thing to be careful of is making sure that the two arms are long enough so that they can always reach between the two connection points on the disks. In the real world, if they were too short, they’t probably just break or jam up the motors. In code, you’ll probably just get a NaN (not a number) error when you go to do the acos and you’ll be sitting there wondering what’s wrong. I speak from experience.

Drawing the Curve

Finally, let’s see what this thing draws. For this, I’m going to abandon the animation and stop drawing the circles and arms. I’ll just track where the pen is on each iteration and use that to draw a long, looping, Lissajous-ish curve.

width = 800
height = 600
canvas = (width, height)

function render() {
  t = 0
  
  d0 = disk(250, 550, 141, 2.741, 0.5)
  d1 = disk(650, 550, 190 0.793, 0.0)

  // the length of the arms
  a0 = 400
  a1 = 400
  
  for (i = 0; i < 50000; i++) {
    x0 = d0.x + cos(t * d0.speed + d0.phase) * d0.radius
    y0 = d0.y + sin(t * d0.speed + d0.phase) * d0.radius

    x1 = d1.x + cos(t * d1.speed + d1.phase) * d1.radius
    y1 = d1.y + sin(t * d1.speed + d1.phase) * d1.radius

    // get the distance between p0 and p1
    dx = x1 - x0
    dy = y1 - y0
    d = sqrt(dx * dx + dy * dy)

    // find the two key angles and subtract them
    p1_p0_pen = acos((a1 * a1 - a0 * a0 - d * d) / (-2 * a0 * d))
    p0toP1 = atan2(y1 - y0, x1 - x0)
    angle = p0toP1 - p1_p0_pen

    // find the pen point
    pX = x0 + cos(angle) * a0
    pY = y0 + sin(angle) * a0
  
    lineTo(pX, pY)
    t += 0.01
  }
  stroke()
}

I’ve left lots of room for you to optimize this, so go for it! Even in its rough form though, this code should draw something like this:

There’s lots of things to play around and experiment with here. But the truth is that this is a pretty simple pintograph, so mostly the shapes are going roughly look like what you see above. But you can build on these principles and make all kinds of more complex devices. This site has several demos to inspire you:

https://michaldudak.github.io/pintograph/demo/

Disks on disks, and rotary pintographs and 3-wheel pintographs, etc.

And if you’re into this stuff, just search “harmonograph”, “pintograph” and “drawing machines” on Youtube to get an endless supply of inspiration – either for coding or actually building. Some of the more interesting ones (in my mind) are the ones that draw the curves on a piece of paper that itself is slowing rotating on a turntable.

Summary

This wraps up our discussion of Lissajous curves and the simulation of physical drawing machines. At least for a while. Next time we’ll go back a bit more to basic, standard geometric curves.

Coding Curves 05: Harmonographs

coding curves

Chapter 5 of the Coding Curves Series

This installment builds on Chapter 4’s discussion of Lissajous curves. Actually, a harmonograph is not a type of curve, it’s a device used to draw Lissajous (and similar) curves. And when I say a device, I mean a real world physical device that has ropes or chains and levers and pen and paper or bottles of sand and pendulums or other mechanics to create these curves.

Real Harmonographs

The first time I saw a harmonograph was at the Museum of Science in Boston on one of my many childhood trips there. It was a pendulum with a container of sand that leaked out and created a trail. The video below is not the exact one I was familiar with, but is essentially the same thing. I didn’t know it was called a harmonograph until many years later.

The video is worth watching and discusses Lissajous patterns in depth. With a pendulum, the time it takes to go back and forth is called its period, but it’s what we called the frequency in the last installment of this series. At around 4:12 in the video, the presenter explains how this pendulum can have two different periods – one on each axis. This is why the patterns it forms look like Lissajous curves – because they are! If each axis had the same period, it would just create circles and ovals and spirals. Still technically Lissajous curves, but not as interesting.

Here’s another version using a pen and paper:

From https://en.wikipedia.org/wiki/Harmonograph

In this case, the pen is stationary and it’s the paper that moves around on a pendulum. But it accomplishes the same thing.

Here’s another video of a similar setup, which appears to be made of cardboard, string and tape!

There is a key difference between a pure Lissajous curve and a mechanical, pendulum-based harmonograph though. The pendulum slowly loses energy and the distance it travels on each pass gets smaller and smaller. Eventually it will stop swinging all together and sit stationary in the middle of the drawing.

While this type of harmonograph will produce some interesting drawings, you can make it even more complex by using a double pendulum. A common way to do this is to have the paper or drawing surface moving on one pendulum, and the pen moving on another one.

Here’s one example of this:

Here, both the paper and pen are mounted on top of the pendulums, which are weights swinging below the table. So they move independently and are able to produce more complex curves.

And here’s yet another video of a very fancy double-pendulum, showing some of the amazing drawings it can create. Quite a couple of characters here too.

Simulated Harmonographs

As this is more of a programming focused site, I’m not going to explain how to build a physical harmonograph. But, these devices operate on the principles of physics. And the formulas that control them are known. We can use these formulas to create a virtual harmonograph.

We’ll start with a simple, single pendulum version. But first, let’s revisit our Lissajous curve formula:

x = A * sin(a * t + d)
y = B * sin(b * t)

A and B are the amplitude of the wave on each axis and a and b are the frequencies. And d is the delta, which puts x out of phase with y. And of course t is the parametric time variable. Initially we said that t would go from 0 to 2 * PI, but later we saw how it could increase infinitely.

To start to move towards simulating a harmonograph, we’ll recognize that each axis will have its own phase, rather than thinking one is phased and the other … unphased? So we’ll change the single d to p1 and p2.

x = A * sin(a * t + p1)
y = B * sin(b * t + p2)

This still gives us a Lissajous curve, but with a bit more complex definition. To fully move it to a simulated harmonograph, we’ll need to simulate that loss of energy, or damping. To do this fairly accurately, this will be an additional multiplier that looks like this:

e-d*t

… or “e to the power of minus d times t”

In code, that might be:

pow(e, -d * t)

Where pow is the power function, available in any fine math library.

So what is all this? We have two new variables here: e and d. Actually e is a constant, aka Euler’s number, equal to approximately 2.71828. I’ll let you read up on that on your own, but e is used in all kinds of real world physical formulas. Including, it seems, the damping of pendulums.

By now you might have guessed that d is for the damping factor. We’ll set d to a pretty small number, something like 0.002 is good to start with. Now when t is 0, that will make the exponent 0 and the result of the power calculation will be 1.0.

As t increases, by say 0.01 on each iteration, the value of the exponent will slowly grow negatively. When t is 0.01, the exponent will be -0.00002, and the result of the whole damping equation will be 0.9999800002.

After 100 iterations, t will hit 1.0 and the damping factor will be 0.9980019987. After 1000 iterations, it will be 0.9801986733. So you can see this goes down very slowly. If you increase d, it adds more damping and that number will go towards 0.0 faster. Here’s how we work it into the harmonograph equation:

x = A * sin(a * t + p1) * pow(e, -d1 * t)
y = B * sin(b * t + p2) * pow(e, -d2 * t)

Notice I made it d1 and d2, so you can have separate amplitudes, frequencies, phases and damping for each axis.

To bring it all home, as t increases, that last part of the equation moves closer and closer to 0.0, meaning both x and y will get smaller and smaller and will eventually be 0.0 themselves, simulating the pendulum running down and stopping. The higher d1 and d2 are, the quicker that will happen.

Depending on the math library you use, there may be a shortcut here. Since taking e to some power is a pretty common operation, there’s usually a built in function for doing just that, often called exp. For example in JavaScript, you could say Math.exp(-d1 * t), which would be exactly the same thing as Math.pow(Math.E, -d1 * t), but a bit shorter, and potentially more optimized.

Thus we can change the pseudocode to:

x = A * sin(a * t + p1) * exp(-d1 * t)
y = B * sin(b * t + p2) * exp(-d2 * t)

The Function

Let’s make something happen! Here’s our function:

function harmonograph(cx, cy, A, B, a, b, p1, p2, d1, d2, iter) {
  res = 0.01
  t = 0.0
  for (i = 0; t < iter; i += res) {
    x = cx + sin(a * t + p1) * A * exp(-d1 * t)
    y = cy + sin(b * t + p2) * B * exp(-d2 * t)
    lineTo(x, y)
    t += res
  }
  stroke()
}

Whole lotta parameters goin’ on. But you should understand most of this by now. The one I’ll mention is iter. Earlier we were mostly looping from 0 to 2 * PI. Now we want to loop a whole lot more than that, as the curve is going to continue to change as it is dampened and the amount of motion decreases. We’ll use a very high number for iter which simulates the harmonograph running for a long time. Real world harmonographs can take five minutes or longer to complete a single drawing.

Here’s an example of the function in use:

width = 800
height = 800
canvas(width, height)

A = 390
B = 390
a = 2.0
b = 2.01
p1 = 0.3
p2 = 1.7
d1 = 0.001
d2 = 0.001
iter = 100000

harmonograph(width / 2, height / 2, A, B, a, b, p1, p2, d1, d2, iter)

Yeah… 100,000 – a hundred thousand iterations. Might take a second or two. But you should get something like:

Here are some others I came up with by trying random parameters:

I’ve found that it’s best to keep a and b close to whole numbers, and let them vary by a very small amount, like in the first example where they were 2.0 and 2.01. It also works pretty well if the numbers are easily broken down into a relatively simple ratio, like 7.5 and 2.5 which is 3:1. And again, if you add a small amount to one of them it becomes a little more interesting, like 7.5 and 2.501. But totally random numbers like 5.7 and 3.2 will make for rather chaotic drawings.

The d values change how fast the pendulum is dampened, so a very low value will tend to draw more lines away from the center. Here’s the first example with damping on both axes at 0.0003:

And the same with damping of 0.003:

The pendulum decayed more quickly and you get more lines towards the center.

You can play with this endlessly. Try adding color too!

Double Pendulums

The drawings produced in that last video look pretty compelling. To do that, we need to come up with a double pendulum harmonograph simulation. You can consider that we have an x, y pendulum for the pen, and an x, y pendulum for the paper. Both will move independently, and wind up creating much more complex curves. You just need to calculate both x-axis pendulums and add them together for the final x, and the same on the y-axis.

Although the concept is relatively straightforward, this means we’ll be doubling the number of parameters we need.Four each for amplitude, frequency, phase and damping. If we do this naively, we might come up with something like this, which is really tough to manage.

// don't code this!!!
function harmonograph2(cx, cy, a1, a2, a3, a4, f1, f2, f3, f4, p1, p2, p3, p4, d1, d2, d3, d4, iter) {
  res = 0.01
  t = 0.0
  for (i = 0; t < iter; i += res) {
    x = cx + sin(f1 * t + p1) * a1 * exp(-d1 * t) + sin(f2 * t + p2) * a2 * exp(-d2 * t)
    y = cy + sin(f3 * t + p3) * a3 * exp(-d3 * t)+ sin(f4 * t + p4) * a4 * exp(-d4 * t)
    lineTo(x, y)
    t += res
  }
  stroke()
}

I’ve tried this and totally confused myself multiple times trying to remember which frequency value controlled which axis of which pendulum, etc. A better idea (maybe not the best, but better) would be to encapsulate the parameters needed for a single axis pendulum (amplitude, frequency, phase and damping) into a single value object, and pass four of those into the function.

I don’t know if the platform you’re using has classes or structs or plain old generic objects, so I’m just going to say that we have some kind of object with four properties:

pendulum: {
  amp,
  freq,
  phase,
  damp,
}

Don’t worry about the syntax here. Use whatever you need to use that will create such an object.

Now we can create four of these, maybe call them penX, penY, paperX, paperY. Like this:

penX = pendulum(90.0, 7.5, 1.57, 0.0001)
penY = pendulum(90.0, 4.0, 0.0, 0.0001)
paperX = pendulum(280.0, 1.001, 1.57, 0.0001)
paperY = pendulum(280.0, 2.0, 0.0, 0.0001)

Again, don’t get caught up in the syntax. This could be a factory function, a constructor, or you could just create some kind of object literal with those values – amplitude, frequency, phase and damp – for each pendulum.

Now we can change the harmonograph2 function to look like this:

function harmonograph2(cx, cy, penX, penY, papX, papY, iter) {
  res = 0.01
  t = 0.0
  for (i = 0; t < iter; i += res) {

    x = cx
      + sin(penX.freq * t + penX.phase) * penX.amp * exp(-penX.damp * t)
      + sin(papX.freq * t + papX.phase) * papX.amp * exp(-papX.damp * t)

    y = cy 
      + sin(penY.freq * t + penY.phase) * penY.amp * exp(-penY.damp * t)
      + sin(papY.freq * t + papY.phase) * papY.amp * exp(-papY.damp * t)

    lineTo(x, y)
    t += res
  }
  stroke()
}

There’s still a lot of code duplication there, but at least the method signature is better. I’ve done what I could to make it as readable as possible. There’s probably more you can do to make this whole set up easier, but that’s often the fun part of programming something complex – taking a somewhat janky proof of concept and turning it into an elegantly coded application. I don’t want to take that away from you, so I’ll leave plenty of room for improvement.

Anyway, now you can take those pendulum values we created and feed them into the function like so:

penX = pendulum(90.0, 7.5, 1.57, 0.0001)
penY = pendulum(90.0, 4.0, 0.0, 0.0001)
paperX = pendulum(280.0, 1.001, 1.57, 0.0001)
paperY = pendulum(280.0, 2.0, 0.0, 0.0001)

harmonograph2(width/2, height/2, penX, penY, paperX, paperY, 100000)

And if you’ve done everything right (and if I wrote this all up correctly), you should get an image like this:

Pretty neat, eh? There’s nothing magic about the values of those parameters. I just fiddled and tweaked and experimented and came up with something that looked cool. Here’s some more parameters to try:
penX = pendulum(50.0, 17.5, 1.57, 0.0001)
penY = pendulum(50.0, 11.0, 0.5, 0.0001)
paperX = pendulum(280.0, 0.50, 1.57, 0.0007)
paperY = pendulum(280.0, 1.50, 0.0, 0.0007)

Anyway, play with that for a while. There is an infinity of shapes you can create.

Animation

So far, we’ve just created static images here, but this is ripe for animation. You can do the obvious and show the curve building up over time just as if a real harmonograph were drawing it. I’ll skip over that one as it’s pretty easy and from my viewpoint not all that interesting.

What’s more fun is to animate some of the other properties. The phases are good candidates. Here’s an example where one of the phase values just moves from 0 to 2 * PI. It almost looks three-dimensional.

And here, some of the damp values are going back and forth between 0.001 and 0.0001.

Summary

So that’s harmonographs. Code it up and have some fun with it. There’s an endless ways you can tweak this to create interesting shapes. Heck, you might even decide to go buy some hardware and make a physical harmonograph. I’d love to see it if you do.

Next chapter we’ll be looking at yet another physical device and simulating that.

Coding Curves 04: Lissajous Curves

coding curves

Chapter 4 in the Coding Curves Series

Make sure you are familiar with at least the first chapter, to understand how the code samples work.

Lissajous curves have always been one of my favorite techniques. They are useful for many things beyond the obvious loopy shapes they create. In this installment, we’ll cover the basics, and as usual, wander off course here and there to look at other ways they can be used.

The Basics

Lissajous curves are also known as Lissajous figures or Bowditch curves. Both names came from the names of men who looked into and wrote about them in the 1800s.

These figures are formed by long looping curves that go back and forth and left and right. In their pure form, they recall the glowing patterns seen on oscilloscopes used for special effects in a vintage scifi movies.

Image from https://mjoldfield.com/atelier/2015/08/xy-scope.html under Creative Commons Attribution-ShareAlike 3.0 Unported License

The Formula

In this case, a useful parametric formula was one of the first things I found in researching this subject.

x = A * sin(a * t + d)
y = B * sin(b * t)

Basically what we have here is a sine wave on the x-axis, and another sine wave on the y-axis. So instead of going off infinitely in any direction, it keeps looping back in on itself.

We have a bunch of variables here. Let’s break them down.

A and B wind up being the width and height of the curve on the x- and y-axes. Or technically, half the width and height because the curve will extend that distance in each direction.

The t is a parametric variable that will range form 0 to 2 * PI. Although it can actually go beyond that in either direction, it will effectively just loop back on itself when it does. t is multiplied by a for the x component, and by b for the y component, and the sine is taken of the result on each axis. In addition, the x component has a d or delta variable added to it to move it out of phase.

This formula might look a bit familiar from the previous chapter on circles. If we set A and B equal to each other and call them r, and set a and b both equal to 1, then remove the d and use cosine instead of sine for the x component, then we get:

x = r * cos(t)
y = r * sin(t)

This is the parametric formula for a circle (with its center at 0, 0). Since cosine is the same as sine but 90 degrees out of phase, we could also say:

x = r * sin(t + d)
y = r * sin(t)

… where d equals 90 degrees, or PI / 2 radians.

You can also see the relation to the ellipse formula if you keep A and B separate, where A is what we called the “x radius” and B is the “y radius”:

x = A * sin(t + d)
y = B * sin(t)

So when everything is in sync like this, we get a circle or ellipse, but when we start changing these parameters, we get much more interesting curves.

OK, enough talk, let’s code. We can jump right into making a Lissajous function. I’ll abbreviate a bit.

function liss(cx, cy, A, B, a, b, d) {
  res = 0.01
  for (t = 0; t < 2 * PI; t += res) {
    x = cx + sin(a * t + d) * A
    y = cy + sin(b * t) * B
    lineTo(x, y)
  }
  closePath()
}

Here, the resolution value is not as straightforward as in circles and ellipses. For now, I’m just going to keep the res value very small, even if it means we’re doing to much work for simpler, smaller curves. Since we’re going from 0 to 2 * PI (6.28…), incrementing by 0.01 will give us 628 line segments, which should be enough for most cases. If it starts getting blocky, you can increase it, but I’m not going to go into how to best predict it.

Now we can use this in a sketch like this:

width = 600
height = 600
canvas(600, 600)

liss(300, 300, 250, 250, 1, 1, PI / 2)
stroke()

Here, I did what I described above, setting A and B equal to each other, a and b to 1, and d to PI / 2. This should give us a circles, and in fact…

… it does.

Let’s set d to 0 and mess with a and b for a while. Here, a and b are 2 and 1 (again, with d at 0):

And now they are 2 and 3:

Let’s crank them up to 11 and 8.

In all these cases, with d at 0, the waves on each axis are in phase with each other. Here, I kept 11 and 8 and set d to 0.5, moving them out of phase:

Here’s an animation with a at 6, b at 7, and d varying.

It’s worth noting that a and b should be whole numbers if you want the curve to join its start and end points together smoothly. Here’s what happens if you set a to 6.4 and b to 7.3:

You can see even more clearly what is happening, if you remove the closePath call from the function:

Now you can see the curve starts and ends in random locations, rather than joining up smoothly as it does when a and b are whole numbers.

Of course, when you use fractional numbers here, you can now extend your t way past the 0 to 2 * PI range and continue to fill the space. Here, t goes from 0 to all the way up to 20 * PI:

The paths will eventually join up again if you use rational numbers for a and b. But it might take a while. Here it looks like they came pretty close.

We can of course, change A and B to get wide figures:

A = 250, B = 100

or tall figures:

A = 100, B = 250

That’s about it in terms of drawing these figures directly. Check out the Wikipedia article on the subject to learn more about the various properties of this type of curve. I was thinking about creating an interactive demo here, but it turns out there are a bunch of them on line already. A few are linked near the bottom of the Wikipedia article I just mentioned.

Instead we can look at some other applications here.

Animation

Here, I’m not talking about animating the curve itself, like I did above, but using the Lissajous curve results to animate an object. But first let’s talk about a simpler level of animation.

Oscillation

Often you want to animate an object but have it remain on screen. You just want it to go back and forth or up and down, or twist one way or the other or even grow and shrink. You might recognize that these are some of the more basic 2D transformations – translation, rotation and scaling. You can use a sine or cosine function to generate values for these transformations, and just change the input to that function over time.

We’ll have to expand our pseudocode to include a loop function that runs repeatedly forever and can draw something differently each time it’s run to create an animation. Processing has something like this built in, as may other graphics systems. But you might have to put something together yourself. If you’re using HTML and JavaScript you can use requestAnimationFrame.

And we’ll postulate into existence a clearCanvas function too. Why not? We’ll also include a circle function that can be the one we created in the last installment, or one that comes with your drawing api. We’ll start something like this:

width = 400
height = 400
t = 0
canvas(width, height)

function loop() {
  clearScreen()
  circle(width / 2 + sin(t) * 100, height / 2, 20)
  fill()
  t += 0.1
}

Which should give you something close to this:

This takes the sine of t and multiplies that by 100 to get an offset position for the circle on the x-axis. As t is continually incremented, the circle keeps going back and forth. You could easily do the same thing on the y axis, or you could apply the sine to the radius:

width = 400
height = 400
t = 0
canvas(width, height)

function loop() {
  clearScreen()
  circle(width / 2, height / 2, 50 + sin(t) * 20)
  fill()
  t += 0.1
}

Here, we’re taking 50 as the base radius, and using the sine of t times 20 to that. Because sine will go from -1 to +1, we’ll be adding -20 up to +20 to the radius, making it oscillate from 30 to 70.

We could do the same thing with rotation, but then we’d have to switch to something other than a circle so we could actually perceive it rotating. I’ll leave that to you to try.

But going back to moving the circle around. Say we want to have it move all around the canvas. We could of course use sine and cosine to make it move in a circle:

width = 400
height = 400
t = 0
canvas(width, height)

function loop() {
  clearScreen()
  circle(width / 2 + cos(t) * 100, height / 2 + sin(t) * 100, 20)
  fill()
  t += 0.1
}

But what if we wanted something more organic? We could start adding random motion, but then you run into the problem of how to constrain it so it doesn’t wander off screen. Not insurmountable, but we can do a pretty good trick using the Lissajous formula, like so:

width = 400
height = 400
t = 0
a = 13
b = 11
canvas(width, height)

function loop() {
  clearScreen()
  circle(width / 2 + sin(a * t) * 100, height / 2 + sin(b * t) * 100, 20)
  fill()
  t += 0.02
}

This is an imperfectly looping gif, but you get the idea. The circle just seems like it’s randomly moving around the screen, but it’s actually following the path of a Lissajous curve. To me it looks like a fly buzzing around. In fact, if you add several of these with different parameters, it gives you a pretty convincing swarm of flies. High numbers for a and b, that don’t have common denominators, make the most random looking path.

Lissajous Webs and Random Lissajous Webs

This isn’t anything remotely “official” that you’ll find documented anywhere (well, maybe one place I’ll mention below), but I think it’s always good to cross-pollinate different ideas. You often come up with something very unique. So I’m just throwing these out there as something I came up with.

Some years ago I had been playing about with drawing Lissajous curves and was thinking about different ways of rendering them. Rather than just rendering the paths themselves, I decided to try capturing each point that made up the curve and then connecting nearby points with lines. The results were pretty cool, and I dubbed them “Lissajous Webs”. Here’s a few examples:

You can find a few more examples here: http://www.artfromcode.com/?p=657

I thought these were pretty damn cool looking. The technique was included in the book Generative Design (with full credit to and permission by me).

As I continued to play around with the idea, I wanted to make them even more organic. Rather than fixed a and b multipliers, I started letting these parameters wander a little bit, randomly. These wound up creating some really amazing images, which I called “Random Lissajous Webs”.

This concept became one of my more popular pieces. I was even commissioned some years later to do a series of designs using this technique that got used on wine bottles.

You can see more pictures of those here: https://www.anarchistwineco.com/ (and buy the wine!)

I’m not going to go through the code for all of these. This is more just for inspiration – how the concept of Lissajous curves led to something entirely different and very interesting.

The next chapter will look at a couple of related systems that are also very interesting ways of creating curves.