2022 in Review

misc

I’ll keep this light this year. Professionally, a strange and difficult year. But at least I still have a job, so I’m grateful for that.

Beyond the job though, I did do some fun coding projects.

Probably the biggest was my raytracing journey. I’ve never been super interested in that kind of photo-realistic rendering from an artistic viewpoint. But working through the books and creating the renderer itself was the most fun I’ve had coding in ages. Of course, once it was sufficiently “complete”, I kind of lost interest in it. It didn’t really change the basic fact that 3D stuff is not what I’m really interested in creatively. That said, I’ll probably play with it some more in the future. My original thinking was to do some kind of generative stuff that could then be rendered in 3D. But “generative” fairly often means lots of individually created units interacting in some way – at least the way I usually do generative. so it can get expensive rendering hundreds or thousands of objects. I might need to look more into optimizing the engine – I skipped that chapter in the book!

Other than that, I created a few vim plugins. One of which, bufkill, I use on a regular basis and really love. I made a few others, but none that were really worth talking about too much. During my plugin-writing phase, I also did a lot of upgrading of my neovim setup, switching my whole config to use Lua, and dropping Conquer of Completion in favor of neovim’s built-in LSP system for completion and syntax checking. That was a lot of work, but I’m happy with the result.

And of course, I started the Coding Curves series, which I’m still really excited about. As I said in the initial post, this was a book that I’ve wanted to write for years. I finally came to the conclusion that I was not going to sit down and write it from start to finish, but writing each chapter as a blog post was way more doable. I banged out ten “chapters” so far and they’ve been pretty well received. I’m taking a short break, but will be back to do the rest before too long. Most of what’s left I already have lots of code and examples written for, and a lot of the explanations are kind of half written in my head already.

I haven’t put out a lot of creative-code artwork this year, but in the last few weeks I’ve been working hard to upgrade my personal code libraries. I currently use a couple of libraries of my own in every creative coding project I do: blcairo which is the drawing api with a ton of custom rendering routines, and bitlib which has math, geometry, color, a PRNG, noise, and other various utilities I use all the time. I love this library and I’ve added a lot to it in recent months.

Oh, and I quit Twitter. I fully deleted my account and set up a new private account with the old user name just to keep anyone from taking it over and impersonating me. It was tough to walk away from 8,000 followers, but every time I see the toxic mess that the platform has become, I have zero regrets. Find me now at https://mstdn.social/@bit101. I remain very happy with Mastodon.

Plans for 2023

There are a few things I want to do in the new year.

  • Create my own interpreted language. A while back I got the book Writing an Interpreter in Go and worked partway through it. It’s quite good and I want to do more with this. My idea would be a simple interpreted language that would depend on my own Go libraries as described above. This would probably just be for my own education and personal use. I don’t have a lot of interest in supporting a language in the wild.
  • I want to finally do something with music. At this stage in life, I doubt I will ever learn any kind of instrument, but I have enjoyed doing stuff with electronic music on and off over the years. I’m currently messing around with MilkyTracker, which reminds me of playing with mod trackers back in my Amiga years. I know trackers are kind of toys, but that’s all I’m really looking for now. Something to play with and have fun and maybe learn a bit. Eventually I might like to dip my toes into musical hardware, synths, sequencers, etc. Anyway, one of my goals will be to release a song in 2023. “Release a song” sounds way more serious than I mean it to sound. I will create a song and put it on my web site and hopefully it won’t suck too much (but, fair warning, it probably will). If anyone has any advice or resources for learning this stuff, let me know.
  • I’d love to do another side project creating graphics for something. My last big projects were back in 2018. I’m open for projects!
  • Of course, I’ll finish the Coding Curves project. Probably in January or February. I’m toying with the idea of a similarly structured project with a different subject, tba.

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 08: Bézier Curves

misc

Chapter 8 of the Coding Curves Series

I had to hold myself back here. Bézier curves are fun to program, fascinating to explore, and you can go down a deep hole in explaining how they are constructed and what the formulas mean. The thing is, I’ve already done that a few times. In books and in videos. Here’s a couple of my own videos you might want to check out to learn more:

https://www.youtube.com/watch?v=dXECQRlmIaE: Coding Curves 08: Bézier Curves https://www.youtube.com/watch?v=2hL1LGMVnVM: Coding Curves 08: Bézier Curves

And here are a couple of amazing videos by Freya Holmer:

