An Isometric Primer: coordinates

I thought it would be useful to write about coordinates for isometric games. Disclaimer: I have a degree in maths. You might not. Let me know if anything needs explaining more fully.

Firstly, grab a copy of my new game. It will help understand what I’m talking about. You might even enjoy it.

Ok let’s begin.

We want to describe a 3D world, and project that into 2D. This is a very common thing to do. 3D games do this all the time, using 3- and 4-dimensional vectors to store world-space coordinates and then use matrix transformations to project these onto the screen.

For now, let’s not worry about matrices and see what we can do with some simple geometry.

Let’s call our world-space coordinates (in any arbitrary units, though I use one unit per block) (u,v,w) and our screen space coordinates (in pixels) (x,y).
20151101_172042 - Copy (3)

So the points on our basic block, positioned at (u,v,w), would be:

Isometric projection means that 1 unit along any of the world space axes looks the same distance in screen space. This means that there is 360^{\circ} \div 3 = 120^{\circ} between each of the axes.

But in games, we don’t use actual isometric projection. We use something close, and call it isometric anyway. We want nice smooth lines so that for each 2 pixels along, we go 1 pixel up or down. This gives a line at angle \arctan(2)\approx 64^{\circ} to the vertical, instead of 60^{\circ}. Notice I’ve called this angle \theta.

Though it is not true isometric projection, we probably do want a true rotation for it to look realistic. Later we will work out the correct value for h, given s. (Spoiler: it’s h=\sqrt{6}s \approx 1.25 s. This is not possible to get exactly right with pixels, so I’ve found s=8 pixels, h=20 pixels works well).
20151101_172042 - Copy

Taking a closer look, we can work out a few things we’ll need later:
20151101_172106 (1) - Copy

\cos{\theta} = s/\alpha, \sin{\theta} = 2s/\alpha

For now, let’s work with w=0 so that all points lie in a plane. Consider some point on the screen with coordinates (x,y).

We find y by projecting parallel to the y-axis from the point until we hit the x-axis. Then the distance along is x.
20151101_172042 - Copy (2)

We can do something similar to find u and v, but bearing in mind (a) these axes are scaled by \alpha on the screen and (b) we are below the u-axis, so v is negative.
20151101_172106 (1)

Putting these two diagrams together, it suddenly becomes easy to find x and y in terms of u and v.

Clearly we have

    \[ \begin{aligned} x &= (\alpha u \sin{\theta})+(-\alpha v \sin{\theta}) \\ &=\alpha \sin{\theta} (u-v) \\ &=2s(u-v) \end{aligned} \]


    \[ \begin{aligned} y &= (-\alpha v \cos{\theta}) - (\alpha u \cos{\theta}) \\ &=-\alpha \cos{\theta} (u+v) \\ &= -s(u+v) \end{aligned} \]

But if you think about it, it’s pretty clear that increasing w by 1 will just decrease y by h. So we have our final coordinate projection of:

    \[ x = 2s(u-v) \]

    \[ y = -s(u+v) - hw \]

That was fairly straightforward.

Now comes the trickier part: working out depth.

Depth should be thought of as the distance to the camera, and it is particularly important so that we can order the drawing of sprites.

Essentially, depth is a third coordinate to store the extra information lost when when converting 3D (u,v,w) down into 2D (x,y). Let’s call depth z.

Now we’re going to need to use matrices. We want to find a matrix \mathbb M such that

    \[ \left( \begin{array}{c} x \\ y \\ z \end{array} \right) = \mathbb M \left( \begin{array}{c} u \\ v \\ w \end{array} \right) \]

But we already know most of it, we only need to find m_{31}, m_{32} and m_{33}:

    \[ \mathbb M = \left( \begin{array}{ccc} 2s & -2s & 0 \\ -s & -s & -h \\ m_{31} & m_{32} & m_{33} \end{array} \right) \]

This transformation should be a simple rotation of the axes, plus a scaling by some factor \beta. This means that \mathbb M / \beta is an orthogonal matrix with determinant +1. Lost me yet? If you have, don’t worry, you can just use the result we find.

So we need

    \[ \begin{aligned} &\left( \begin{array}{ccc} \beta^2 & 0 & 0 \\ 0 & \beta^2 & 0 \\ 0 & 0 & \beta^2 \end{array} \right) = \mathbb M \mathbb M^T \\ &= \left( \begin{array}{ccc} 8s^2 & 0 & 2s(m_{31}-m_{32} \\ 0 & 2s^2+h^2 & -(m_{31}+m_{32})s-m_{33}h \\ 2s(m_{31}-m_{32} & -(m_{31}+m_{32})s-m_{33}h & m_{31}^2+m_{32}^2+m_{33}^2 \end{array} \right) \end{aligned} \]

So we have \beta^2 = 8s^2 and \beta^2 = 2s^2+h^2, so h=\sqrt{6} s as foretold.

Also, 0 = 2s(m_{31}-m_{32}) so m_{31}=m_{32}, and 0=-(m_{31}+m_{32})s-m_{33}h so m_{33}=-2sm_{31}/h.

Finally, \beta^2 = m_{31}^2+m_{32}^2+m_{33}^2 so 8s^2 = \frac{4s^2+2h^2}{h^2} m_{31}^2 = \frac{8}{3} m_{31}^2, using h^2 = 6s^2.

So we have, taking positive square root for determinant +1, m_{31} = m_{32} = \sqrt{3} s, m_{33} = -\sqrt{2} s.

This gives:

    \[ z = \sqrt{3}s(u+v) - \sqrt{2}sw$ \]

along with:

    \[ x = 2s(u-v) \]

    \[ y = -s(u+v) - \sqrt{6}sw \]

You should now be able to use these formulae to draw depth-sorted sprites in isometric space. But be warned: sorting is not this simple when the bounding boxes of objects overlap in 3D space. For that, you’ll need to use depth buffers.

I will probably write another post soon about collisions in isometric games.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *