How do I reverse "Cartesian->Isometric" function?

I'm working on an isometric tile rendering engine and I need to reverse a function I used to translate the Cartesian coordinates into isometric coordinates. I've already written a function for this, but I've been getting a strange logical error and I think it must be somewhere in my code for translating from isometric coordinates back into Cartesian. Anyways, here's the code for translating Cartesian->Isometric

private Vector2 cartToIso(Vector2 init){
Vector2 iso = new Vector2();
iso.x = (((init.x - init.y) * TILE_WIDTH_HALF))+X_OFFSET;
iso.y = MAP_HEIGHT-TILE_HEIGHT-((init.x + init.y) * TILE_HEIGHT_HALF);
return iso;
}

Any suggestions would be appreciated. Also if anyone was wondering, what I'm trying to do right now is create an algorithm for tile picking in my engine. The API(LibGDX) is a little strange in the sense that it uses a bottom-up Y coordinate system but with any of the input(ie, clicks) it uses a top-down Y coordinate system. I can post the code I have for tile picking later if anyone thinks they can help me more, but I'll save it for now since it's pretty long.

Edit: This is the method I use to reverse it, and it seems to be giving me the incorrect results. Does this look correct?

private Vector2 isoToCart(int x, int y){
Vector2 cart = new Vector2();
x = x-X_OFFSET;
y = y-MAP_HEIGHT;
cart.x = (x / TILE_WIDTH_HALF + (y / TILE_HEIGHT_HALF)) /2;
cart.y = (y / TILE_HEIGHT_HALF -(x / TILE_WIDTH_HALF)) /2;
return cart;
}
• Can you describe what the "strange logical error" you're encountering is? What are the symptoms? This will help people searching for similar problems in future find this post and its answers. Mar 24 '15 at 15:47
• No problem at all. When you try to select tiles approaching x=0, the x and y coordinates that it selects are off by 1. X is usually incremented and the Y is usually decremented. Mar 24 '15 at 22:40
• Anyone have any suggestions? Mar 25 '15 at 23:13

If you ignore the constants you have in your calculations (such as tile width, for example) the calculations for isometric to cartesian and back are usually;

float isometricX = cartesianX - cartesianY;
float isometricY = (cartesianX + cartesianY) * 0.5f;

And the reverse is

float cartesianX = (2.0f * isometricY + isometricX) * 0.5f;
float cartesianY = (2.0f * isometricY - isometricX) * 0.5f;

That's assuming square tiles of unit length.

I'm not sure how your MAP_HEIGHT plays into this, but you should be able to scale that by your tile size to get the values need for your game.

Edit; To use this to pick a tile the following method could be used (note that here tileSize is the height of the tile, so in your case 32px);

private Vector2 screenToMap(float screenX, float screenY) {
float mapx = (screenX / tileSize + screenY / (tileSize * 0.5f)) * 0.5f;
float mapy = (screenY / (tileSize * 0.5f) - (screenX / tileSize)) * 0.5f;
return new Vector2(mapx - 0.5f, mapy + 0.5f); // -.5/+.5 because the drawing isn't aligned to the tile, it's aligned to the image
}

private Vector2 cartesianToIsometric(float cartesianX, float cartesianY)  {
float isometricX = (cartesianX - cartesianY);
float isometricY = (cartesianX + cartesianY) * 0.5f;
return new Vector2(isometricX, isometricY);
}

The above doesn't take a viewport into account, so you'll have to do panning and zooming yourself, or you could rely on libGDX's viewport.

I knocked up a pure java example of this where I draw the map, get the map coordinate of the mouse and pick tiles; Isometric sandbox

(again, this isn't actually isometric but I think it's what you are looking for).

Edit: When doing this in a libGDX application, under a OrthographicCamera, you need to unproject screen coordinates to camera space, then call my screenToMap function as that works on unprojected coordinates.

So at the beginning of the map's render you need to do something like this;

public Vector2 unproject(OrthographicCamera camera, Vector2 position) {
Vector3 unprojected = camera.unproject(new Vector3(position.x, position.y, 0));
return new Vector2(unprojected.x, unprojected.y);
}

private Vector2 screenToMap(Vector2 screen) {
float mapx = (screen.x / tileSize + screen.y / (tileSize * 0.5f)) * 0.5f;
float mapy = (screen.y / (tileSize * 0.5f) - (screen.x / tileSize)) * 0.5f;
return new Vector2(mapx - 0.5f, mapy + 0.5f); // -.5/+.5 because the drawing isn't aligned to the tile, it's aligned to the image
}

public void render(OrthographicCamera camera, Vector2 mousePosition) {
Vector2 mp = screenToMap(unproject(camera, mousePosition));
spriteBatch.setProjectionMatrix(camera.combined);

Fact that libGDX has a y-axis that increases as you go up on the screen doesn't matter, the unproject will fix that for you as you'll also render in the different direction.

For sake of completion, here's a link to a libGDX application of what I am explaining here: libGDX source. It will render a map and highlight the cell you're over with the mouse.

I assume you're doing this because you want to learn, because if you just want a isometric map in your game, libGDX comes with Tmx-classes that can load and render a isometric map created in for example Tiled.

• Minor caveat: the coefficients you've given here are an "almost-isoetric" dimetric projection, which is often used as a stand-in for isometric in games because it keeps the measurements in rational numbers. A true isometric projection will use a factor of 1/sqrt(3) Mar 24 '15 at 15:45
• @DMGregory You are correct. I used that projection because I think that's what OP wants, based on his cartesian-to-iso calculation. I should have pointed that out in my answer. Thanks for pointing it out. Mar 24 '15 at 19:34
• I should have included more information in the OP. Anyways, my tiles are 64x32. The MAP_HEIGHT value is being used as a temporary camera. It's only to offset the map so that it draws the map starting from the bottom of the screen. I can post more of my code inside the OP if that would be useful. Also, where would I place my constants inside of that method? My brain feels really fried with this error and I can't really continue my project until it's resolved. Mar 24 '15 at 22:45
• Hmmm I've copied your implementation to the T but still can't get it. I think it has something to do with LibGDX's way of handling graphics with the inverted Y coordinate for drawing but the standard Y coordinates for input. I've tried converting but it still doesn't work and gives me incorrect results. Can you take a look at this? There are two implementations of my "Map" object included. "Map" is the original and "Map2" is what I tried based on your recommendations. Just uncomment the call for it's getTileAt method in the core class. dropbox.com/s/9em5ylidpq9ddai/IsoTest.rar?dl=0 Mar 26 '15 at 9:07
• Can do, I'll have a look at this after work today. Mar 26 '15 at 9:37
function isoToCart (isoX, isoY, isoZ = 0) {
const x = (isoY - isoX) / 2;
const y = (isoY + isoX) / 2 - isoZ;
return { x, y };
}

function cartToIso (cartX, cartY) {
const x = cartY - cartX;
const y = cartY + cartX;
return { x, y };
}

above are reversible no need to thank me by +1

sorry for javascript but you get the math