https://www.youtube.com/watch?v=aVwxzDHniEw: Coding Curves 08: Bézier Curves https://www.youtube.com/watch?v=jvPPXbo87ds: Coding Curves 08: Bézier Curves

So I’m going to limit myself here to the bare basics, some functions, and some cool, practical tips and tricks I’ve discovered over the years.

The Basics

A Bézier curve is defined by two end points and one or more control points. It starts at one end point, curves towards (but not through) the control point(s) and ends at the other end point. By moving any of the points, you alter the shape of the curve. These curves are generally visually pleasing and are used in all kinds of design tools, and are a key part of the shapes of things from fonts to cars.

There are two types of Bézier curves that you’ll run into most often.

Quadratic Bézier Curves

These are defined by two end points and a single control point. Here’s an example:

The control point is the one near the bottom of the canvas. If I move that to the right, it changes the curve:

The lighter lines and black points I just threw in for visual context.

Cubic Bézier Curves

Cubic Bézier Curves have two end points and two control points. Example:

It is possible to have higher order Bézier curves with more control points, but the math gets more and more costly. See Freya’s videos above for some explanations about this.

Most drawing apis have methods for both quadratic and cubic curves, but how they are names varies greatly.

I’ve seen quadratic Bézier curve methods named:

  • curveTo
  • quadraticCurveTo

And cubic Bézier curve methods named:

  • curveTo
  • cubicCurveTo
  • bezierCurveTo

So make sure you know what the method names are for your api. A common strategy, as seen in the examples above, is to have the starting point defined by using a moveTo, or the last known position of the drawing cursor, and then have the curve method just define the control points and the final end point. So you’d do something like:

moveTo(100, 100)
cubicCurveTo(200, 100, 200, 500, 100, 300)
stroke()

But some apis may have other methods that allow you to specify all the points at once.

That’s about it for the basics and built-ins, but of course, we’ll now leave the apis behind and code up some curves ourselves.

Coding Bézier Curves

We’ll start with quadratic curves and then move on to cubic. But before we create the methods that draw the paths, we’re going to create another, more basic method. This will give us the point at any interval along the Bézier curve.

Quadratic

Interestingly, the basic formulas for Bézier curves are one-dimensional. To make two-dimensional, three-dimensional or higher Bézier curves, you just apply the formula once for each dimension. We’ll be sticking two 2D here, so we’ll be doing this twice. The single parametric formula is:

x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2

Here, x0, x1, and x2 are the end and control “points” and t is a value that ranges from 0.0 to 1.0. This returns the x value along the Bézier path corresponding to the value of t. When t is 0, x is equal to x0. When t is 1, x is equal to x2. When t is between 0 and 1, x will be interpolated.

So to make a 2D quadratic Bézier point function we do this:

function quadBezierPoint(x0, y0, x1, y1, x2, y2) {
  x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2
  y = (1 - t) * (1 - t) * y0 + 2 * (1 - t) * t * y1 + t * t * y2
  return x, y
}

You can do this if your language lets you return multiple values. Otherwise, you’ll have to encode x, y in come kind of point object.

Note that we have a lot of duplication going on there. We can clean it up first factoring out all those 1-ts:

function quadBezierPoint(x0, y0, x1, y1, x2, y2, t) {
  m = (1 - t)
  x = m * m * x0 + 2 * m * t * x1 + t * t * x2
  y = m * m * y0 + 2 * m * t * y1 + t * t * y2
  return x, y
}

And then:

function quadBezierPoint(x0, y0, x1, y1, x2, y2, t) {
  m = (1 - t)
  a = m * m
  b = 2 * m * t
  c = t * t
  x = a * x0 + b * x1 + c * x2
  y = a * y0 + b * y1 + c * y2
  return x, y
}

If nothing else, this makes it much easier to read.

Now that we have this, we can make a function to draw quadratic Bézier curves. To make it perfectly clear, I’ll name this quadCurve and the cubic one will be cubicCurve

function quadCurve(x0, y0, x1, y1, x2, y2, res) {
  moveTo(x0, y0)
  for (t = res; t < 1; t += res) {
    x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    lineTo(x, y)
  }
  lineTo(x2, y2)
}

To make sure we explicitly start and end on the start and end points, we’ll start with an explicit moveTo to the first point and end with an explicit lineTo to the last point. The function takes a res parameter that lets you know how many steps to take along the curve. We’ll start t out equaling res because we’ve already moved to the first point, which is what you’d get if t was 0. In the middle of all that, we just get the point corresponding to the current t and draw a line to it.

