Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I've spent the last few days researching isometric tile-picking algorithms (converting screen-coordinates to tile-coordinates), and have obviously found a lot of the math beyond my grasp.

I have come fairly close and what I have is workable, but I would like to improve on this algorithm as it's a little off and seems to pick down and to the right of the mouse pointer.

I've uploaded a video to help visualize the current implementation: http://youtu.be/EqwWcq1zuaM

My isometric rendering algorithm is based on what is found at this stackoverflow question's answer, with the exception that my x and y axis' are inverted (x increased down-right, while y increased up-right).

Here is where I am converting from screen to tiles:

// these next few lines convert the mouse pointer position from screen 
// coordinates to tile-grid coordinates. cameraOffset captures the current
// mouse location and takes into consideration the camera's position on screen.
System.Drawing.Point cameraOffset = new System.Drawing.Point( 0, 0 );
cameraOffset.X = mouseLocation.X + (int)camera.Left;
cameraOffset.Y = ( mouseLocation.Y + (int)camera.Top );

// the camera-aware mouse coordinates are then further converted in an attempt
// to select only the "tile" portion of the grid tiles, instead of the entire
// rectangle. this algorithm gets close, but could use improvement.
mouseTileLocation.X = ( cameraOffset.X + 2 * cameraOffset.Y ) / Global.TileWidth;
mouseTileLocation.Y = -( ( 2 * cameraOffset.Y - cameraOffset.X ) / Global.TileWidth );

Things to make note of:

  • mouseLocation is a System.Drawing.Point that represents the screen coordinates of the mouse pointer.

  • cameraOffset is the screen position of the mouse pointer that includes the position of the game camera.

  • mouseTileLocation is a System.Drawing.Point that is supposed to represent the tile coordinates of the mouse pointer.

If you check out the above link to youtube, you'll notice that the picking algorithm is off a bit. How can I improve on this?

share|improve this question

4 Answers 4

up vote 5 down vote accepted

What you need is some good old fashioned matrix math.

Let's say you have two spaces:

  • World space - where the tiles reside
  • Screen space - where the mouse pointer is

You want to check the mouse coordinates against the tiles. But first, you will have to convert your mouse coordinates from screen coordinates to world coordinates.

That sounds harder than it is. Let's pretend the tiles are not laid out isometrically, but in a grid. Like this:

Original

I've added numbers that represent the tiles in memory. For example, 0 would be m_Tiles[0].

Here, it's easy to see which tile is selected. In pseudocode:

vec2 pos = mouse.pos - tileset.pos_upperleft;
int index = ((pos.y / tileset.tile_height) * 2) + (pos.x / tileset.tile_width);

In this case, that gives us an index of 2, which is correct.

But watch what happens when we rotate the tileset 45 degrees:

Image2

Now we're selecting a different tile altogether! How can we fix this? Well, in two ways:

  • Rotate the tileset to fit the screen coordinates (world space to screen space)
  • Rotate the mouse coordinates to fit the world coordinates (screen space to world space)

We're going for the first method here. It's great for 2D selection because it allows us to keep the rest of the math the same.

First, let's build a matrix that defines the transformation of our tileset. We want to rotate the tileset by 45 degrees, so we'll take a 3x3 matrix (2D matrix) and add a rotation. Then we inverse the result. Think of it this way: to go from world space to screen space, we must rotate by 45 degrees. So to go the other way around, we must rotate by -45 degrees.

Now the code becomes:

mat3x3 transform = mat3x3.identity();
transform.rotate(45.0);
transform.inverse();

vec2 tileset_transformed_pos = transform * vec3(tileset.pos_upperleft.x, tileset.pos_upperleft.y, 1.0);

vec2 pos = mouse.pos - tileset_transformed_pos;
int index = ((pos.y / tileset.tile_height) * 2) + (pos.x / tileset.tile_width);

As you can see, we use the transform matrix to transform the upperleft corner of the tileset. Now the tileset coordinates are in the same space as the mouse coordinates.

But we're not there yet. The tiles are also squashed a bit. The same effect can be achieved by scaling the matrix by a certain amount:

Squashed

Here I've scaled the whole tileset on the y-axis to 63%, which looks remarkably like an isometric projection. The full pseudo-code:

mat3x3 transform = mat3x3.identity();
transform.rotate(45.0);
transform.scale(1.0, 0.63);
transform.inverse();

vec2 tileset_transformed_pos = transform * vec3(tileset.pos_upperleft.x, tileset.pos_upperleft.y, 1.0);

