I created this page for myself because I was using several different axes and angle systems and wanted to keep track of the formulas.
1 Axes#
In math, we typically have X pointing right and Y pointing up. But in 2D graphics for computer screens, including SVG and HTML5 Canvas, we usually have Y pointing down:
(Historical note: we use Y down because that’s how CRT electron guns scanned the lines for TVs. But IBM OS/2 used Y up for its 2D graphics!)
When working with 3D you have to be extra careful about axes. Both DirectX and OpenGL have Y pointing up, but DirectX and OpenGL textures have Y pointing down. DirectX has a Z axis pointing away from the screen and OpenGL has a Z axis pointing towards the screen. Unity uses Y pointing up for graphics and Y pointing down for UI elements.
2 Angles#
In math we have a standard angle system where 0° is east, 90° north, and angles grow counterclockwise. But in navigation we have a compass angle system where 0° is north, 90° east, and angles grow clockwise. And you might also have a math angle system flipped upside down to match the screen.
Either way, angles are a little tricky because of the wraparound. Unit vectors are often nicer. But when working with angles I use separate functions to tell me how far I have to turn left vs how far I have to turn right. Note that you may have to reverse the names depending on which coordinate system you use.
function mod(value, modulo) { return ((value % modulo) + modulo) % modulo } function degreesLeft(startDeg, endDeg) { return mod(endDeg - startDeg, 360) } function degreesRight(startDeg, endDeg) { return mod(startDeg - endDeg, 360) } function degreesApart(startDeg, endDeg) { return Math.min(degreesLeft(startDeg, endDeg), degreesRight(startDeg, endDeg)) } function test(expr, expected) { console.log(expr, "=", eval(expr), ", should be", expected) } test("degreesLeft(350, 10)", 20) test("degreesLeft(10, 350)", 340) test("degreesRight(350, 10)", 340) test("degreesRight(10, 350)", 20) test("degreesApart(10, 10)", 0) test("degreesApart(10, 350)", 20) test("degreesApart(10, 90)", 80) test("degreesApart(10, 190)", 180) test("degreesApart(10, 40)", 30)
There’s a shorter way to express the non-directional version on this stackoverflow answer[1].
function mod(value, modulo) { return ((value % modulo) + modulo) % modulo } function degreesApart(startDeg, endDeg) { let diff = mod(endDeg - startDeg, 360) return 180 - Math.abs(Math.abs(diff) - 180) } function test(expr, expected) { console.log(expr, "=", eval(expr), ", should be", expected) } test("degreesApart(10, 10)", 0) test("degreesApart(10, 350)", 20) test("degreesApart(10, 90)", 80) test("degreesApart(10, 190)", 180) test("degreesApart(10, 40)", 30)
3 Trigonometry#
These rules are independent of the coordinate systems you’re using:
θ = atan2(A, B) A = sin(θ) B = cos(θ)
They always have to match up; it’s a nice way to double check consistency.
Axes | Angles | ||
---|---|---|---|
math y-up | screen y-down | compass | |
math y-up | θ = atan2(y, x) y = sin(θ) x = cos(θ) | θ = atan2(-y, x) y = -sin(θ) x = cos(θ) | θ = atan2(x, y) x = sin(θ) y = cos(θ) |
screen y-down | θ = atan2(-y, x) y = -sin(θ) x = cos(θ) | θ = atan2(y, x) y = sin(θ) x = cos(θ) | θ = atan2(x, -y) x = sin(θ) y = -cos(θ) |
(Is this right? need to double check)
I find it easiest to remember things when θ = atan2(y, x), either math angles + math axes, or screen angles + screen axes, but sometimes I need to use another system for the project.