Of course, you can make a quadCurveTo method by dropping the first two parameters and the moveTo. This will rely on the user using their own moveTo to specify the starting point of the curve (or continuing it from an existing path). Example of this in use:

canvas(800, 800)
quadCurve(100, 100, 200, 700, 700, 300, 0.01)
stroke()

Which gives us:

If we change the res to something larger like 0.1, we see that things get a bit chunky:

So here you’ll have to experiment with a resolution value that works well. Given that the built-in Bézier methods already figure out a good resolution for you, this curve method has questionable value. But, it got us to write the quadBezierPoint function, which has a LOT of value, as we’ll see.

One thing that the point function can do that the built in methods can’t is animation. For this section, like I did in previous chapters, I’m going to assume you have or can make some kind of function that runs repeatedly and can create animations. I’m going to call it loop. What I’m going to do here is rather than drawing the curve from a t of 0 to 1, I’ll have it go from 0 to a value finalT that will change over time.

canvas(400, 400)
x0 = 50
y0 = 50
x1 = 150
y1 = 360
x2 = 360
y2 = 150
finalT = 0
dt = 0.01
res = 0.025

function loop() {
  clearCanvas()
  moveTo(x0, y0)
  for (t = res; t < finalT; t += res) {
    x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    lineto(x, y)
  }
  stroke()

  // add to finalT
  finalT += dt

  // if we go past 1, turn it around
  if (finalT > 1) {
    finalT = 1
    dt = -dt
  } else if (finalT < 0) {
    // if we go past 0, turn it back
    finalT = 0
    dt = -dt
  }
}

And this should give you an animation that looks something like this:

Here, the for loop is going from res to finalT so it doesn’t draw the entire curve (unless finalT equals 1). Then we change finalT by adding dt to it. This brings finalT closer and closer to 1, so the curve is drawn more and more fully. Eventually finalT will go beyond 1 so we set it back to 1 and make dt negative, which reverses the whole process until finalT goes below 0, where we bounce it back the other way.

Rather than just drawing a line, we can animate an object along a Bézier path now! Here’s the code for that. It should be pretty clear. I’ll just include the loop function. The rest should be the same.

function loop() {
  clearCanvas()

  x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, finalT)
  circle(x, y, 10)
  fill()

  // no changes beyond here...
  // add to finalT
  finalT += dt

  // if we go past 1, turn it around
  if (finalT > 1) {
    finalT = 1
    dt = -dt
  } else if (finalT < 0) {
    // if we go past 0, turn it back
    finalT = 0
    dt = -dt
  }
}

Now we’re just getting the x, y point for the current value of finalT and drawing a circle there. This assumes you have a circle drawing function. You can use the one we created in Chapter 3 if you need one.

In this example, I drew a light line for the same quadratic curve using the built-in method of my api, just to show that we’re on track with the standard definitions of these things.

OK, let’s take a break here and jump over to cubic curves

Cubic

Pretty much everything I said above for quadratic curves is going to apply to cubics. It’s just a different formula – a bit more complicated. Here is is for one dimension:

x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3

And the 2D function:

function cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t) {
  x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3
  y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3
  return x, y
}

Yikes! That’s a mess. Let’s clean it up again by factoring out the 1 - ts:

function cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t) {
  m = 1 - t
  x = m * m * m * x0 + 3 * m * m * t * x1 + 3 * m * t * t * x2 + t * t * t * x3
  y = m * m * m * y0 + 3 * m * m * t * y1 + 3 * m * t * t * y2 + t * t * t * y3
  return x, y
}

That’s a little better. One more step to clean it up:

function cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t) {
  m = 1 - t
  a = m * m * m
  b = 3 * m * m * t
  c = 3 * m * t * t
  d = t * t * t
  x = a * x0 + b * x1 + c * x2 + d * x3
  y = a * y0 + b * y1 + c * y2 + d * y3
  return x, y
}

Much better!

Now we can make a cubicCurve function.

function cubicCurve(x0, y0, x1, y1, x2, y2, x3, y3, res) {
  moveTo(x0, y0)
  for (t = res; t < 1; t += res) {
    x, y = cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t)
    lineTo(x, y)
  }
  lineTo(x2, y2)
}

