Coding Curves 03: Arcs, Circles, Ellipses

coding curves

Chapter 3 in the Coding Curves Series

In this installment we’ll look at how to draw arcs, circles and ellipses. (And wander off on some tangents before we get done.)

It’s likely that your platform’s drawing api has at least some of this built in. For example, the HTML Canvas api does not have a circle method, but it does have an arc method as well as an ellipse method, either of which can be used to draw circles.

But it’s good to know how to do all of these manually. You’ll wind up using it someday, somewhere on some platform.

First let’s just look at arcs and circles. You can say that an arc is just part of a circle, or you could say that a circle is an arc that extends 360 degrees. So we could go at this either direction, but it makes sense to me to start with circles and specialize into arcs.

Note on Measurements

The drawing api I am using has the y-axis reversed from standard Cartesian coordinates. Negative values go up, positive down. This is very common for drawing apis which aren’t specifically made for math or science use cases. It’s the same with Processing, HTML canvas, Cairographics, .net graphics, and many others. Some apis do use Cartesian coordinates and have positive angles going counter-clockwise. Yet others, such as pygame, mix the systems – y values go down positively, but positive angles go couter-clockwise.

This affects the measurements of angles. Zero degrees is due east. In a Cartesian system, positive angles will move in a counter-clockwise direction and negative angles in a clockwise direction. In reversed y-axis systems, the opposite is true. In this chapter, I don’t make any attempt to “correct” this. The functions we’ll be creating here will mirror the built-in functions of many drawing apis.

But know that if you do want to create circle, arc and ellipse functions that operate per the Cartesian coordinates, that’s easy enough to do. And when you finish the section on arc, you’ll know how to do that.

Circles

Definition

A circle is usually defined something like “the set of points that are equidistant from a given center point”. But when you are trying to draw a specific circle, that’s not very useful. I don’t need a set of infinite points. I just need enough points to draw short line segments through that will form a circle.

You’ll also see the “equation of a circle” as x2 + y2 = r2. Also pretty useless from the perspective of trying to draw one.

But then you get to the parametric form:

x = a + r * cos(t)
y = b + r * sin(t)

Here, a, b is the center of the circle, r is the radius, and t is a parametric variable that ranges from 0 to 2 * PI.

This starts to get useful. We can define the center point and the radius and then run a for loop from 0 to 2 * PI to get a set of points that we can draw lines through.

Eternal reminder. All the code here is pseudocode. See the first post in this series for more info.

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

cx = width / 2
cy = height / 2
radius = 250
for (t = 0; t < PI * 2; t += 0.01) {
  lineTo(cx + cos(t) * radius, cy + sin(t) * radius)
}
closePath()
stroke()

Depending on your drawing api, you might need to start off with a moveTo before the lineTos. If so, I trust you’ll figure that out. Otherwise, this is straightforward. cx, cy and radius are a, b and r from the above formula. And t is the angle we loop through.

Note the final closePath call there. That’s a feature of most drawing apis. It will draw a final line segment from where the last operation left off to the place where the current path started, closing the circle. It might be different on your platform but there should be something there.

This gives us…

circle

One question is the 0.01 value in the for loop. At this point, this was just a rough guess. If you make it too big, like 0.2, then you’re going to be jumping around the circle in large jumps and it’s not going to look quite as good:

But if you make the increment too small, then you’re doing a lot of extra work for nothing. The larger the radius, the larger the circumference, and the more segments you need to use to make it smooth. The smaller the radius, the fewer the segments you need.

If you’re going with a constant 0.01 increment, you’re drawing 628 segments for each circle. This is way too many for a small circle.

By shear trial and error, I’ve found that a workable resolution seems to be about 4.0/radius. This looks good on circles down to a radius of 5. And on a radius of 200 draws half as many line segments as an increment of 0.01 and looks just as good. You can do some experiments on your own and see what looks good to you as it may vary by system.

A Function

With this, we can make a circle drawing function like so:

function circle(x, y, r) {
  res = 4 / r
  for (t = 0; t < PI * 2; t += res) {
    lineTo(x + cos(t) * r, y + sin(t) *r)
  }
  closePath()
}

