# Screen coordinates to a multilevel isometric world

I have an isometric world that has stuff on multiple levels, i.e. it has an XYZ coordinate system, and tiles can be stacked on each other, accumulating screen Y offset as they go. I currently have an algorithm in place that converts the screen coordinates to world XY coordinates, but it doesnt take into account that there might be e.g. a hill under the mouse and selects the tile behind it instead.

Is there a generalized form algorithm for this? I need a generalized form because all the variables are unknown (I'm writing a game engine):

• Tile width (as in the width of a single tile rectangle)
• Tile height (as in the height of a single tile rectangle)
• Tile depth (as in how much Y offset each level contributes)

I suppose one option is run down the map with something like this (pseudo code):

p = screenToMap(screenPos)
bestMatch = null

for (x=p.x, y=p.y, z=0; x<mapWidth && y<mapHeight; x++, y++, z++) {
b = highestPlacedBlockInPos(x, y)

if (b.z === z) bestMatch = [x, y, z]
}


But I'm wondering if there's a better way? This seems pretty ineffective especially if you have a big map.

Depending on the specific requirements, it might be better to use a color picking system rather than converting the coordinates back manually.

Basically give every selectable object/tile a unique color and when you render the object also render it's silhouette to a secondary unseen buffer. Then when the user clicks look up the color at the coordinates they clicked and look it up.

This would allow you to ensure that what the user clicks on is what they select. You could select a plane flying high above the ground for example. Or pick out a object in a stack of overlaying ones. It is also more general solution that could work with any rotation and completely different engine types. You can even expand on it to do per-polygon picking in 3D.

Of course it has the cost of some extra memory (for the buffer) additional rendering time (possibly a 2nd pass depending on what you are using to render) and having to keep track of the objects. Neither of which would be an issue on any hardware now days (or decades ago even).

• Also note that color-picking is prone to errors on objects edges when user has forced anti-aliasing turned on. – Kromster Jul 27 '12 at 8:18
• Sounds like a pretty neat way to do it, especially from the UX perspective! However, I'm writing this in JS against a 2D canvas, which means that having a full-screen isometric game will bring the computer to it's knees, not to mention if I'm drawing a duplicate of the screen, recoloring tiles on every draw, etc. Maybe I should've used WebGL instead, so I'd just have to use a nice little shader for each tile. – quinnirill Jul 27 '12 at 15:26
• @KromStern: That's annoying, but there are workarounds: opengl.org/discussion_boards/showthread.php/170499-AntiAliasing – David C. Bishop Jul 27 '12 at 23:37
• @David: Thats right, unluckily more cards support AA than FBO. Just a small side-note. – Kromster Jul 28 '12 at 6:23

Another approach could be to store your tiles as an array or stack of tiles for each x, y. So let's say ..

map[1, 2] = ["grass", nil, "hill"]


You can just take the mouse coordinates and calculate the map coordinates and just take the top tile as what the user selected.

The catch to this is that the "hill" tile's coordinates are actually x=3, y=4, z=2. But for rendering and click detection purposes it belongs to x=1, y=2. Note that if you don't actually care about being able to calculate the real coordinates of the top tile, and just want the tile itself, you can leave out the nils. They're just there to preserve depth.

Another upside to this approach is say the player is located at x=1, y=2 and you want to show the tile the player is standing on and perhaps show overlapping tiles at 50% transparency you just draw the top tile at 50% before drawing the player, then draw the bottom tile.

If you don't actually want to store your map data this way you can do something like ..

for (x=0; x<map.width; x++) {
for (y=0; y<map.height; y++) {
for (z=0; z<map[x, y].depth; z++)
//not entirely sure how you're storing your tiles
render_map[x - z, y - z].add(map[x, y].tile_at_depth(z))
}
}
}
//Doesn't preserve depth with nils


I'm making some assumptions about your map object but I think you get the idea. Hope this helps.

• I'm actually currently storing the tiles like this: I have a nested array for X, Y and Z, so for example to get (x,y,z) you'd do tiles[x][y][z] where it's either null or an instance of a tile class. This would be a really good idea, but it actually stumbles on something I realized the algorithm I showed in the post does as well: it assumes that the tile depth equals the tile height, and I already need to use my engine for a project where this assumption is false. :/ But thanks! I think I have something boiling in my mind now. :) – quinnirill Jul 29 '12 at 0:45