That was easy. No explanation or example needed I think.

Now for your assignment: adjust the animations we did above to work for cubic curves. It’s really just a matter of adding a new x3, y3 point and calling the new function.

That’s the basic code for implementing Bézier curves and paths. But there are a few more neat tricks for you.

Drawing Through a Point

At some point, everyone who starts coding Bézier curves is going to say:

This is neat, but I want it to go THROUGH the control point(s).

Me – some time around the year 2000.

Well, we can do that! It’s pretty easy for quadratic curves. What you need to do is create another control point that will pull the curve even further out so that it just goes through the original control point. And that new point is pretty simple to calculate. Where the points are x0, y0, x1, y1, x2, y1, the new control point will be:

x = x1 * 2 - x0 / 2 - x2 / 2
y = y1 * 2 - x0 / 2 - x2 / 2

Now we can make a function, let’s call it quadCurveThrough that implements this behavior. It just gets this new point and uses the built-in function to draw the curve. I’m going to postulate that’s called quadraticCurveTo on your system, but it might be something else.

function quadCurveThrough(x0, y0, x1, y1, x2, y2) {
  xc = x1 * 2 - x0 / 2 - x2 / 2
  yc = y1 * 2 - y0 / 2 - y2 / 2
  moveTo(x0, y0)
  quadraticCurveTo(xc, yc, x2, y2)
}

Here I’ve drawn a regular quadratic curve in red and one using this function in blue. And I drew in the points to prove that it does what I claim it does!

Your next question is how to do the same thing for cubic curves. I don’t have an answer for that one yet. But I will keep digging. I guess there’s a chance that someone will comment the answer here, or tell me it’s not possible. 🙂

Piece-wise Quadratic Bézier Curves

The other question people commonly ask is:

How do I make a Bézier curve with N control points (where N is 3 to infinity)?

Also me around the same time as earlier question

As mentioned earlier, this is mathematically possible, but it gets prohibitively expensive when you move past cubic. That’s why you’ll probably never see quartic or quintic Bézier curve functions. But it’s still a very useful thing to have a smooth curve with an arbitrary number of control points. And of course you’ve seen this kind of thing any time you’ve used a pen tool in a drawing program.

In the splines video above (second one by Freya), she shows making a longer curve by piecing together multiple cubic Bézier curves.

https://www.youtube.com/watch?v=jvPPXbo87ds: Coding Curves 08: Bézier Curves

These are sometimes called piecewise Bézier curves. I’m going to show you something a bit simpler, using only quadratic curves. It’s not too hard to implement and it supports any number of control points you want to throw at it. I’ll even show you a version that creates a closed loop.

This technique is actually covered in depth in the second video I posted above (by me):

https://www.youtube.com/watch?v=2hL1LGMVnVM: Coding Curves 08: Bézier Curves

So I’m not going to do too deep a dive here, but I’ll go over the basics and give you some code and examples.

The basic principle is you’re going to first create a new point that is mid-way between p0 and p1. Call that pA. And another that’s between p1 and p2. Call that pB. Draw a line from p0 to pA, then draw a quadratic curve using pA, p1 and pB.

Then you’ll get midpoint from p2 and p3, called pC and make a curve from pB through p2 and ending at pC.

You’ll continue that until the end where you draw a curve from the second-to-last midpoint, through the second-to-last point, ending at the last midpoint. Then finally a line from the last midpoint to the last point.

And here’s the curve:

The code for this can get a little tricky, but having worked through it a few times, I’m pretty happy with a method that looks like the following. Note, that due to the potentially large number of parameters that will be passed in, it really helps to have some kind of point object. Whether that’s a class, structure, or generic object with x and y properties.. up to you and your language. This method will take an array of these point objects. The code assumes the array has a length property, but there might be something different in your language, like a len method.

function multiCurve(points) {
  // line from the first point to the first midpoint.
  moveTo(points[0].x, points[0].y)
  midX = (points[0].x + points[1].x) / 2
  midY = (points[0].y + points[1].y) / 2
  lineTo(midX, midY)

  // loop through the points array, starting at index 1
  // and ending at the second-to-last point
  for (i = 1; i < points.length - 1; i++) {
    // find the next two points and their midpoint
    p0 = points[i]
    p1 = points[i+1]
    midX = (p0.x + p1.x) / 2
    midY = (p0.y + p1.y) / 2

    // curve through next point to midpoint
    quadraticCurveTo(p0.x, p0.y, midX, midY)
  }

  // we'll be left at the last midpoint
  // draw line to last point
  p = points[points.length - 1]
  lineTo(p.x, p.y)
}