Note that I left off the stroke in the function. This way you can create the circle with this function and then choose to stroke it, fill it or do both. If you want, you can make a strokeCircle function and a fillCircle function. Here’s how you’d use this:

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

circle(width / 2, height / 2, 200)
stroke()

Arcs

Now that we have that down, we can build on this to create an arc function. Again, your api may already have this, but lets’ do it anyway.

This’ll be pretty easy. It’s the same as the circle function, but instead of starting at 0 and ending at 2 * PI, we let the user say what angles they want to start and end at.

This is all pretty straightforward, I’ll just throw the code out here without any pre-explanation:

function arc(x, y, r, start, end) {
  res = 4 / r
  for (t = start; t < end; t += res) {
    lineTo(x + cos(t) * r, y + sin(t) *r)
  }
  lineTo(x + cos(end) * r, y + sin(end) *r)
}

Told you it was simple. Instead of hard-coding the start and end angles to 0 and 2 * PI, we make them parameters. Also, I removed the call to closePath and replaced it with a final lineTo that draws a last line to the end angle, just to be precise.

To use it:

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

arc(width / 2, height / 2, 250, 0.5, 3.5)
stroke()

And that gives you:

But there are a couple of problems. What if I entered the start and end angles in the opposite order?

arc(width / 2, height / 2, 250, 3.5, 0.5)

This will jump out of the for loop right away because 3.5 is already greater than 0.5. Nothing gets drawn. What I probably wanted was to start at the angle of 3.5 and go around until I crossed the start of the circle and hit 0.5 again, like this:

One way to handle this is just to make sure that the end angle is greater than the start angle. We can do that by checking if it’s smaller and then adding 2 * PI to it until it is bigger.

function arc(x, y, r, start, end) {
  while (end < start) {
    end += 2 * PI
  }
  res = 4 / r
  for (t = start; t < end; t += res) {
    lineTo(x + cos(t) * r, y + sin(t) *r)
  }
  lineTo(x + cos(e) * r, y + sin(e) *r)
}

Now this should work as expected and produce the image shown above. One more thing though. We’re always making the assumption that we’re drawing the arc clockwise. We should allow the user to make that decision. Luckily this is pretty simple. We’ll just add another parameter, anticlockwise. If this is true, we just need to swap start and end and we should be good.

function arc(x, y, r, start, end, anticlockwise) {
  if (anticlockwise) {
    start, end = end, start
  }
  while (end < start) {
    end += 2 * PI
  }
  res = 4 / r
  for (t = start; t < end; t += res) {
    lineTo(x + cos(t) * r, y + sin(t) *r)
  }
  lineTo(x + cos(e) * r, y + sin(e) *r)
}

If you’re lucky, your language will let you do the swap like this:

start, end = end, start

If not, you’ll have to go the old fashioned route:

temp = start
start = end
end = temp

Now this code:

arc(width / 2, height / 2, 250, 3.5, 0.5, false)
stroke()

will give you this arc:

And this code:

arc(width / 2, height / 2, 250, 3.5, 0.5, true)
stroke()

will give you this arc:

Both start at an angle of 3.5 and draw an arc to 0.5. One goes one way, the other goes the opposite way.

As mentioned at the beginning of the article, positives angles going clockwise is the default I chose here, unlike Cartesian coordinates. Now that you know how to draw arcs in either direction, you are free to make your default whichever way you want.

Now that we have a solid arc function, we can actually go back and remove some duplication from our circle function, changing it to this:

function circle(x, y, r) {
  arc(x, y, r, 0, 2 * PI, true)
}

This draws an arc from 0 to 2 * PI, which is a circle.

Segments and Sectors

There are a couple of other functions you can create if you find them useful. A segment is an arc that is joined by a line segment between its beginning and end (a chord). We can do this by drawing an arc and then just calling closePath or whatever does that on your system.

function segment(x, y, r, start, end, anticlockwise) {
  arc(x, y, r, start, end, anticlockwise)
  closePath()
}

Here’s a segment that goes from an angle of 2.5 to 4.5:

And a sector is an arc that is joined by line segments that go to the center of the circle. We can do that by executing a lineTo to the center point and then calling closePath

function sector(x, y, r, start, end, anticlockwise) {
  arc(x, y, r, start, end, anticlockwise)
  lineTo(x, y)
  closePath()
}

Here is a sector drawn with the same angles as the segment example:

Now you’re well on you way to making pie charts!

Polygons

Before we move on to ellipses, I want to give you one bonus function: regular polygons. This isn’t what I would normally think of as a curve, but mathematically, it might be. Anyway, it’s low hanging fruit, right there for the picking, so let’s do it.

When we were talking about resolution, we saw how a low resolution circle starts to look chunky. You can see the individual line segments that make it up. Well, we can push that bug even further and turn it into a feature.If we push the resolution so low that we only wind up drawing six segments in our circle (exactly six), we have a hexagon. Five create a pentagon, four a square and three a triangle. We just have to specify how many sides we want, and divide 2 * PI by that number to get the resolution that will make that shape.

Here’s one take:

function polygon(x, y, radius, sides) {
  res = PI * 2 / sides
  for (i = 0; i < PI * 2; i+= res) {
    lineTo(x + cos(i) * radius, y + sin(i) * radius)
  }
  closePath()
}

Now you can call it like:

polygon(300, 300, 250, 5)
stroke()

and get a pentagon like this:

You might want to specify an initial rotation, you can do that like so:

function polygon(x, y, radius, sides, rotation) {
  res = PI * 2 / sides
  for (i = 0; i < PI * 2; i+= res) {
    lineTo(x + cos(i + rotation) * radius, y + sin(i + rotation) * radius)
  }
  closePath()
}

Now you can say

polygon(300, 300, 250, 5, 0.5)
stroke()

and have the polygon rotated a bit.

Try it with different numbers of sides.

A fun effect is to create a series of polygons of different sizes, each slightly rotated.

angle = 0
for (r = 5; r <= 255; r += 10) {
  polygon(300, 300, r, 5, angle)
  stroke()
  angle += 0.05
}

This creates a nice pattern like so:

Might be a bit off-topic, but hey, there are five new emergent curves there! I’ll accept it.

Ellipses

Final bit of this installment, ellipses.

Well, let’s look at the definition of an ellipse, from Wikipedia…

a plane curve surrounding two focal points, such that for all points on the curve, the sum of the two distances to the focal points is a constant.

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

hmm… I get it, but doesn’t really help us to draw it. How about…

Ellipses are the closed type of conic section: a plane curve tracing the intersection of a cone with a plane

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

Nope. Let’s keep reading…

An ellipse may also be defined in terms of one focal point and a line outside the ellipse called the directrix: for all points on the ellipse, the ratio between the distance to the focus and the distance to the directrix is a constant.

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

OK, this is going nowhere. But as before, we can eventually find the parametric formula, which I’ve tweaked a bit to be similar to the one we had for the circle.

x = a + rx * cos(t)
y = b + ry * sin(t)

Here, in addition to the a and b that form the center position of the circle, we have rx and ry which I find easiest to think about as “radius x” and “radius y”, though these names will probably make mathematicians cringe. But for an un-rotated ellipse, rx will wind up being equal to half the ellipse’s width, and ry half its height.

So we can make a function:

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()
}

About the only thing worth mentioning here is that to get the resolution value, I divide 4.0 by the largest of the two “radii”. you might think of a better way, but this is good enough for me. Now you can call it like:

ellipse(300, 300, 250, 150)
stroke()

And get:

Bonus

Sometimes I can’t stop writing. This next part isn’t really so much about creating curves… or maybe it is. You decide. But rather than draw line segments between each point on a circle (or arc, or polygon, or ellipse), we could just draw some other shape there. We’ll have to increase the interval that we use to draw the curve so all the shapes don’t mash together. In fact, the polygon method is perfect for this. This lets us draw a circle with a set number of circles. I’m not even going to explain this code. You should get it.

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

cx = width / 2
cy = height / 2

