Archive for the ‘Articles’ Category

VSync

Saturday, January 1st, 2000

Shearing

What is VSync?

Let’s see how displaying something on a monitor works. Basically, conceptionally, there is an area of memory, let’s call it the framebuffer, which has all the RGB values of all pixels visible. This gets updated by the graphics card - maybe because you actually sent pixels to it, or because you asked the graphics card to draw some 3D scene to it, it doesn’t really matter to us here - it’s just a simple model how we will view things.

Now the graphics card sends the contents of this framebuffer to the monitor, who displays all the pixels. If for example a letter is changed in a displayed text, then the framebuffer is modified accordingly, and the next time the monitor redraws the corresponding part of the display, this will be visible. The important thing to note is, you cannot tell the monitor to update just one specific location. Instead, the framebuffer gets modified, and then is sent line by line and pixel by pixel to the monitor, starting at the top left corner, and ending at the bottom right corner.

Originally, in old CRT monitors, the electron beam would be sent back up to the top left monitor corner from the lower right corner after a complete update was done, and when it started this, it would send a VSYNC to the graphics card. This was then the preferred time to update the contents of video ram - since if you could finish the updating of the framebuffer before the electron beam would start displaying the contents again, you could be sure that the image would be displayed correctly.

If this is not done, there will be visible shearing, as can be noticed in games when you disable vsync. Assume, we have two frames in our game:

111111
111111
111111
222222
222222
222222

First a screen full of color 1, then a screen full of color 2. The correct way to display would be, in the first update, the monitor displays all 1s, and in the second update, all 2s. But now assume, we have no vsync. So first we display frame 1.

111111
111111 <-
111111

Next, we switch to frame 2. That is, the contents of the framebuffer are changed from all 1s to all 2s (and compared to the speed of the monitor update, the change happens in an instance). Now let’s say the monitor has just updated the line marked with an arrow. So in the next line, it will see the updated framebuffer contents.

111111
111111
222222 <-

At this point, our monitor displays a picture which never existed. We only had two frames, one with all 1s, one with all 2s - and the result just is wrong, it shows a frame which has a part of the first frame and a part of the second. If for example there’s a video with a straight lamp post, moving at 10 pixels / frame, this lamp post might now have a 10-pixel big step in the middle, like so:

wanted:

O
|
|
|
|

result without vsync:

O
 |
 |
  |
  |

The shearing does not necessarily have to be at one single location. If you switch your graphics let’s say with 600 FPS, and your monitor has a 60 Hz update, you will get 10 horizontal lines with shearing. I.e. for one monitor update, the actual framebuffer contents change 10 times. If you want to play a classical side-scroller that way, you won’t have much fun. On the other hand, if you are browsing websites, you probably wouldn’t mind much, since the effect would only be visible while scrolling text.

In the following, we don’t talk about shearing any longer, but the problem of getting smooth animation with VSync enabled. So from now on, VSync is implicitly assumed to be always enabled.

Synchronizing logic and rendering

With VSync enabled, the display is only updated at discrete points in time. For example, 60 or 75 times / second. The exact time is determined by the monitor. But a game could also use a discrete logic rate, for example, 100 updates / second. One very basic problem now is that both logic and rendering may take CPU time. If the CPU is too weak to handle the logic updates, the game cannot run at all (or only slowed down). If the CPU can barely handle the logic, there may be not enough time to render all frames - in which case video updates may get skipped, usually making the game unplayable as well, or if only some frames are skipped, somewhat jumpy.

Now, in the following, we assume there always is enough CPU power. In most cases, the rendering will completely happen on the video card’s GPU, so the actual time consuming part is memory transfers. In any case, if there’s not enough CPU/memory bandwidth to handle it all, it will be hard to get smooth animation.

Smoothness

The smoothness problem discussed here has nothing to do with shearing, or missed updates due to exhausted CPU time. With enough CPU time, you can have smooth animation with shearing (which will still be useless), and you also will get un-smooth animation without any shearing. But why would animation ever be un-smooth?