That method seems long, but I added a lot of comments to each part.

For an example, I generated a half dozen random points. I don’t know how you’d do that on your system, so I’m going to say we have a function randomPoint(xmin, ymin, xmax, ymax). (Actually I do have such a method in my own library!) Once you have your points array, you just pass that array to your new function and stroke the resulting path:

context(800, 800)
points = []
for (i = 0; i < 6; i++) {
  points.push(randomPoint(0, 0, 800, 800))
}

multiCurve(points)
stroke()

The glorious result:

Quite nice. I also drew all the points to give a bit more context on why the curve looks like it does.

Closed Curves

The final thing in this section will be to alter the method to allow for a closed loop. Mainly that’s going to be getting rid of the starting and final line segments, and figuring out the curve from the end of the curve back to the beginning.

function multiLoop(points) {
  // find the first midpoint and move to it.
  // we'll keep this around for later
  midX0 = (points[0].x + points[1].x) / 2
  midY0 = (points[0].y + points[1].y) / 2
  moveTo(midX0, midY0)

  // the for loop doesn't change
  for (i = 1; i < points.length - 1; i++) {
    p0 = points[i]
    p1 = points[i+1]
    midX = (p0.x + p1.x) / 2
    midY = (p0.y + p1.y) / 2
    quadraticCurveTo(p0.x, p0.y, midX, midY)
  }

  // we'll be left at the last midpoint
  // find the midpoint between the last and first points
  p = points[points.length - 1]
  midX1 = (p.y + points[0].x) / 2
  midY1 = (p.y + points[0].y) / 2

  // curve through the last point to that new midpoint
  quadraticCurveTo(p.x, p.y, midX1, midY1)

  // then curve through the first point to that first midpoint you saved earlier
  quadraticCurveTo(points[0].x, points[0].y, midX0, midY0)
}

To explain a bit more here’s the first step and the for loop:

We move to the first midpoint, then loop through the rest, finding midpoints and doing quadratic curves. This leaves us at the last midpoint. Then…

We find the midpoint between the first and last points, and execute the last two curves to close up the shape. The following image is made with the same setup as the last one, but calling multiLoop instead of multiCurve (and different random points).

These are two of my favorite functions, and I’m happy to share them with you.

Even Distribution

The final trick I want to share has to do with evenly distributing objects on a quadratic curve. One use case for this is when you want to have text follow a curve. You want to be able to space the letters evenly on the curve. You’ll also want to rotate them to follow the curve, but that’s beyond the scope of this article.

At first this might seem like a trivial problem. You have your t value that you use to divide up the curve. If you want, say, 20 object spaced out on the curve, just have them 0.05 apart. 20 x 0.05 = 1.0. Work done. Well, let’s try it.

canvas(800, 800)

x0 = 100
y0 = 700
x1 = 100
y1 = 100
x2 = 700
y2 = 400

moveTo(x0, y0)
quadraticCurveTo(x1, y1, x2, y2)
stroke()

// 20 evenly spaced t values (21 counting the end one)
for (t = 0; t <= 1; t += 0.05) {
  x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
  circle(x, y, 6)
  fill()
}

Here’s what that gives us.

Not evenly spaced at all. The ones near the ends are spaced out and in the middle they are closer together. That’s just the way it is with Bézier curves. So, we have to figure out a way to get those points evenly spaced.

Sadly, there’s not a very easy way to do this. I’m going to give you a horribly un-optimized, brute force way to get decent results, and a couple of hints on how to make it better.

So, to get evenly spaced points along a curve, it makes sense that you need to know the length of the curve. If the length is 200 pixels, and you want 20 points, you put one point every 10 pixels along the length of the curve.

Surprisingly, there is no simple formula to get the length of a Bézier curve. But we can get pretty close by sampling a bunch of points along the curve and getting the distance between each pair. This would look something like this:

function quadBezLength(x0, y0, x1, y1, x2, y2, count) {
  length = 0.0
  dt = 1.0 / count
  x, y = x0, y0
  for (t = dt; t < 1; t += dt) {
    xn, yn = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    length += distance(x, y, xn, yn)
    x, y = xn, yn
  }
  length == distance(x, y, x2, y2)
  return length
}