res = PI * 2 / 20 // to draw 20 circles
for (t = 0; t < PI * 2; t += res) {
  x = cx + cos(t) * 200
  y = cy + sin(t) * 200
  circle(x, y, 20)
  stroke()
}

Which gives us:

Summary

I’m already elaborating on this in my head, but we’re off-topic enough, and this installment is long enough.

So far things have been pretty basic, but hopefully still interesting. From here on, they will get a bit more complex and hopefully even more interesting.

Coding Curves 02: Trig Curves

coding curves

Chapter 2 in the Coding Curves Series

This is going to be a relatively simple one, but we’ll get into a few different applications. I’m not going to take a deep dive into what trigonometry is, there will be no images of triangles with the little square in the corner telling you which angle is 90°. No definitions of adjacent, opposite, hypotenuse. And NO soh-cah-toa!!!

I’m going to assume you know all that stuff. And if you don’t, a google search for basics of trigonometry will net you about 18,800,000 results in under one second. Proof:

google search result for basics of trigonometry showing 18 million results

So if you have no idea what I’m talking about in that first paragraph, stop here and do some learning on that stuff first.

Wait, what am I doing? Totally ignoring an opportunity for shameless self-promotion on my own blog. OK, here’s a playlist of trigonometry videos by myself. The best material on the subject out of all of those 18 million results, or your money back.

Coding Math – Trigonometry Playlist

But we have to start somewhere…

OK, there are a few basic trig functions that your language should have somewhere in it. Maybe they are global, maybe part of a math library. For this post, we’ll be dealing with what you’ll probably find named sin, cos, and tan. These stand for sine, cosine and tangent, which you should know about or have just learned more about above.

Let’s start with sin. You pass it a number and it gives you a number back. If you give it 0.0, it returns 0.0. As you increase that argument incrementally, the result you get will slowly increase up to 1.0 – or almost 1.0 anyway. Then as you continue to increase it it will go back down to 0.0, then down to -1.0 and back up to 0.0. As you continue to increase the argument, the result will continue to oscillate between -1.0 and 1.0.

Try it out:

for (i = 0.0; i < 6.28; i += 0.1) {
  print(sin(i))
}

In your language, you might need println or some kind of console or log function to display values. But you should get some output that looks like:

output of sin - a long list of numbers going from 0 to 1 and back down

As I predicted, it starts at 0, goes up to 0.99957… and then starts going down again. Your output should continue going down to something like -0.999923… and then back up to almost 0.0.

Note that it did exactly one cycle of 0 to 1 to -1 to 0. Not a coincidence. It’s because the for loop ends at 6.28, which is approximately 2 * PI. Or 2 * 3.14… In most programming languages, the trig functions work on radians rather than degrees. This is another point I’ll assume you have some understanding of. But a radian is approximately 57 degrees. And 3.14… (PI) radians is 180 degrees. 6.28… (2*PI) radians is 360 degrees.

So the result of sin will go from 0 to 1 to -1 to 0 exactly one time from 0 to 360 degrees, or 0 to 6.28… radians.

If you change the for loop to end at PI (most languages have that as a constant somewhere) and the numbers should go from 0.0 up to 1.0, back to 0.0 and stop there. That’s half a cycle.

Bring on the Curves!

OK, let’s draw a sine wave. Size your drawing area to 800×500. It’ll also be helpful to set variables (or constants) to the width and height values. Then have a for loop with a variable x going from 0 to 800, using that width variable. Then we’ll take the sine of x and that will be our y value. We’ll add that to half the height so our sine wave will run along the center of the image. Draw a line segment to x, y and we’re all set.

Remember, this is all psuedocode. You’ll have to convert this to the language and drawing api of your choice. More details in the series intro.

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

for (x = 0; x < width; x++) {
  y = height / 2 + sin(x)
  lineTo(x, y)
}
stroke()

Depending on your drawing api, you might have to start with a moveTo(0, height/2) before you do the lineTos. When you’re done, you should have something like this image:

a sine wave with very small amplitude and many many waves.