vec2 pos = mouse.pos - tileset_transformed_pos;
int index = ((pos.y / tileset.tile_height) * 2) + (pos.x / tileset.tile_width);

Obviously you will have to tweak this a bit, but it should give you a sense of where to begin.

share|improve this answer
    
What does int index represent in the last two code blocks? –  Cypher Jun 25 '12 at 15:31
    
I've expanded my answer a bit to hopefully clear things up. ;) –  knight666 Jun 25 '12 at 19:11
    
Thanks. I've discovered the issue (admittedly, it came to me while I was preparing a fork of my code to attempt to implement the many changes needed to accommodate your method), but will make an attempt to implement this method for the game. Appreciate the clear explanation. –  Cypher Jun 26 '12 at 3:41

Seems that adding or substracting 0.5f to your calcs would do the trick... you only should be careful with negative tile coords, then should negate the sign of 0.5f

Here you a are a video where I show how it's done (code included)

though I'm working in 3D projecting to isometric 2D, the facts are the same.

share|improve this answer
    
Event though I didn't think this would do anything, I gave it a shot anyway. Didn't work. :) Thanks, anyway. –  Cypher Jun 26 '12 at 3:45
    
something similar works for me... ;) thoug the transform calcs are very different –  Blau Jun 26 '12 at 7:51

I'm implementing a tile editor for isometric tiles, like you, and I'm having a problem with XNA and the GraphicsDeviceControl Microsoft gives as an example. The thing is I want to read the Mouse state (wich is read in the Update method in a classic Game class), but the class GraphicsDeviceControl doesn't implement it so I can't override it. What can I do? And sorry for my english if it's kinda weird, thanks!

share|improve this answer
    
When working in Windows Forms, the execution model changes from procedural (games) to event-driven (windows). This means that you aren't polling the mouse location every frame. Instead, your application responds to events fired by objects. Either override an event or assign an event handler. I track my mouse inputs via event handlers on MouseMove, MouseUp, and MouseDown. –  Cypher Jun 26 '12 at 3:25
2  
Your question here is also better asked as a separate question, as opposed to an answer to this question. :) –  Cypher Jun 26 '12 at 3:26
    
I know, i'm sorry but i couldn't post a new question cause I don't have enough reputation or something. Thanks for the quick response ;) –  Nico Jun 26 '12 at 3:42

After taking a closer look at the programmed algorithm and what I had on paper, I had left out a modification on the Y-axis before the conversion. I needed to subtract half of the tile height during the stage where I adjust the mouse position to take the camera into account.

The final algorithm looks like this:

// these next few lines convert the mouse pointer position from screen 
// coordinates to tile-grid coordinates. cameraOffset captures the current
// mouse location and takes into consideration the camera's position on screen.
System.Drawing.Point cameraOffset = new System.Drawing.Point( 0, 0 );
cameraOffset.X = mouseLocation.X + (int)camera.Left;
cameraOffset.Y = ( mouseLocation.Y + (int)camera.Top ) - ( Global.TileHeight / 2 );

// the camera-aware mouse coordinates are then further converted in an attempt
// to select only the "tile" portion of the grid tiles, instead of the entire
// rectangle. this algorithm gets close, but could use improvement.
mouseTileLocation.X = ( cameraOffset.X + 2 * cameraOffset.Y ) / Global.TileWidth;
mouseTileLocation.Y = -( ( 2 * cameraOffset.Y - cameraOffset.X ) / Global.TileWidth );

This provides the accuracy I was looking for in my zone editor. Although, knight666's answer looks much more elegant (and makes more sense), re-writing the input and drawing code isn't something I feel like spending time on for the editor. I will, however, attempt to use his answer for the game.

share|improve this answer
    
A note for future readers: this method isn't so accurate when the tile coordinates are in the negative, but accurate (seemingly) to the pixel in the positive. Looking at knight666's answer, the red tile would be my (0,0) with the x-axis increasing down-right and the y-axis increasing up-right. –  Cypher Jun 26 '12 at 3:51
    
lol... see my answer for that... when they are negative then should negate what you add... ;) –  Blau Jun 26 '12 at 7:49
    
It doesn't quite work out as you seem to imply; there's more to it (our calculations are quite different). I just don't have the motivation to make it any better since all my maps only render from world(0,0) on up and won't go into the negative anyway. –  Cypher Jun 26 '12 at 14:49

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.