Here, count is how many samples we want to take. The more samples, the more accurate we’ll be.

Then, dt is the amount to increase t by as we loop through the curve.

We keep track of the last point, x, y, which will start as x0, y0. Then we loop through the curve getting each new point, xn, yn and finding the distance between the last point and the new point, then making the new point the last point. I’m not going to show you how to find the distance between two points, just assuming that you have a function for that. You add that distance to the accumulating length.

Then you do one last length addition for x2, y2. Then return the length.

Make sure that all makes sense first, because I’m going to throw something else in there.

It’s going to be very useful to keep track of what the length was at each point as we work through the curve. So we’re going to store each successive value in an array. And rather than returning the total length, we’ll return the array.

function quadBezLengths(x0, y0, x1, y1, x2, y2, count) {
  lengths = []
  length = 0.0
  dt = 1.0 / count
  x, y = x0, y0
  for (t = dt; t < 1; t += dt) {
    xn, yn = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    length += distance(x, y, xn, yn)
    lengths.push(length)
    x, y = xn, yn
  }
  length == distance(x, y, x2, y2)
  lengths.push(length)
  return lengths
}

Now the full length of the curve is in the last element, but we have a whole bunch of other sub-lengths too. Here’s what we do.

count = 500
lengths = quadBezLengths(x0, y0, x1, y1, x2, y2, count)
length = lengths[count-1]

for (i = 0.0; i <= 1; i += 0.05) {
  // the length of the curve up to the next point
  targetLength = i * length

  // loop through the array until the length is higher than the target length
  for (j = 0; j < count; j++) {
    if (lengths[j] > targetLength) {
      // t is now the percentage of the way we got through the array.
      // this is the t value we need to get the next point
      t = j / count

      // get the point and draw the next circle.
      x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
      circle(x, y, 6)
      fill()
      break
    }
  }
}

OK, a bit complex, but let’s go through it. We get the lengths using a count of 500, and capture the total length.

The we loop through from 0 to 1 by 0.05, like we were doing before. But rather than using that as the t value of the Bézier curve, we use it to find a fraction of the length of the curve. Say the curve was 500 pixels long and i was 0.5, then the target length we are looking for is 250 for the next point.

Now we loop through the array with j and get the length values until we go above our target length. If we divide j by count, we wind up with the t value that created this particular length. We plug that back into the Bézier point function to get the next point and draw it. We also want to break out of the inner loop at that point, since we are done and can move on to the next point.

This gives us the following:

We missed the last point (because we never went beyond that length), but we could just draw another point at x2, y2. This is pretty close to evenly spaced. The higher the value you use for count, the more accurate it will be. If we move count down to 100, we can see it’s off:

Now, there is a LOT wrong with this code. Mostly in its optimization.

First of all, rather than looping through from 0 on each point, we could use a binary search.

Secondly, we have to add a lot of points to get any kind of accuracy. Because we are just grabbing the last point we find in that inner loop. Instead of taking a predefined point, which we know is a bit too high, we could interpolate between that point and the previous point.

For example, say our target length was 150. And at index 87 we got a length of 160. We look back at index 86 and we find 140. OK, so we want a value half way in between 86 and 87. So instead of calculating t as 87 / count, or even 86 / count, we interpolate 50% and say 86.5 / count. It still won’t be perfect, but you can use a lower count and still get good results.

I’m going to leave all of that as an exercise for you.

If you want more info on this technique, and a fuller explanation, check out this site:

http://www.planetclegg.com/projects/WarpingTextToSplines.html

Summary

So there, you go, a few basics, a few tips, a few tricks. Until next time…

Goodbye Twitter

misc

I’ll keep this brief. I’m leaving Twitter after 16 years. A tough decision. 8000 followers. Lots of history. It was an overwhelmingly positive experience. But the platform is in a very different place now and I just don’t want to have any connection to it going forward. I’ll disable my account some time on December 13, 2022 probably.

You can now find me over at Mastodon at bit101 (@bit101@mstdn.social) – Mastodon 🐘

I’ve flirted with Mastodon for several years now. There is now a critical mass of people I know there. I’m having a great time there and it feels very much like the early days of Twitter. Good conversation, good creativity, low drama.

I met so many people and maintained so many good relationships on Twitter. I hope to continue to stay in touch with a lot of you, either here, on Mastodon, or on other avenues.

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!