That’s a sine wave, but maybe not quite what you would expect. There are two problems, which is good, because they both lead into the next two things we need to talk about: wavelength/frequency and amplitude. Here, there are too many waves – the wavelength is too short (or the frequency is too high), and the waves themselves are mere blips – the amplitude is too low.

Amplitude

The amplitude is easy enough to handle. The sin function returns from -1.0 to 1.0, so we just need to multiply that number by an amplitude value and we’ll be good. The height times something like 0.45 will make the wave just smaller than the size of the canvas.

width = 800
height = 500
amplitude = height * 0.45
canvas(width, height)

for (x = 0; x < width; x++) {
  y = height / 2 + sin(x) * amplitude
  lineTo(x, y)
}
stroke()
a chaotic looking sine wave that has too many waves to comprehend

That gives us height, but there are still too many waves so it’s hard to even comprehend this as a sine wave. We’ll fix that next.

Wavelength and Frequency

Wavelength is… wait for it… the length of a wave. Or the length between the same point on two consecutive waves. In the real world, these are actually physical distance measurements, with units anywhere from meters to nanometers, depending on what kind of wave it is you are measuring.

Another way of measuring waves is frequency – how many waves there are in a given interval (of space or time).

Wavelength and frequency are inversely related. A low wavelength (small distance between waves) equals a high frequency (many more waves in a given space). A high wavelength equals a low frequency.

You can write code to use either method of measurement. Let’s start with frequency.

Frequency

This is often specified as “cycles per second” (cps) rather than cycles in a given distance. Radio waves, for example, hit a receiver of some kind and we can just count how many hit it each second. The term “Hertz”, abbreviated “Hz” is the same as cycles per second. 100 Hz is 100 cycles per second. One kilohertz is 1000 cps, one megahertz is a million cps, etc.

Since we’re not dealing with moving waves though, it’ll be easier for us to measure frequency in terms of how many cycles occur in a given space. What you come up with here is up to your given application, but since we’re currently drawing a sine wave across an 800-pixel wide canvas, we can say that a frequency of one means that we should see one cycle occur as the wave goes across that canvas.

There’s a bit of math involved, so I’ll throw the pseudocode out there, then we’ll go through it.

width = 800
height = 500
amplitude = height * 0.45
freq = 1.0
canvas(width, height)

for (x = 0; x < width; x++) {
  y = height / 2 + sin(x / width * PI * 2 * freq) * amplitude
  lineTo(x, y)
}
stroke()

First we create a new variable, freq and set it to 1.0.

Then instead of just taking the sine of x, we divide x by width. This results in values from 0.0 to 1.0 as we move across the canvas.

Then we’ll multiply that by PI * 2. This will give us values from 0.0 to 6.28…, which should be familiar from the very first code example. This alone would make the results go from 0 to 1 to -1 and back to 0 exactly one time, as we saw. One cycle.

Finally, we multiply that by freq. That’s set to 1.0 now, so we’ll still get one cycle. With all this in place, you should be seeing this:

a single sine wave

OK, that’s more like it. But you might be more used to the sine wave going up first, then down. If you’re seeing it go down and then up, like in my example, it’s because your drawing surface has the y-axis oriented such that positive values go down, the reverse of Cartesian coordinates. If you know how to use the 2d transform methods of your drawing api, you can fix it that way. I’ll go for a simpler fix of just subtracting y from height in the lineTo call.

width = 800
height = 500
amplitude = height * 0.45
freq = 1.0
canvas(width, height)

for (x = 0; x < width; x++) {
  y = height / 2 + sin(x / width * PI * 2 * freq) * amplitude
  lineTo(x, height - y)
}
stroke()

And now we have:

a sine wave with more familiar orientation

Now you can mess with those two variables, amplitude and freq to create different waves. Here I set amplitude to 50 and freq to 5:

a sine wave with 5 cycles and a lower amplitude

Wavelength

Now let’s encode this to use wavelength instead of frequency. In this case we’ll be defining wavelength in terms of how many pixels it will take the wave to complete a full cycle. Let’s say we want one cycle to be 100 pixels long. Again, here’s the code, explanation to follow:

width = 800
height = 500
amplitude = height * 0.45
wavelength = 100
canvas(width, height)

for (x = 0; x < width; x++) {
  y = height / 2 + sin(x / wavelength * PI * 2) * amplitude
  lineTo(x, height - y)
}
stroke()

This time, inside the sin function call we divide x by wavelength. So for the first 100 pixels, we’ll get 0.0 to 1.0, then for the next 100 pixels 1.0 to 2.0, etc.

This gets multiplied by PI * 2, so for every 100 pixels we’ll get some multiple of 6.28… and will execute a complete wave cycle.

The result:

a sine wave with 8 cycles.

Since our canvas is 800px and the wavelength is 100px, we get eight full cycles, as expected.

Resolution

A quick word about resolution. Here, we are moving through the canvas in intervals of a single pixel per iteration, so our sine waves look pretty smooth. But it might be too much. If you are doing a lot of this and you want to limit the lines drawn, you can do so, with the possible loss of some resolution. We’ll just create a res variable and add that to x in the for loop and see what that does. We’ll start with a resolution of 10.

width = 800
height = 500
amplitude = height * 0.45
freq = 3
res = 10
canvas(width, height)

for (x = 0; x < width; x += res) {
  y = height / 2 + sin(x / width * PI * 2 * freq) * amplitude
  lineTo(x, height - y)
}
stroke()
a lower resolution sine wave

On the plus side, we are drawing only 10% of the lines we were drawing before. But you can already see that things have gotten a bit chunky. Going up to 20 for res reduces the lines drawn by half again. But now things are looking rough.

a very low resolution sine wave

But the important thing is knowing the options, their benefits and tradeoffs.

Cosine

I’m going to keep this one really quick. Because everything I’ve said about sine holds true for cosine, except that the whole cycle is shifted over a bit. Going back to a single cycle and simply swapping out cos for sin

width = 800
height = 500
amplitude = height * 0.45
freq = 1.0
canvas(width, height)

for (x = 0; x < width; x++) {
  y = height / 2 + cos(x / width * PI * 2 * freq) * amplitude
  lineTo(x, height - y)
}
stroke()
a single cosine wave

Here, an input of 0.0 gives us 1.0. And then we drop to 0.0, -1.0, back through 0.0 and end on 1.0 as we move from inputs of 0.0 to 2 * PI. It’s the same wave shifted over 90 degrees (or PI / 2 radians). It’s easier to see if we move the frequency up a bit.

4 cycles of a cosine wave

I don’t really have a whole lot to say about cosine waves for this post. But we’ll be revisiting them again later in the series.

A Function

We can use everything we’ve covered so far to make a reusable function that draws a sine wave between two points. Again, I’ll do the pseudocode drop and then the explanation.

function sinewave(x0, y0, x1, y1, freq, amp) {
	dx = x1 - x0
	dy = y1 - y0
	dist = sqrt(dx*dx + dy*dy)
	angle = atan2(dy, dx)
	push()
	translate(x0, y0)
	rotate(angle)
	for (x = 0.0; x < dist; x++) {
		y = sin(x / dist * freq * PI * 2) * amp
		lineTo(x, -y)
	}
	stroke()
	pop()
}

This function has six parameters, the x and y coords of the start and end points, frequency and amplitude.

We get the distance between the two points on each axis as dx and dy.

Then, using these, we calculate the distance between the two points using the Pythagorean theorem and the sqrt function which should be somewhere in your language.

And we get the angle between the two points using the atan2 function that should also be available. If you don’t understand what’s happening so far, I suggest you go look at the references at the beginning of the post.

Now this function is assuming that your drawing api has some 2d transformation methods. If so, it probably also has a way to push and pop transforms from a stack. In HTML’s Canvas api for example, these would be context.save() and context.restore().

We want to push the current transform to the stack so we can transform the canvas, do our drawing, and then restore the earlier state when we are done. So we call push.

Then we translate to the first point and rotate to the angle we just calculated. At this point we just need to draw a sine wave using dist, freq and amp exactly as we’ve been doing.

Since our canvas is translated exactly as we want it, we can just call lineTo(x, -y) which just corrects the sine wave like we did before.

