5
\$\begingroup\$

My goal is to display a tile map in isometric projection. This tile map has 25 tiles across and 25 tiles down. Each tile is 32x32. See below for how I'm accomplishing this.

World Space

enter image description here

World Space to Screen Space Rotation (45 degrees)

enter image description here

Using a 2D rotation matrix, I use the following:

double rotation = Math.PI / 4;
double rotatedX = ((tileWorldX * Math.Cos(rotation)) - ((tileWorldY * Math.Sin(rotation)));
double rotatedY = ((tileWorldX * Math.Sin(rotation)) + (tileWorldY * Math.Cos(rotation)));

World Space to Screen Space Scale (Y-axis reduced by 50%)

enter image description here

Here I simply scale down the Y value by a factor of 0.5.

Problem

And it works, kind of. There are some tiny 1px-2px gaps between some of the tiles when rendering. I think there's some precision loss somewhere, or I'm not understanding how to get these tiles to fit together perfectly. I'm not truncating or converting my values to non-decimal types until I absolutely have to (when I pass to the render method, which only takes integers). I'm not sure how to guarantee pixel perfect rendering precision when I'm rotating and scaling on a level of higher precision. Any advice? Do I need to supply for information?

\$\endgroup\$
3
  • \$\begingroup\$ How about rendering to an image without transformations and rotating and scaling that image? \$\endgroup\$
    – Matthias
    Sep 19, 2013 at 16:00
  • \$\begingroup\$ How are you drawing the tiles? Are you rendering 3D polygons via a transformation matrix? Or are they prerendered sprites and you're trying to calculate positions to place them? If the latter, you're better off just calculating the positions in 2D directly, using integer arithmetic and a pixel-exact x and y offset tuned for the tile. \$\endgroup\$ Sep 19, 2013 at 18:04
  • \$\begingroup\$ @NathanReed These are just tiled 2D sprites. I suspected that would be the case, but I wanted to make sure there wasn't some way to make the matrix math work out. Thanks for the response. \$\endgroup\$ Sep 19, 2013 at 19:08

2 Answers 2

2
\$\begingroup\$

I am actually working on an isometric game right now. Here is how I did my rendering. I start by using images that are already transformed:

A basic tile in my game

The tile itself is 64 x 32, but the actual image is 64 x 64 (there are 16 pixels of padding on top and bottom). This way I can add depth and height to certain tiles. I determine the resulting screen x and y coordinates like this:

screen_x = iso_x * (TILE_WIDTH / 2) - iso_y * (TILE_WIDTH / 2) - (TILE_WIDTH / 2);
screen_y = iso_x * (TILE_HEIGHT / 2) + iso_y * (TILE_HEIGHT / 2);

As you can see, we didn't even have to use sin or cos, and it is much more simple. The reason we subtract TILE_WIDTH / 2 from the x, is because we want 0, 0 in our isometric world to represent the top left corner of the first tile. If we were leave that part out, 0, 0 (or any other coordinate) would actually point right here on the tile:

Where it would point

If you do everything right, it should look something like this:

Result

If you look closely, you will notice that some tiles are overlapping the other tiles. This because the tile extends 3px above the top of the tile (0, 0 + 0, -3). This is why I have those extra 16 pixels of padding on the top and bottom. It allows me to add depth.

As for the reason why you have gaps, it may not be inaccuracy, but instead the tiles aren't exactly the right shape. So if you want, you can use the tile I have above as a template.

\$\endgroup\$
0
\$\begingroup\$

If you're willing to draw sprites pixel-by-pixel, you can make the math work. To make sure you don't leave untouched screen pixels, you can use the inverse transform.

i.e., instead of mapping source pixels to screen pixels, map screen pixels to source pixels.

Just build your regular transform matrix M as you mentioned above, and invert it, so:

screenPixel[x, y] = getSourcePixel(M-1 * (x,y))

This may result in some artifacts, but they should be reasonable. We can learn from OpenGL how to handle those gracefully: In OpenGL, a fragment's (screen pixel) color will be chosen according to which polygon contains that fragment's center. OpenGL uses a bias to make sure that if an edge is lies exactly on a fragment center, one polygon is chosen consistently (see section 3.5.1 in OpenGL 1.3 spec). If you treat your source pixels as polygons (quads) you can determine which fragment center is inside which polygon, and choose that polygon's color as the fragment color.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .