Finding tileX and tileY based on x and y (and vice versa) in isometric 2D view

I'm working on 2d Isometric game in ruby and gosu, although my problem is probably universal. My map is saved in 2d array:

@map_tiles[layer][y][x] = tile_id


Each tile is displayed on coordinates:

isoX = (x * @tile_width/2) - (y * @tile_height)
isoY = ((x * @tile_width/2) + (y * @tile_height)) / 2


So it's displayed in "diamond" like way. But I'm having a problem with calculating two things. - Entity's tile_x and tile_y (on which tile is the entity positioned) when given x and y, and; - Entity's x and y when I'm sending it's tile_x and tile_y.

One method is pretty much the other one reversed. I tried various methods I found over internet, but either I'm doing something wrong, or I don't understand the instructions correctly. The closest I got is:

@x = (tile_x * 32) - (tile_y*32)
@y = ((tile_x * 32) + (tile_y * 32) / 2)


for finding @x and @y based on tiles (player "teleportation" method), and

@tile_x = ((@x / 32) + (@y / 32)) / 2
@tile_y = ((@y / 16) - (@x / 32)) / 2


But they kinda don't work correctly.
- When I send player to tile position (4, 3), he is displayed at (5, 4), and after recalculating it's tile_x/y position, printing says it is at tiles (3,5)
- When I send player to tile position (3, 4), he is displayed at (4, 5), and after recalculating it's tile_x/y position, printing says it is at tiles (2,5)
- When I send player to tile position (4, 2), he is displayed at (5, 3), and after recalculating it's tile_x/y position, printing says it is at tiles (2,5)

So you can clearly see that something's not right here.

That's pretty much it, thanks in advance. I can post the code on github if it's neccessary, but I reckon the problem lies in what I posted.

• Would you kindly post a screenshot to get the visual idea of how tiles are rendered? – Helbreder Nov 17 '13 at 16:36
• Sure, there are two screenshots, first one is how it displays tiles immediately: dl.dropboxusercontent.com/u/18909657/… And this is how it looks like when I move the camera to the left (displays whole map) dl.dropboxusercontent.com/u/18909657/… Numbers over tiles are displaying (x,y) of given tile. – ekharrtoll Nov 17 '13 at 17:40

We need to find a linear transformation between two bases of a linear space: a base for a screen coordinates and a base for game grid coordinates.

Let's define our base vectors for both cases:

• x and y vectors are basis for screen coordinates (define pixel coordinates)
• a and b vectors are basis for game grid coordinates (define tile coordinates)
• additional t vector define offset of grid space origin point relative to screen space origin point in screen space coordinates.

We can describe our x, y, a, b and t vectors in screen space coordinates as:

x = [ x0, x1 ] = [  1,   0   ]
y = [ y0, y1 ] = [  0,   1   ]
a = [ a0, a1 ] = [  w/2, h/2 ]
b = [ b0, b1 ] = [ -w/2, h/2 ]
t = [ t0, t1 ]


We can define our screen space base to grid space base transition matrix as:

    | a0 b0 |   | w/2  -w/2 |
P = |       | = |           |
| a1 b1 |   | h/2   h/2 |


To get screen space coordinates from grid space coordinates all we need is to multiply transition matrix P by transposed grid space vector, like so:

             |       gx      |
|               |
|       gy      |
------------------------------
| w/2  -w/2  | gx*w/2 - gy*w/2
|            |
| h/2   h/2  | gx*h/2 + gy*h/2


Taking into accout the base offset vector, the final screen space coordinates are:

s = [ sx, sy ] = [ (gx-gy) * w/2 + t0,  (gx+gy) * h/2 + t1 ]


To get grid space vector coordinates from screen space vector coordinates we need to find inversed P matrix ...

    | p00 p10 |
P = |         |
| p01 p11 |

1      |  p11  -p10 |
Pinv = ------ * |            |
det(P)   | -p01   p00 |

det(P) = p00 * p11 - p10 * p01 = w/2 * h/2 + w/2 * h/2 = w * h/2

2     |  h/2  w/2 |   |  1/w  1/h |
Pinv = ----- * |           | = |           |
w*h    | -h/2  w/2 |   | -1/w  1/h |


... and multiply it by transposed screen space vector:

             |       sx     |
|              |
|       sy     |
-----------------------------
|  1/w   1/h |  sx/w + sy/h |
|            |              |
| -1/w   1/h | -sx/w + sy/h |


So, the final grid space vector is given as:

g = [ gx, gy ] = [ (sx - t0)/w + (sy - t1)/h, -(sx - t0)/w + (sy - t1)/h ]


There's a short c++ program to test defined transformations (exact parameters are taken from a screenshot in my post):

#include <iostream>
using namespace std;

const float w = 64.0f;
const float h = 32.0f;

const float t0 = 800.0f;
const float t1 = 123.0f;

void gridToScreenSpace( float gx, float gy )
{
cout << "Grid space [" << gx << ", " << gy << "] in screen space: [";
cout << ( gx - gy ) * w / 2.0f + t0;
cout << ", ";
cout << ( gx + gy ) * h / 2.0 + t1;
cout << "]" << endl;
}

void screenToGridSpace( float sx, float sy )
{
cout << "Screen space [" << sx << ", " << sy << "] in grid space: [";
cout << ( sx - t0 ) / w + ( sy - t1 ) / h;
cout << ", ";
cout << -1.0f * ( sx - t0 ) / w + ( sy - t1 ) / h;
cout << "]" << endl;
}

int main( )
{
screenToGridSpace( 798, 360 );
gridToScreenSpace( 7, 7 );

return 0;
}


... and the results:

Screen space [798, 360] in grid space: [7.375, 7.4375]
Grid space [7, 7] in screen space: [800, 347]

isoX = (x * width/2) - (y * height)
isoY = ((x * width/2) + (y * height)) / 2


if that's your World coordinates of tile[x,y] then you have to solve system for x,y and you get tile indices like this

x=(2*isoY + isoX) / width
y=(2*isoY - isoX) / (2*height)


but those are real values so you have to floor them to get indices