When we’re done, we stroke that path and call pop (or restore or whatever your api uses) to leave things how we found them.

We can now use this function like so:

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

sinewave(100, 100, 700, 400, 10, 40)

This draws a sine wave starting at 100, 100, going to 700, 400, with a frequency of 10 and amplitude of 40.

a sine wave going diagonally from one point to another

If your drawing api does not have transform methods, I pity you. You can still do this kind of thing, but it will be much more complex. And beyond the scope of this post.

As mentioned in the comments below, the “frequency” aspect of this method might be a bit confusing, because the way it’s coded “frequency” means “number of cycles between point A and B”. So a long wave drawn with this method would have a larger wavelength than another, shorter line drawn with the same frequency. To address this, you might want to change the function to use wavelength instead. This would ensure that all waves drawn with the same wavelength would look similar, no matter how long they were. Another option would be to adopt a frame of reference for frequency, like “so many cycles per 1000 pixels” or something. What you do here depends on your needs and application, so I’ll leave that up to you.

Tangent

Drawing tangent curves is not nearly as useful as sine and cosine waves, but we’ll cover it for completeness.

Unlike the two functions we’ve covered so far, tangent does not constrain itself to the range of -1 to 1. In fact, it’s range is infinite. Let’s do the same thing we did for sin and just trace out the values.

for (i = 0.0; i < 6.28; i += 0.1) {
  print(tan(i))
}

You’ll get something like this:

a list of numbers showing the value of tangent

Again we start at 0, but quickly go up to 14, then jump to -34. But that’s deceiving. The values are truncated because we are moving in relatively large steps of 0.1. What is actually happening is that we are rapidly going up to positive infinity as the input angle approaches PI / 2 radians (or 90 degrees). Once it crosses that value, it jumps towards negative infinity, swiftly rises to 0.0 again at PI radians (180 degrees) and then repeats the cycle again once more before it gets to 2 * PI radians (360 degrees). This isn’t a linear progression though – it executes a curve as it approaches and leaves 0. Let’s code it up using frequency to see a single tangent wave cycle.

width = 800
height = 500
amplitude = 10
freq = 1.0
canvas(width, height)

for (x = 0; x < width; x++) {
  y = height / 2 + tan(x / width * PI * 2 * freq) * amplitude
  lineTo(x, height - y)
}
stroke()

Same thing here again, but swapped out tan for sin. I also reduced the amplitude just so we could see the curve better. In fact, it’s probably a misnomer to talk about the amplitude of a tangent wave since its amplitude is actually infinite. But this value does affect the shape of the curve.

a tangent wave

One thing to note is those near-vertical lines that shoot down from positive to negative infinity. Those aren’t technically part of the plot of the tangent. Mathematically, it implies that the tangent could have a value somewhere along that line at some point, but it will not. It is actually undefined at an angle of 90 and 270 degrees. And near infinity/negative infinity just before and after those values.

Not sure if this one will be of much use to you, but there it is.

Summary

Well, we got through the first few curves in this series. Good ones to have under the belt as a lot of other curves will uses these functions in one way or another. See you soon!

Coding Curves 01

coding curves

Background

For a number of years, I’ve been wanting to writing a book called “Coding Curves”. There are all kinds of really fascinating curves that are fun to code and understand and create very interesting images. I’ve started this book at least three times. I’m feeling the urge to to it again, but I’m going to be more realistic about it this time. I don’t think I’m going to sit down and write a book cover to cover and get it formatted and edited and self publish it. I’ve accepted that that’s just not going to happen. But I want to write about this stuff.

The Plan

So I’m going to do it as a series of blog posts. I don’t know how often these posts will come out. Maybe once a week if I’m diligent. Maybe more often if I get on a roll. Maybe less if I get bored, distracted, or busy with other things. But even if there’s a gap, I can pick back up where I left off and continue.

Maybe, when it’s all done (will it ever be?) there will be enough there to actually compile into a book. Who knows.

Update (2/19/23)

