“cos() and sin() take 0..1 instead of 0..PI*2”
-the Pico-8 manual
In Pico-8, the trigonometry functions (cos(), sin() and atan2()) use turn-based angles. That means a “full” angle, which would be 2π radians or 360°, has a value of 1. If you’ve never used Pico-8 before or if you’re new to it, you might be asking yourself “uh ok but why?” and that’s what today’s Doodle Insights is about!
How good is this turn-based system? Why do we use degrees and radians anyway? Should we dump all our degrees and radians for turns? Is it really easier this way? Does it allow us to do amazing things more simply? And if it is that good, what’s the catch?
Here’s my first argument in favor of turn-based angles:
Hopefully that also clears up any confusion about what I mean by turn-based angles.
I’m assuming we’ve all seen angles in degrees in elementary school, so the first circle shouldn’t shock anyone. A complete turn is 360°, half-a-turn is half of that, 180°. But why though? Why 360? What does it even mean?? I literally just looked it up and here’s what Wikipedia has to say about it:
“The original motivation for choosing the degree as a unit of rotations and angles is unknown.”
WHAT? This is the angle unit being taught to kids in schools and no-one knows why we even use it? That Wikipedia article also says it could be because there’s approximately 360 days in a year and the Greeks loved astronomy or something or maybe it was because of an ancestral numeral system or maybe it was just because 360 is a super divisible number. Ok, that ancestral numeral system is somewhat interesting but not a valid reason for keeping degrees and that thing about being divisible number could be an acceptable reason to teach it in schools I guess. But is it though?
Moving on to the second circle! I understand the main reason radians are used is because they’re necessary to express arc lengths. Basically, measuring the length of an arc with maths is a bit of a mind-fuck and we need this π constant to make it work. In the 18th century, some very serious people found out that measuring angles with arc lengths was a good idea and the radian was created in concept and then in form in the 19th century. Terrific.
The Wikipedia article also has a part about the advantages of using radians and they say it’s “more natural”. Except the exemples they give don’t look any kind of natural to me, so I assume it really works for more complex maths. Cool, we’re not doing any of that today.
Third circle now! Please just take a moment to look at this circle and try to think of a simpler way to write angles, aside maybe from using little angle drawings.
I think we can agree that talking of a quarter of a turn (0.25) is much more intuitive than saying 90 degrees or π/2 radians for any simple human being. You don’t need any mathematical knowledge to look at an angle and say that it’s “that much” of a full turn.
Turn-based angles are more intuitive than degrees and radians.
The reason we’re discussing this today is that angles are omnipresent in graphics programming. (and also in gameplay programming) And if there’s any way to make my programming simpler with little to no sacrifice, I’ll take it.
In this doodle, the sparks go in random directions. Their movement is defined by vx and vy values, which are themselves based on the cos() and sin() of a random angle. Where I would have ‘a=rnd(360)’ or ‘a=rnd(PI_2)’, in Pico-8 I have ‘a=rnd(1)’. No need for a constant, no need for what looks suspiciously like a magic number. Call me picky but ‘a=rnd(1)’ is cleaner. (and it makes more sense too, we’re just choosing a random portion of a full turn)
I wanted this doodle to produce a perfect loop gif. Everything had to be in sync and everything had to be back at its exact starting position after a certain number of frames.
To achieve this, the position of every sphere, relative to the spheres they orbit around, is calculated from a same variable t. t is a variable set to 0 in the _init() and incremented by 0.01 in the _update(). 0.01 because 1 would send us above the higher Pico-8 number possible (32768) in a few minutes, and because 0.001 gets super imprecise inside Pico-8 for reasons unknown to me. (0.01 gets imprecise too but much less, it’s still usable) It also means that t, as a turn-based angle, makes a full turn in 100 frames (that’s a bit over 3 seconds at 30fps) which is kinda slow but a good base nonetheless.
This t variable is in almost everything I make, game or doodle, with this exact behaviour, and I could probably write an entire Doodle Insights just about it. It’s very handy.
In this case, each sphere is an object in the list of spheres, with parameters defining its orbit and two parameters defining its current progress in this orbit based on t: a multiplier s.mt and an addend s.kt. Considering the orbits are just circles, this progress is just an angle, calculated as ‘a=s.mt*t+s.kt’. To make sure we can have our perfect loop, s.mt can only be one of 15 possibilities that work with a loop of 2000 frames.
If we had degrees or radians there, we would probably stick them to this angle after calculating it with a ‘a=a*360’ or ‘a=a*PI_2’. Otherwise, including them in the calculating process or in the parameters would make it much messier and there would be a higher risk of imprecision. I want a perfect loop, that means the 2001st frame should be the exact same than the 1st frame, to the pixel. Working only with turns here is not only appropriate (as we’re talking about orbits), it’s more precise and clean.
In this interactive doodle, the shapes are being drawn with splines. Yet the data for the points originally isn’t in the [x;y] form as you would expect, but as [l;a]. Before drawing the spline, we make a new array of points where we have ‘x=l*cos(a+t)’ and ‘y=l*sin(a+t)’ and that lets us rotate the shapes super easily. It’s definitely not the most performance-friendly way to do it but it’s easy and it works.
This [l;a] data is defined for each shape in a super ugly function called in the _init(). (again, not the best way to do it) Each shape has its own bit of code to generate the data, sometimes with maths formulas and sometimes with fancier stuff. My point is that these bits of code weren’t always working as intended and I had to debug them.
For that, I had to check on the [l;a] data to see what’s going on. Imagine having to interpret angles in radians to debug this. Seriously, imagine yourself taking numbers in radians and getting anything out of them. Even with degrees, although easier, it would still require some brain work. With turn-based angles though: “oh hey that’s not the right quarter” or “wait, that’s the symmetric of what it should be, wtf”.
Turn-based angles are easier to interpret as data.
Chances are you’ve already had to figure out the reflection of an angle. Maybe you did it so often that you barely even have to think about it when doing it. But the first few times, it’s easy to get it wrong. Here is the one formula that works with every angle:
--oa is original angle --sa is reflective angle --na is reflected angle na=-(oa-sa)+sa
That actually works with any type of angle, degree, radian, turn-based or anything else. It goes to show that the angle system you use doesn’t really matter since it can be abstracted out. Yet, if you start swapping these variables for numbers, that’s where the difference comes. With degrees and radians you will always have everything revolve around 360 and 2π, two values generally irrelevant to what we want to code.
In fact, when I used to animate radian angles with code, I would generally calculate it as a portion of a full angle and then I would multiplicate it by 2π at the end.
Once again, it feels more natural to think in portions of full angles and it is definitely more elegant in the code.
Before we come to a conclusion, I have to say that I do not know how computers compute trigonometry. I did some research but my maths class didn’t go far enough for me to understand what I found. So I guess it is possible that using angles in radians is easier for computers and makes for faster computing. Maybe, I really have no idea.
Aside from that, it can be said that radians are “scientifically correct”, to which I say “fuck that”. Honestly, nobody cares if your code is scientifically correct appart from yourself and maybe the other people you code with. Gather them around, have a debate about angles and scientific correctness and you’ll be fixed.
Again, it’s very possible that the reasons for using radians go beyond my knowledge, but I think that means they’re useless for our everyday coding needs. And nothing can be said in the defense of the degrees, they’re worthless.
The one good reason to keep these angle systems is that you’re used to them and they feel natural to you. They feel natural to me too. But they don’t make my coding easier.
TURN-BASED ANGLES ARE THE FUTURE.
There, I said it. They’re more intuitive, they make for cleaner angle-related code, they’re easier to interpret, they make for cleaner angle algebra, they let us get away without using mysterious numbers like 360 or the irrational number π. They’re more elegant and you’re already thinking with them when dealing with angles.
On the other hand, degrees are used for long-forgotten reasons and radians are used for reasons beyond the use of the common human and for scientific correctness. Both are awkward to use until you get used to them.
Time to move on, folks.
Some people have been discussing this article and I thought I would update it with some of the interesting points I saw!
The first thing is Tau! If like me you had never heard of it, it’s a super handy constant which has the value τ=6.283185… or, as I like to write it, τ=π*2. While it’s true that with this constant, angles can be expressed as turn-based times τ, we still have to deal with a constant. However, it would really work if “tau radians” was considered as a unit, as that would leave us only the turns for the number part.
Someone has been argumenting that cos() and sin() were mathematical functions and that you can’t just modify them. That would just be wrong maths.
While this ultimately comes back to scientific correctness, which we previously discussed, I have to say I see the truth in this argument and it did make me feel less confortable with all this. But then again, “cos” and “sin” are just function names. So you could either rename the turn-based-angle cos() and sin(), or you could embrace the philosophy of the Lua language which lets you override and rename functions whenever you want and just override cos() and sin().
Still on the subject of maths, Darren Moore said that trigonometry derivatives get slightly more complicated to compute, with sin(x) = cos(x)*2pi. I don’t really use derivatives myself but this is totally a fair point. Here is the demonstration he kindly provided!
About computing sin() and cos(), I was told computers used polynomials and most notably the Taylor series! (look it up if you’re interested and not numerophobic)
I was also told that it was probably easier to compute with radians but my understanding is that adapting it would not (or barely) be consequential.
Finally, Pico-8 numbers are all fixed point values and not floating point which allows for more precision by using scientific notations! Pico-8 does not use scientific notations and so the numbers are limited in range and precision by the number of bits they are set in, which is 32. (I think)
Furthermore, Zep himself has tweeted that Pico-8 was using this to store the pre-computed results of the cos() and sin() functions!
End of the update!
That’s it for the Doodle Insights #17! It was a bit different than usual but I hope you liked it! If you have any questions or remarks, do ask them either here, on the Patreon or directly to me on Twitter! And if you think I’m wrong, I’d love to read your arguments!
Next week we’ll probably talk about logic data generation for a more regular kind of Doodle Insights. But maybe I’ll change my mind!
I want to thank my awesome Patreon patrons for supporting this series, among the rest of my works! Here are the names of all my 3$+ supporters!
Joseph White, Adam M. Smith, Ryan Malm, Goose Ninja, Matthew, Giles Graham, Luke Davies, Jake Meanwell, Tim and Alexandra, Sasha Bilton, berkfrei, Nick Hughes, Christopher Mayfield, Jearl, Dave Hoffman, Thomas Wright, Morgan Jensen, Zach Bracken, Cole Smith, Marty Kovach, GucioDevs, Corey O’Connor, nanoplink, Anne Le Clech, Flo Devaux, Brent Werness, Ian Fare, babyjeans, Emerson Smith, Cathal O’Keeffe, Dan Sanderson, Andrew Reist, vaporstack, Dzozef, Tony Sarkees, Justin TerAvest and Vorzam!
If you enjoy the things I do, please consider supporting me on Patreon. It’s currently my only source of income and I could really use your help. Besides, there’s a bunch of cool rewards and even just one dollar gets you exclusive content now!
Thank you for reading and please try using turn-based angles!