# Isometric coordinate maths not quite right

I have written two functions, screenToCoords and coordsToScreen.

Screen being the coordinates on screen.

Coords being isometric coordinates.

So here are my functions:

Screen to isometric:

Vector2 TileMap::screenToCoords(Vector2 coords){
coords.x -= offset.x;
coords.y -= offset.y;
Vector2 newCoords;
newCoords.x = (coords.x / (TILE_WIDTH/2) + coords.y / (TILE_HEIGHT/2)) /2;
newCoords.y = (coords.y / (TILE_HEIGHT/2) - (coords.x / (TILE_WIDTH/2))) /2;
return newCoords;
}


Isometric to screen:

Vector2 TileMap::coordsToScreen(Vector2 coords){
Vector2 newCoords;
newCoords.x = (coords.x - coords.y) * (TILE_WIDTH/2) + offset.x;
newCoords.y = (coords.x + coords.y) * (TILE_HEIGHT/2) + offset.y;
return newCoords;
}


And these seem to work totally fine, except when dealing with mouse coordinates, as you can see here: I can't figure out what the issue is.

Basically what's happening in this image is that I'm converting mouse coordinates into isometric coordinates, then back into screen coordinates so that I can render that highlight box.

• Did you take a look at this? At around quarter-third of the article he shows a formula to convert from screen space to isometric and back. – Bálint Jan 29 '17 at 23:17
• I based my code off this. It just doesn't seem to be working with my code for some reason... – genfy Jan 29 '17 at 23:26
• Make sure your tile-drawing offset is set correctly, you may need to start drawing a full tilewidth left of where you currently are. Here's a link to a related question regarding screen-space (client) and game-space (server). In my answer there I included a link for coordinate conversion and isometric tilemaps that I think is easier to read than the link you used. I also linked a nice SO isometric answer about isometric drawing with some very good diagrams. – TOM__ Jan 30 '17 at 5:49
• Also double-check the parenthesis in the coordinate conversion formulas. It never hurts to debug by forcing order of operations with an obscene amount of parentheses. Here's a link to operator precedence in c++. – TOM__ Jan 30 '17 at 6:04

Like I stated in my comment to your question, double-check your offset, everything else you're doing seems fine.

I faced a similar problem with my isometric game. It turned out I needed to account for the tileset (ie source image) drawing offset, and a logical/game-space offset.

My isometric game operates in a standard 2D top-down mode. That is, I handle all collision, path-finding, and other mechanics in the 2D top-down space. It is only at render-time that anything needs to be converted to look isometric.

I hard-coded a solution to the not quite right isometric math during game startup that also caches the offset world-coordinates of all tiles. The top-left corner of the logical tile position in world-space is converted to isometric and saved in my 2D tile array:

// row and column are just the indexes of the 2D array where eTiles are stored
// logical cell width == logical cell height == 32 in my case,
// although the actual source image has tiles 64w x 64h in pixels (including alpha space)
// and the actual tile being rendered is visually 64w x 32h pixels
eTile & tile = cells[row][column];
eVec2 tileOrigin = eVec2((float)(row * tileMap.CellWidth()), (float)(column * tileMap.CellHeight()));
eMath::CartesianToIsometric(tileOrigin.x, tileOrigin.y);
tileOrigin.y -= 16; // tileset specific offset, not all tileset images are created equal
tileOrigin.x -= 32; // logical-to-screen isometric coordinate offset to account for the 64w x 64h tile images used
tile = eTile(tileOrigin, type, layer);


These two isometric tiles from Clint Bellanger are visually 64 pixels wide x 32 pixels tall, but the rectangle used to clip them from the tileset is 64 x 64. Moreover, my game operates on 32 x 32 sized cells whose dimensions become 64w x 32h when converted to isometric. Overall confusing, but it amounts to adjusting the offset for each tileset used. (side-note: They are centered differently in their 64 x 64 clip rectangles to give the further illusion of a deeper pool of water relative to "ground level".)

As an example of how I use this:

Similar to your tile-highlighting, I have a function executed each frame which allows me to change the properties of a tile under the mouse position:

if (input->MousePressed(SDL_BUTTON_RIGHT)) {
// get mouse position in the window/screen (0,0 at top-left),
// and top-left corner of 2D camera in world-space and convert to cartesian
// (ie coordinates consumable by my 2D tile array as seen in ToggleTile function)
eVec2 tilePoint = eVec2((float)input->GetMouseX() + game.GetCamera().GetAbsBounds().x, (float)input->GetMouseY() + game.GetCamera().GetAbsBounds().y);
eMath::IsometricToCartesian(tilePoint.x, tilePoint.y);
ToggleTile(tilePoint);
}


Its important to note here that the method I use assumes the gathered coordinates are already isometric and need to be converted to Cartesian.

ToggleTile(tilePoint) then gets the tile-array index of the tile currently under the mouse so it can set that tile's display properties (from grass to water for example):

void eMap::ToggleTile(const eVec2 & point) {
// ...
row = (int)(point.x / cellWidth);       // cellWidth == 32
column = (int)(point.y / cellHeight);   // cellHeight == 32
eTile & tile = cells[row][column];
// ...
}


Regardless of whether I cache my tile coordinates, my methods for converting from isometric to Cartesian are as follows:

//************
// eMath::IsometricToCartesian
// rotates input coordinates 45 degrees counter-clockwise
// DEBUG: assumes the input coordinates are isometric
//************
inline void eMath::IsometricToCartesian(float & x, float & y) {
float isoX = x;
float isoY = y;
x = (2.0f * isoY + isoX) * 0.5f;
y = (2.0f * isoY - isoX) * 0.5f;
}

//************
// eMath::CartesianToIsometric
// rotates input coordinates 45 degrees clockwise
// DEBUG: assumes the input coordinates are cartesian
//************
inline void eMath::CartesianToIsometric(float & x, float & y) {
float cartX = x;
float cartY = y;
x = cartX - cartY;
y = (cartX + cartY) * 0.5f;
}


I hope this points you in the right direction to fixing your issue.

As a side-note: I cache all the tile coordinates at startup so I don't need to constantly divide by tile dimensions to get the screen position at render time. All I have to do is grab the already isometric tile origin and offset it by the camera world-coordinates:

            eVec2 screenPoint = eVec2(
eMath::NearestFloat(tile.Origin().x - game.GetCamera().GetAbsBounds().x),
eMath::NearestFloat(tile.Origin().y - game.GetCamera().GetAbsBounds().y)
);


Is the problem in the image that its not smooth? If thats the case then you're getting integer values when you want floats. Make sure Vector2 contains floats or doubles, and make sure you cast correctly when multiplying and dividing.

• Okay, so I switched the integers in Vector2 with floats, I then floored each axis in screenToCoords() to keep it locked to a grid. I then changed the image that I was using for the tiles. This was my result. As you can see it's not a great improvement. – genfy Jan 29 '17 at 22:49
• I'm sorry but I'm not sure what the problem you're having is. Is it that its not accurate? Is the square always supposed to be centered around the mouse? I just suggested the float thing because thats a mistake I always make. – user1152717 Jan 30 '17 at 4:51
• In general if you're casting a float to an int its safer to round to the nearest integer with something like (int)(currentFloatValue + 0.5f); to avoid strange rounding errors. – TOM__ Jan 30 '17 at 5:56

It looks like your mouse pointer's origin is in its top right corner instead of top left, where the finger is. Make sure it's positioned correctly.