It’s “done”. Which is not to say that I’ll never add to it, but I’ve covered what I set out to cover initially, and I’m pretty happy with it. I may eventually compile it all into an ebook that people can download as a single file and read like that. Probably under a pay-what-you-want type system. But for now, other projects await.

Table of Contents

This is the plan. It may differ, but we gotta start somewhere. I’ll link the posts as they come out here, so this post can continue to serve as an index.

  1. Intro (you’re reading it)
  2. Trig Curves
  3. Arcs, Circles, Ellipses
  4. Lissajous Curves
  5. Harmonographs
  6. Pintographs
  7. Parabolas
  8. Bezier curves
  9. Roulette curves
  10. Spirals
  11. Roses
  12. Guilloche Patterns
  13. Superellipses and Superformulas
  14. Miscellaneous Curve

In general we’ll be covering two-dimensional curves. Some of these subjects will take multiple posts to cover. I’ll update the TOC as it evolves. There may be more subjects too, and I’m open to suggestions.

The Audience and Purpose

The audience for this course is people who are interested in exploring different types of curves and want practical techniques for drawing them. It may be artists, designers, game developers, UI developers, or recreational mathematicians. This is NOT a mathematics course, though there will be plenty of math in it. A lot of the explanations I give will be very incomplete. There will hopefully be enough information in the materials to give you the understanding you need to draw the given curves and expand on them. But at best, each chapter will be a mere introduction to a topic. Hopefully nothing will be straight out incorrect, but I’m happy to hear about it if there is, and will make my best efforts to correct it.

Personally, I love finding some new mathematical formula that I can apply to create some new, interesting shape, then tweaking the parameters to see how it changes that shape, and eventually animating those parameters. And finally combining the formula for one curve with the formula for another one and possibly making some shape that nobody has every seen before. If any of that rings true for you too, you will probably enjoy reading through this.

The Format

Most of what I’ve written before has been tailored to a specific language. For example, I wrote Playing With Chaos using JavaScript and HTML 5 Canvas. The idea was that it should be generic enough to convert to any language or platform, but I felt I needed to base it in some concrete language.

After recently reading a couple of really fantastic books on ray tracing (covered here), which were completely language agnostic and only included pseudocode samples, I’ve changed my thinking about this. So all “code samples” in this series will actually be “pseudocode samples”.

So write in whatever language or platform you want. The main requirements will be:

A. The ability to set up and size a canvas or drawing surface of some kind.

B. The ability to draw lines of different colors and widths on said canvas.

C. Typical language features and control structures like functions, variables, for loops, conditionals, etc. We’ll probably need some kind of array or list as well as some structured object type, so we can have a point object that has x and y properties. Most of this is all table stakes for any modern language, though it may look different in each one.

Additionally, it will be nice if you can draw circles (or at least arcs, which can become circles) and rectangles, and be able to fill an area with a given color. Extra credit if your drawing api lets you write text to the canvas.

A simple pseudocode example might look like this:

canvas(800, 800)

setLineWidth(5)
setColor(1, 0, 0) // red
moveTo(100, 100)
lineTo(700, 700)
stroke()

This should output an image like this:

I’ll avoid going as deep as OOP or FP as those can look quite different on different platforms, but we’ll need some functions, so I’ll keep those pretty basic:

canvas(500, 100)
foo(100)

function foo(count) {
  for (i = 0; i < count; i++) {
    moveTo(i * 10, 10)
    lineTo(i * 10, 90)
  }
  stroke()
}

Resulting in:

A few things to note here.

First, I’m calling the function before it’s defined. That might work in your language, or it might not. If not, you’ll have to rearrange things.

Secondly, the drawing api functions are written like global functions. It’s likely that in your drawing api, these will need to be called as methods of some kind of canvas object, which may have to be passed into the function, or might be able to be defined globally. I’m going to ignore that and assume that you can figure stuff like that out. The focus here is on covering concepts, not best coding practices.

Also, I’m not bothering with types for vars, args or returns, unless in some instance it becomes non-obvious and important.

As I get deeper into the series, my pseudocode style might change somewhat. If so, I’ll come back here and update things.

Wait for it!

Look for the first installment soon!