Axes and Angles

 from Red Blob Games
25 Sep 2019

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:

+x-x+y-y0,0math+x-x-y+y0,0screen

(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.

180°90°270°math y-up180°270°90°screen y-down90°270°180°compass

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.

AxesAngles
math y-upscreen y-downcompass
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.

Email me , or tweet @redblobgames, or comment: