working with isometric maps

Ok, I’m working on my own time to create a game, and exploring technical possibilities on this adventure !

In the beginning I was planning to do the map 2D plain, the coordinates 10,10 would be easy to calculate, was just a matter of multiply the x,y by the width and height of the small tile that would be in the screen.

isometric1.png

But nothing is simple ! My daughters are helping me on the discussions of the game, and also helped to create the map, thanks girls you are the best daughters !! The 8 years old one come with the great idea ! Hey dad, why don’t you make the game like the age of empires or warcraft III maps ? a little bit shifted back … Oh well that began all of this :)

Now I have a new map to work with

isometric2.png

And the nice problem of how to convert from a screen coordinate to the map coordinates ? remember that in this case the screen 0,0 is the top left point of the window.

I will try to describe how I get to the solution that I made, I believe that is not the most I-know-geometry-a-lot but it works and I can explain hehehe :)

step 1 – divide and conquer

I tough: well, as I believe that there is no way to calculate in one line, let’s see if I can discover how close to a tile of the map the mouse is. To do it I split the map in smaller segments, with half of the width of the tile, and half of the height, getting something like this

isometric3.png

So part of the problem was solved (dreamer) divide the screen coordinates by the half of the tile measures and I have what segment the mouse clicked on ! lets say that the tile have 100X50, width of 100 and height of 50, doing so we have segments of 50,25, lets see and example :

isometric4.png

we need to find in which segment the mouse is, so :

segment(x) = (int) mouse(x) / segment.width
segment(y) = (int) mouse(y) / segment.height

very simple isn’t it ?

180 / 50 = 3.6 => 3
22 / 25 = 0.88 => 0

then the segment is the 3,0, but this is just the first step :)

step 2 – triangles are your friends

Now that I have the segment I had to find out if the point is above or under the line that splits the segment in two …. before we go in the formulas we need to remember that the original point 180,22 now have to be converted to something relative to the top left corner of the segment, so the relative.to.segment(x) would be x – ( segment(x) * segmen.width ) that is 30, and relative.to.segment(y) would be y – ( segment(y) * segment.height ) that is 22

relative.to.segment(x) = screen(x) – ( segment(x) * segment.width ) relative.to.segment(y) = screen(y) – ( segment(y) * segment.height )

based on the fact that the tangent of the angle of this line is calculated by the formula :

tangent = oposite side / adjacent side

I just need to store the tangent for the segment, that is segment.height / segment.width, and use it to calculate the y for a given x that would be at the line :) let’s see a drawing, that talks more than 1024 words.

isometric5.png

so for a given X I calculate the maximum size of Y to be above the line, so if Y is bigger than this value the point is under.

Ye ye we have another problem, sometimes the line is from top-right to bottom-left what make the calculation a little different, in that cases we don’t use the relative.to.segment(x) as adjacent, we need to subtract from the width of the segment the relative.to.segment(x) and then we have the adjacent …

isometric6.png

As we already know, nothing is simple, so we need to figure out when the segment have the line for one side and when have to other side … when the sum of the segment(x) and segment(y) is even the the line goes up, when is odd goes down … believe me its true. :)

step 3 – it’s a kind a magic

this step I don’t know how to explain, I just know that after some observations of the numbers I found out a relation between it, that can be described as follow :

we have a segment that we know which tile it refers to, is the tile(0,0) top-left segment, let’s call it start-segment.

Based on that one all the others will have the information of how much shift is need from the start segment to find the expected segment, look at the image bellow

isometric7.png

so based on that information of the shifts from the start segment, we have the following formulas :

shift(x) = tile(x) – tile(y)
shift(y) = tile(x) + tile(y)

that I used to create the following formulas that I used to find the tile(x) and tile(y) based on the shift(x) and shift(y)

tile(y) = ( shift(y) – shift(x) ) / 2
tile(x) = shift(x) + ( shift(y) – shift(x) ) / 2

As I said, these formulas came from lots of tests and thinking about the shifts, tiles, segments … And I really like it because even with lots of small step, I can understand how it works :)

step 4 – packing everything

And to finish based on the current segment we need to identify if its a NorthWest, NorthEast, SouthWest or SouthEast segment, because for each one we have different rules as we can see in the image bellow

isometric8.png

Using the formula

tile(y) = ( shift(y) – shift(x) ) / 2
tile(x) = shift(x) + ( shift(y) – shift(x) ) / 2

We can find the tile(x) and tile(y), but using the following rules :

NorthWest = the values are round
NorthEast = tile(y) has -0.5
SouthWest = tile(y) has +0.5

I believe that this piece is better understood with the source code, isomapjava.txt

Note that is implemented to be used with the framework pulpcore

7 Responses to “working with isometric maps”

  1. Lis says:

    What you created isn’t an isometric view – with computers the angle is different from the actual isometric angle. You have to go two pixels across, one pixel up every time to create an isometric line.

  2. athanazio says:

    hehehe Its true Lis, is almost isometric, but what made me happy on that is the fact that I figure it out how to map the screen to the points =)
    but thx for pointing me that

  3. [...] my isometric efforts, I will follow my friend advice, and make plain 2D, lets see If I can make it [...]

  4. yepke says:

    I think you are doing a lot of extra calculations! You can usde much more maths.
    It took me all day, but I get the same results now:
    public void getTileAt(int screenX, int screenY){

    //adjustment will be 4*tileWidth in your example.
    double x = screenX – tileWidth;

    //Y*2 -> squares instead of diamonds => 90° = PI/2
    //easyer calculations!
    double y = screenY*2;

    //distance from origin to point in ISO
    double r = Math.sqrt((x*x) + (y*y));
    double theta = Math.atan2(x, y);
    double angle = (Math.PI/4) – theta;

    //length of the side of one tile (128/64 are mine)
    double rr = Math.sqrt(2*64*64);

    //’real x’ is the projection of screen distance by the angles cos
    double rx = Math.floor(r* Math.cos(angle) / rr);

    //’real x’ is the projection of screen distance by the angles sin
    double ry = Math.floor(r* Math.sin(angle) / rr);

    System.out.printf(“(%.0f,%.0f)\n”,rx,ry);
    }

  5. athanazio says:

    great job yepke !

  6. Jörn says:

    Great help for a tricky problem! Thanks! And also thanks for the comments that helped aswell!!

  7. I’ve been working on an isometric engine for the last couple months. I found what seems to be the best method for screen-to-map conversions. I know it works with diamond-type isometric maps, not sure about other types (like staggered and slide).

    It’s a simple algebraic equation, rather than having to mess with sqrts and angles, as I know they’re not the most efficient calculations cpu-wise.

    Point tileID;
    tileID.X = ((tileWidth * mouse.Y) + (tileHeight * mouse.X)) /
    (tileWidth* tileHeight);
    tileID.Y = ((tileWidth * mouse.Y) – (tileHeight * mouse.X)) /
    (tileWidth* tileHeight);

Leave a Reply