Pixelate:Issue 12/Isometric Projection

From Allegro Wiki

Jump to: navigation, search

== Isometric Projection == ~ JD Marble (aka MidoriKid)


Most three dimensional computer games use either perspective or isometric projection. With perspective projection objects appear smaller the farther away they are, just like in real life. Games like Quake use this method. In isometric all objects appear the same size no matter what their distance from the observer is. You can see isometric projections in games like Simcity and Final Fantasy Tactics. So why would anyone want to create a 3D game using the much less realistic isometric projection? It's easier to program, faster for the computer to calculate, and more convenient for me to explain. Hopefully the following will teach you how to use Allegro to draw models or sprites in a three dimensional world.

Before we get to the fancy models and sprites, we should start with something easier like a single point. We want to turn a three dimensional point (X,Y,Z) into a two dimensional point on the screen (X,Y). We could simply drop the Z coordinate and get a perfect isometric projection looking down straight from the top, but we want to be able to rotate around and look at the scene from different directions. Let's start with 2D rotation. Anyone familiar with simple trigonometry knows how to rotate a vector around its origin.

new.X = cos(angle) * old.X - sin(angle) * old.Y
new.Y = sin(angle) * old.X + cos(angle) * old.Y


Image:Rotatedline.GIF figure 1. Rotated row with measurements


Say we rotate a regularly spaced row of points clock-wise by 10 degrees. If we then measure the vertical and horizontal distance between each point, we'll find that they are the same from point to point. This can be used to optimize the rotation of multiple points so we can get rid of all those nasty and slow cos() and sin() calls. All we need to do is figure out the rotated X,Y coordinates of the point (1,0). We'll call this the "right" vector. To find the rotated coordinates of any point along the X axis we simply multiply it's starting X with the X of the right vector to find the new X and the Y of the right vector to find the new Y. This is fine for all points on the X-axis, but what about the second dimension? All we need to do is rotate the point (0,1) to find the "front" vector and do the same thing we did with the right vector. Add the two Xs and Ys together to find your new rotated point. Now as long as you rotate by the same angle all it takes is a few multiplies and adds to rotate as many points as you need. The front and right vectors essentially form a rotation matrix, but we won't get into that here.


Image:Rotatedgrid.GIF figure 2. Rotated grid


//precalculated vectors
right.X = cos(angle) * 1 - sin(angle) * 0
right.Y = sin(angle) * 1 + cos(angle) * 0
front.X = cos(angle) * 0 - sin(angle) * 1
front.Y = sin(angle) * 0 + cos(angle) * 1

/* Note- This can be reduced to:
 *right.X = cos(angle)
 *right.Y = sin(angle)
 *front.X = sin(angle)
 *front.Y = -cos(angle)
 */
//rotate quickly
new.X = old.X * right.X + old.Y * front.X
new.Y = old.X * right.Y + old.Y * front.Y

Now to view this plane of points isometrically we need to calculate the rotation vectors with a second angle. We'll call the first angle "rotation" and the second "elevation". This is a simplified 3D rotation that only really scales the Y value by the cosine of the elevation. You can get fancy with real 3D rotation matrices, but that's not necessary for a simple isometric game.

//precalculated vectors
right.X = cos(rotation)
right.Y = sin(rotation) * cos(elevation)
front.X = sin(rotation)
front.Y = -cos(rotation) * cos(elevation)

Image:Isometricgrid.GIF figure 3. Isometricly projected grid

Now we can draw a plane isometrically, but what about 3D objects? To do that we need another vector that I call the "up" vector. It works exactly the same as the right and front vectors but is multiplied by the Z value to help find the screen coordinates. Assuming the camera never spins around its axis so up is pointing somewhere other than up, we can leave up.X as 0.

//precalculated vectors
right.X = cos(rotation)
right.Y = sin(rotation) * cos(elevation)
front.X = sin(rotation)
front.Y = -cos(rotation) * cos(elevation)
up.X = 0
up.Y = sin(elevation)

//rotate quickly in 3D!
2D.X = 3D.X * right.X + 3D.Y * front.X + 3D.Z * up.X
2D.Y = 3D.X * right.Y + 3D.Y * front.Y + 3D.Z * up.Y

Image:Isometriccube.GIF figure 4. Isometricly projected cube

Of course, nothing is stopping up from rotating around the camera's axis. It's easy to apply a simple 2D rotation to all three vectors. We'll call the new angle "spin".

//precalculated vectors
oldright.X = cos(rotation)
oldright.Y = sin(rotation) * cos(elevation)
oldfront.X = sin(rotation)
oldfront.Y = -cos(rotation) * cos(elevation)
oldup.X = 0
oldup.Y = sin(elevation)

right.X = cos(spin) * oldright.X - sin(spin) * oldright.Y
right.Y = sin(spin) * oldright.X + cos(spin) * oldright.Y
front.X = cos(spin) * oldfront.X - sin(spin) * oldfront.Y
front.Y = sin(spin) * oldfront.X + cos(spin) * oldfront.Y
up.X = cos(spin) * oldup.X - sin(spin) * oldup.Y
up.Y = sin(spin) * oldup.X + cos(spin) * oldup.Y

Which can be squashed into:

//precalculated vectors
right.X = cos(spin) * cos(rotation) - sin(spin) * sin(rotation) * cos(elevation)
right.Y = sin(spin) * cos(rotation) + cos(spin) * sin(rotation) * cos(elevation)
front.X = cos(spin) * sin(rotation) - sin(spin) * -cos(rotation) * cos(elevation)
front.Y = sin(spin) * sin(rotation) + cos(spin) * -cos(rotation) * cos(elevation)
up.X = -sin(spin) * sin(elevation)
up.Y = cos(spin) * sin(elevation)

Why not scale all the vectors to create a "zoom" feature?

//precalculated vectors
right.X = (cos(spin) * cos(rotation) - sin(spin) * sin(rotation) * cos(elevation)) * zoom
right.Y = (sin(spin) * cos(rotation) + cos(spin) * sin(rotation) * cos(elevation)) * zoom
front.X = (cos(spin) * sin(rotation) - sin(spin) * -cos(rotation) * cos(elevation)) * zoom
front.Y = (sin(spin) * sin(rotation) + cos(spin) * -cos(rotation) * cos(elevation)) * zoom
up.X = (-sin(spin) * sin(elevation)) * zoom
up.Y = (cos(spin) * sin(elevation)) * zoom

Wow! That's a lot of trig functions! Remember that for any given scene, you only have to do this once per a frame. You could even get away with doing it once at program startup if you want it to be viewed from only one perspective like most isometric games. Now that all the points are projected onto the screen, it's as simple as drawing a sprite in its place for a particle effect or connecting them with triangles for a 3D model.

Full example.

Personal tools