Well, some might remember the ultra smooth scrolling we got on an Amiga, or any gaming console. The smoothness comes from the simple fact that things are displayed in a regular manner. Let’s assume, video is updated 4 times a second (will just as well work with 60 times, but 4 is easier to draw). And assume we have a sprite which moves at a constant speed of 4 pixels / second. Then this can be viewed as a diagram like below:

second  0           1           2
|           |           |
pixel   0  1  2  3  4  5  6  7  8
|  |  |  |  |  |  |  |  |
vsync   0  1  2  3  4  5  6  7  8
|  |  |  |  |  |  |  |  |

The first row is the time in seconds. The second row is the x-coordinate of our sprite in pixel. The third row is the time at which a vsync occurs, i.e. when we updated a complete new frame on the monitor.

In this case, each time the display is updated (4 times / second), the sprite has moved exactly one pixel. Therefore animation is completely smooth.

Unsmooth display

Now, assume, we have a PC. The monitor’s refresh rate can be anything, let’s assume we have three monitors, with 3, 4 and 6 refreshes / second.

The above would then look like this:

3 vsync / second

second  0           1           2
|           |           |
pixel   0  1  2  3  4  5  6  7  8
|  |  |  |  |  |  |  |  |
vsync   0   1   2   3   4   5   6
|   |   |   |   |   |   |
drawn   0   1   2   4   5   6   8  <- 3 and 7 were skipped
pixel

4 vsync / second

second  0           1           2
|           |           |
pixel   0  1  2  3  4  5  6  7  8
|  |  |  |  |  |  |  |  |
vsync   0  1  2  3  4  5  6  7  8
|  |  |  |  |  |  |  |  |
drawn   0  1  2  3  4  5  6  7  8 <- smooth display
pixel

6 vsync / second

second  0           1           2
|           |           |
pixel   0  1  2  3  4  5  6  7  8
|  |  |  |  |  |  |  |  |
vsync   0 1 2 3 4 5 6 7 8 9 0 1 2
| | | | | | | | | | | | |
drawn   0 0 1 2 2 3 4 4 5 6 6 7 8 <- 0, 2, 4, 6 were displayed twice
pixel

When we render something in the game, it only can happen exactly at a vsync. Now, assume, we simply draw at the pixel position available at the time of each vsync.

Obviously, only the middle one can be smooth then. If the integer pixel positions are not synched to the display updates, there will be jitter. Either the sprite will be jumpy, and skip certain frames (in the first case above), or it will stutter, and some frames will stay twice as long as others sometimes (in the last case above).

So, this means, when a game is ported from a console to a PC, all the smoothness is gone, unless you are lucky enough to have a monitor with a video mode to support the right frequency. Luckily, we have some solutions to still get a smooth display, even when the frequency won’t match.

Solution: variable timesteps

Let’s first increase the pixels our sprite moves, e.g. 40 pixels / second in the first example from earlier:

second  0           1           2
|           |           |
pixel   0  10 20 30 40 50 60 70 80
|  |  |  |  |  |  |  |  |
vsync   0   1   2   3   4   5   6
|   |   |   |   |   |   |
drawn   0   10  20  40  50  60  80  <- 30 and 70 were skipped
pixel

Here, a straight forward solution exists to get better results: Do your rendering at every vsync, and simply have your game logic compute the position at the required time, given the time delta since the last logic update. This means, in our 3 example cases, we would have:

logic at 1/3 second -> move 40/3 = 13.3 pixel
logic at 1/4 second -> move 40/4 = 10.0 pixel
logic at 1/5 second -> move 40/5 = 8.0 pixel

We pass the time in seconds to the logic() function (i.e. 1/3 or 1/4 or 1/5), and given the constant speed of 40 pixel/second, it will return a position, as seen above. Now we render at this position, and get a smooth update, ”’no matter what vsync rate the PC’s monitor has”’. The example now looks like this:

second  0           1           2
|           |           |
pixel   0  10 20 30 40 50 60 70 80
|  |  |  |  |  |  |  |  |
vsync   0   1   2   3   4   5   6
|   |   |   |   |   |   |
drawn   0   13  26  39  53  66  79  <- not completely smooth as we had to round to full integer
pixel

Of course, this trick is no fully exact, as we had to truncate to full integer positions, even though our exact update step would have been 13.3 pixels. Especially in the original example, if your sprite moves 4 pixels in a second, but you have 3 or 5 display updates, then you always will get jitter. The pixel movements with variable timestep would be:

logic at 1/3 second -> move 4/3 = 1.33 pixel
logic at 1/4 second -> move 4/4 = 1.00 pixel
logic at 1/5 second -> move 4/5 = 0.80 pixel

And so e.g. for the third case, we would end up with exactly the same positions. An easy solution of course is if the display supports subsampling. That is, a sprite drawn to pixel position 1.0 looks differently from one drawn at position 1.33. In this case, we can improve smoothness to almost perfection, as even the inter-pixel fractions are accounted for.

Solution: interpolation

For various reasons, variable timestamps are bad in a game though: If you have constant acceleration instead of constant velocity (e.g. not ””move 4 pixel/second””, but ””accelerate 1 pixel / second²””), you need a complicated integrator to get the right positions. Or think of non-linear motion, e.g. a circle path. And in general, physics quality might now differ depending on the vsync rate. For networked games which need to stay synchronized, it might not be possible at all, and for other multiplayer games, clients with a higher vsync might have an advantage or disadvantage due to more accurate physics prediction. For many classic style games, it simply will make the game logic and collision detection much more complicated.

So, what is the situation here? Assume we have a logic() function, which just ticks 4 times / second again, like the initial example. Each sprite has an integer position sprite.x, which is incremented in each tick. But, our vsync can be 3 or 5 instead of only 4. So, what we want is:

3 vsync / second

second  0           1           2
|           |           |
tick    0  1  2  3  4  5  6  7  8
|  |  |  |  |  |  |  |  |
vsync   0   1   2   3   4   5   6
|   |   |   |   |   |   |
drawn   0 1.33 2.66 4 5.33 6.66 8
pixel

To achieve this, we can give each sprite an extra variable sprite.last_x, and store where it was in the last frame. For vsync #1 above, we get:

sprite.last_x = 1 (from tick #1)
sprite.x = 2 (we already execute tick #2)

So now, we know that our logic tick rate is 4 / second, and our vsync rate is 3 / second. At vsync #1, we are at 0.33 seconds. Tick #1 was at 0.25 seconds, tick #2 at 0.50 seconds. So, we interpolate:

render_x = sprite.last_x + (sprite.x - sprite.last_x) * (0.50 - 0.25) / (0.33 - 0.25) = 1 + 1 * 0.25 / (1/3 - 1/4) = 1 + 0.25 / (1/12) = 1.33

Or in general:

t_i = time when sprite was at sprite.last_x
t_j = time when sprite would be exactly at sprite.x
t   = render time
render_x = sprite.last_x + (sprite.x - sprite.last_x) * (t_j - t_i) / (t - t_i)

In this way, we achieve the same as in the variable timestep solution, but do not have to change our logic code at all. We can write the code as if the vsync rate would always be the logic rate, and only the renderer will need to do extra interpolation. Again, for small movements like in this example, there only will be an advantage with sub-sampling. But if the example again would use 40 pixels / second, things apparently should get much smoother than without interpolation, even for using only full integer positions.

Solution: Increase logic rate

If you look what variable timestep and interpolation did, then it gets clear that the effect they have in our example is more serious the bigger the pixel movement gets. E.g. with 4 pixels per second, displaying at only integer positions would have made no difference at all, since compared to the 3/4/5 video updates per second, we never could be off more than a pixel anyway.

So a simple trick to get the same smoothness as variable timesteps or interpolation is to increase the logic rate. E.g. just take an extreme case of 1000 logic updates / second. Now even if a sprite moves very fast, when it is drawn, it can only be at most 1/1000th of a second worth of movement off the ideal position which we would have arrived at with variable timestep or interpolation.

Of course, this has a serious drawback, we are wasting lots of CPU on doing much more logic updates than we need.

Solution: Adjust refresh rate

One solution which would be the most simple of all, and which also has the best results: Try to adjust the refresh rate. When you set a graphics mode, you may have a selection of different modes, with refresh rates such as 60Hz, 75Hz, 100Hz - and many others. And if your logic rate, or a multiple thereof, or even something sufficiently close, is among the possible rates, you can try to set that rate, and then simply time your game off the vsync signal. E.g. if the rate 61.2 Hz is available, and your logic is supposed to run at 60 Hz, you may get away with simply using vsync, and letting your game run slightly faster (61.2 Hz instead of 60 Hz), but perfectly smooth, without needing any interpolation.

But note that it is important to time the game off the vsync then. If you let your game run at 60 Hz logic rate, and the monitor updates with 61.2 Hz, this will then still stutter when timed off vsync.

And also, sometimes graphics drivers report refresh rates as available, when they really are not. So if you see a nice 100 Hz mode there, switch to fullscreen and set the mode, the user might only get a blanked screen with a message “out of sync” or something.

IsometricProjection

Saturday, January 1st, 2000

Isometric projection

Wikipedia says this: http://en.wikipedia.org/wiki/Isometric_projection

So, it can mean a projection where all 3 directions are projected to the same length, i.e. they meet at exactly 120°.

Here, I don’t mean this “true” isometric projection, but one which is close (but really a dimetric projection), and nice to be used in pixel games. It is the projection where a square tile viewed from above has exactly a ratio of 2:1 - so it means, when doing pixel art, a straight line along a floor axis is 2 by 1 pixels.

An isometric map

Rotated diamond

If you want to deal with an isometric game, there’s of course several ways. One common way is to use a standard tilemap, and sort of rotate it by 45°. In the following, I assume it is rotated 45° degree clockwise - so the new origin lies at the top.

So, our map is now diamond shaped. The origin is at the top, the x-axis goes right down, the y-axis left down. Of course, it works just as well if you put the origin left, and let the x-axis go right up, and the y-axis right down. I just like my game objects to be at the top of a tile when its position is 0/0, instead of at the left.

What is important is, internally, our game need not know about the isometric display. It just sees a normal tilemap like to the left - only the display is different.

Before delfing further, let’s see how exactly an isometric tile can be displayed now. Assume, we want to draw the tile at x/y. So in the picture e.g. 0/0 would be the topmost tile, and 0/3 the leftmost one.

The tile 0/0 of course would be drawn at pixel position 0/0 (if we just set the top of the diamond as 0/0 on screen). And the leftmost tile would be drawn on (3 * w * -0.5 / 3 * h * 0.5), so in our example (-48/24). w/h are the width and height of a single diamond shaped tile, the picture uses 32×16 tiles. In general, the tile on tile_x/tile_y is drawn on:

pixel_x = tile_x * tile_w/2 - tile_y * tile_w/2
pixel_y = tile_x * tile_h/2 + tile_y * tile_h/2

This transformation can be put into a function isometric_transform, and then the code to draw our tilemap gets very simple:

for each tile_x, tile_y:
    pixel_x, pixel_y = isometric_transform(tile_x, tile_y)
    picture = map.get_picture_at(tile_x, tile_y)
    picture.draw(pixel_x, pixel_y)

In words, for each tile in our map, get the corresponding pixel position, and draw the tile there.

Inverse mapping

Now, we basically are done for simple games. We can now put all sorts of stuff into our tilemap, and deal with it as if it was a non-isometric map. But if the game is e.g. isometric minesweeper, there is a problem: When the mouse is clicked over our map, which tiles was it over?

Myself, I best like to simply build the inverse of the formula above, and then get back two floating point numbers, where the integer part tells me which isometric tile a pixel position is in, and the fractional part tells me where in the tile it is (the latter can simply discarded if it is not needed).

The formulas are:

tile_x = (pixel_x/(tile_w/2) + pixel_y/(tile_h/2)) / 2
tile_y = (pixel_y/(tile_h/2) - pixel_x/(tile_w/2)) / 2

For example, if in our example 4×4 map at the top, the mouse is clicked on pixel position 20/40 - which tile is there? The size of the tiles is 32×16. So we get:

tile_x = (20/16 + 40/8) / 2 = 3.125
tile_y = (40/8 - 20/16) / 2 = 1.875

So, the tile is 3/1, and we even know that it is at (.125/.875) inside the tile. .0/.0 would mean the pixel is exactly the topmost pixel inside the tile, .99/.99 would mean it is at the bottommost pixel.

Pixel -48/24 from the initial example would accordingly yield the tile position ((-48/16+24/8)/2,(24/8-(-48/16))/2) or (0.0/3.0).

Pixel positions

By now, we can make a complete isometric minesweeper, or any other game using 2D gameplay and an isometric display. To draw a tile, we transform to isometric pixel coordinates, and if we need to find a tile at some screen position, we can transform back to tile coordinates.

But what if we have other stuff on our tiles than just a single tile picture? Houses, trees, little people, or whatever else. We can just put them on their tile so far, but what if such a game unit wants to smoothly travel from one tile to the next?

  • There are different possibilities now. Such units could for example use a normal, not transformed offset position inside the tile. With our sample 32×16 tiles, they could e.g. roam inside a diamond with the four corners (0/0), (16, 8), (-16, 8), (0, 16).
  • Another possibility would be to make the units completely independent of the tilemap, keep their position in normal, un-transformed pixel coordinates, and use our second transform above to convert into fractional tile coordinates for drawing them.
  • The first two solution both do something bad though: They mix up different coordinate system, which in a somewhat bigger game will sooner or later cause you to scream at your code. I prefer to keep the fact of an isometric display confined to the drawing code, and to things like mouse positions. An easy way for this is to keep the intra-tile positions of objects in the floating point rectangle 0/0 to 1/1, that is, in the same fractional positions we already have in the pixel-to-tile transformation.So, if for example there’s a tree on tile 2/2, and it is on 0.5/0 inside that tile. Then we know that tile is drawn at (0,32). More precisely, its topmost pixel is at (0,32). Now, the intra-tile position can be transformed with the same formula, and (0.5/0) is (8,4). So we can added that offset in pixels to draw the tree there.
  • If each tile of the game stores its objects, then probably the previous method is best. But instead of using intra-tile positions, it can sometimes be better to store global coordinates for units. In this case, our tree would then be at 2.5/2. And the transformation formula still works, and returns (8,36). (At least, as long as floating point is used.)

There are of course other ways, especially in original old games the formulas will be adjusted to work with integer only. But I do like my way a lot. All I need are the two initial transformations, from tile-to-pixel, and from pixel-to-tile. And using floating point, this always works, for tile positions, intra-tile positions, and it even works out for sub-pixel accuracy if you are using all normalized coordinates, e.g. when using OpenGL. And the advantage, the game logic never needs to know that the display is isometric. All my coordinates (tile as well as intra-tile, or global coordinates) are just normal 2D coordinates for a non-isometric map. Only the two transformations transform two and from screen coordinates.

Pixel art

Our projection is welcome to pixel artists, because you can pixel straight lines simply by using 2 pixels in the x direction for every pixel in the y direction. It can look very nice, since it allows you to draw very exact geometry. The below picture can help understand where exactly pixels go - in the example tiles are only 8 pixels wide - but it works the same for bigger ones of course.

Of course, the angle between the edges is not 120°, and also the third dimension, the up-direction, must have a different length to produce something looking like a cube. The projection to get 2:1 lines is one where you look down on the floor by an angle of exactly 30° (2:1 lines itself have an angle of about 26.565 though, don’t confuse those angles.)

Here an old ASCII art explaining how to get to the 30°, but it’s not important:

  \     / view vector
    \ /
    /.\  l/2
  /     \
/_________\
 a     l    \ view plane
At the bottom, there's the tile, with length l. It is viewed from an angle a, and
 we want it to appear as length l/2 (the vertical direction in the pixel art). The sideways direction (horizontal in the pixel art) will continue to be l, so the result will be the desired - lines who are exactly 2:1. This means:
sin a = 1/2 -> a = asin 1/2 = 30°

So, the angle to the floor should be exactly 30° (and conversely, the angle to the vertical should be 60°).

Height

Now, the interesting thing for us is only, how many pixels high must something be drawn to be as long as the side of an isometric tile?

First, again a quote from the same old (and hard to understand, especially as I only quote some small parts) ASCII art aticle:
What about height?

  |\
h'| \h
  |  \
  |   \ 60°
  ------
If something has a height h, what will be the projected height h'?
We know h' / h = sin 60° = cos 30° = sqrt(3) / 2. So if something is h' pixels tall, it
really is h = h' * 2 / sqrt(3) pixels high. And if we want something to have a height of h,
we draw it as h * sqrt(3) / 2 pixels. Real height to drawn pixels is 2 to sqrt(3).
Turning everything by 45°, we get this:
  /\
 /  \
 \  / |
  \/s | x / 2
 __x
2 * x^2 = s^2
 s = x*sqrt(2)
A diamond.
To make the width x units, the side length must be x*sqrt(2);

Now, what it means, is this. We have an isometric tile, which let’s say fits into a 64×32 rectangle. So the ‘x’ in this case is 32. This means, a side really (un-projected) has a length of s = 32 * sqrt(2). And we know by the formula above that something appearing x pixels high really is 2 * x / sqrt(3). So: 32 * sqrt(2) = 2 * x / sqrt(3), or x = 16 * sqrt(2) * sqrt(3). Believe it or not, as always with math formulas, but the result would be about: 39.192

There’s no need to understand any of this, just know: For an isometric tile which fits into 64×32, make something 39 pixels tall to have about the same height as a side is long.

This cube also will align exactly when using in an isometric tile-map, or stack on top of others. All you need to remember is, if your tile fits into a box w * h (where w = 2 * h, e.g. 64 * 32), then (h * sqrt(6) / 2) is how many pixels high to get a perfect cube.

In summary (x is horizontal, y is vertical, z is depth):

  • z / x = 1 : 2
  • z / y = 1 : sqrt(3)

3D Models

Where this really is relevant is if you want to project a 3D model to fit the pixel art. By modelling a cube, then viewing it from exactly 30° from above, and from 45° from the side, and using orthographic projection, you should get something like this cube. It’s also a good way to verify the formula.

Actually, here’s an example done in Blender (for something else, but still). Also a version without subsampling.

Sorting

So far, everything is very nice. We have a tilemap, we can choose how we want object positions mapped into the map, and we know how to draw our objects (if we would have any artist skills at least).

But how can we properly draw objects? Looking at the tilemap, it is simple. If we draw from top to bottom, row-wise (in display rows), we should be all sets. There can now be boxes, columns, whatever standing on the tiles, and it still will look alright. Even if e.g. those trees overlap - they are drawn in the right order.

(insert picture with some columns)

But, if we would now try adding a sprite and moving it between those columns-tiles, it would not work out easily. The sprite could be drawn along with the tile it is standing on - but what if it moves between tiles? One idea which works is splitting each moving sprite into halves, one for each tile it is on. This is relatively easy to do, and works all right for simple cases. E.g. in a diablo1 style map, we should be all set. Whenever the player or a monster moves around a corner, internally there will be two halves, one still on the tile behind, the other in front.

(insert picture from Feud)

But, what in the general case? Like, we want to also move on top and below objects? We want to stack up boxes and push them around? Before looking for an answer, here a picture:

In which order would you sort the three boxes?

Well, now, it makes a whole lot more sense that we actually did split our blocks before, instead of looking for a better way to sort them. Since, in fact, you cannot sort isometric objects (or 3D objects in general, for that matter).

This is of a course a rather big problem for anyone attempting to create an isometric game. Sorting still may be the best option. For example, if we sort by distance of the object center to the viewer, it will work good enough, if the objects don’t come too close.

Splintering

But what if we want to draw those three boxes correctly? A single split along one of the cubes apparently makes it work. So maybe we can come up with a splitting rule like with the player-walking-between-columns before?

My endeavors in that direction so far led to some interesting non-polynomial-time splitting algorithms. What that means is, just comparing each object to each other is not enough, instead i end up with even more checks for a possible split. For few objects this doesn’t really matter, and there’s a lot of optimization potential as distinct groups of objects which are not overlapped could be singled out. However, the complexity for the engine just is bad.. instead of isometric objects each consisting of an image, i have now a collection of object splinters for each objects.

Z-buffer

So, why don’t we go for the classic solution: A z-buffer. This means, we can’t simply use one single picture for our 3d objects, but need depth information. E.g. a box could consist of 3 planes for left/right/top side.

So, if not using pixel art, this of course is the natural choice.

HexMap

Saturday, January 1st, 2000

Introduction

Now that we know a bit about isometric maps - what about hexagon maps?

Wikipedia only says this: http://en.wikipedia.org/wiki/Hex_map
And this: http://en.wikipedia.org/wiki/Hexagon

In games where neighbor tiles play a big role, hexagons are often preferable to the diamond/square tiles we looked at before. With diamonds/squares, there are 4 neighbors with a common edge, and 4 more neighbors with only a common vertex. With hexagons, there are 6 neighbors, and all equally share a common edge.

Hexagons really are just diamonds/squares with corners cut away

First, realize that every hexagon map internally can just be seen as diamond/square map. Here are some examples. First, we draw hexagons into a perfect square grid:

Then in our nice 2:1 tiles isometric grid:

And finally, we try to put a grid over an existing hexagon map:

As you can see, each grid also is a hexmap, and the other way around. And even more, each square/diamond corresponds to exactly one hexagon. So, it will be extremely easy later handling hexmaps. We don’t need to do anything special, except find a convention which cells are neighbors and which not.

The perfect hexagon

One interesting question now would be, what sort of grid do we get for a perfect hexagon map? Well, let’s calculate it.

In a perfect hexagon, each side has the same length, and each angle is the same. So, it gets clear, that the length of each side ”’s”’ will be just the same as the length from the mid-point to a vertex. That means, we simply put together three equal-sided triangles, who have an angle of 60°. Therefore all the inner angles of our perfect hexagon are 120°. Pythagoras tells us that the distance from the center to the middle of a side is sqr(3)/2 which is about 0.866.

The position of the 6 corners of our hexagon, if we start to the left, would therefore be:
(-s, 0), (-0.5 s, 0.866 s), (0.5 s, 0.866 s), (s, 0), (0.5 s, -0.866 s), (-0.5 s, -0.866 s)

And the vector from the hexagon center to the neighbor right up: (1.5 s, 0.866 s)

And here’s how it looks:

One thing gets obvious when working with perfect hexagons - since there’s always that 0.866 factor, and in classic pixel drawing applications, like in the Gimp, you get only an integer grid, it’s not very practical. Of course, just using an almost perfect hexagon, but rounded to integer, solves this problem. Or using a vector application like Inkscape, which was used for the above picture.

Pixel artist’s and modeller’s perfect hexagon

Now, above, we have seen different hexagons, and there are many more. To find the best one to actually use for a map, we could do something like with our iso map. Specify that we want perfect hexagons when looking at our map from directly above, and then just view them from an angle, to get some other wanted effect.

Another idea would be to use the exact same projection as for our 2:1 iso tiles (so e.g. unit artwork could be re-used :) ). As seen in the second picture, this could be used to nicely overlay a hexagon map over our iso map. The real, unprojected heaxgons would not be real hexagons, but it wouldn’t matter.

And of course, also something like the 3rd picture works very well. This simple draws the hexagon into a 4×4 square grid, fitting the hexagon exactly into a square. It’s what makes the pixel artists happy :) This again is not a projection of a perfect hexagon map.

If a 3d modeller with perfect hexagons is used, then grid settings can be used to get the 0.866 factor in there. Just make the grid 0.866 in one direction, and 1 in the other. Hexagons fit in then with grid snapping.

Maybe an interesting question is, in a 3D modeller, what would be the perspective to get a square grid to look like the diamond grid for our perfect hexes? We found out that the ratio of height to width is 0.866 / 1.5 - so just in the same way we did it with our 2:1 isometric tiles, we know that the view angle is asin(0.866 / 1.5) or asin(sqrt(3) / 3) or about 35.264 degree. That’s how much we tilt our view down from viewing perfectly horizontal. (Viewing from directly above, asin(1/1), would be 90°, viewing horizontal, asin (0/1), would be 0°.)

If you remember, with our 2:1 iso tiles, we got asin(1/2), which was exactly 30°. So, the difference is not very big.. unit art could still be used.

With the hexes in the 3rd picture, we would get asin(2/3), which is about 41.810 degree.

These values could maybe be used for artwork to use on the hex map, if no real perfect 3d hexes are actually used in the modeller.