4

I'm generating an isometric tile map using a diamond pattern:

tileWidth = 128;
tileHeight = 94;

for (var x = 0; x < rows; x++) {
  for (var y = 0; y < cols; y++) {
    var screenX = (x - y) * tileWidthHalf;
    var screenY = (x + y) * tileHeightHalf;
    drawTile(screenX, screenY);
  }
}

This renders correctly, but now I'm having trouble converting screen coordinates (mouse location) back to the isometric coordinates.

I've tried reversing the math:

var x = _.floor(screenY / (tileWidth / 2) - (screenX / tileWidth / 2));
var y = _.floor(screenY / (tileHeight / 2) + (screenX / tileHeight / 2));

It works fine for the 0, 0 tile but fails to produce the right value afterwards.

I'm just unable to come up with the right math - am I missing something trivial or am I just all wrong about the process?

helion3
  • 34,737
  • 15
  • 57
  • 100
  • Future visitors: Nico's solution (the accepted solution) is the only one that works for me out of many different solutions found on the internet. I am following the article http://clintbellanger.net/articles/isometric_math/ - You MUST substitute screenX with (screenX - tileWidthHalf) as suggested in Nico's answer. See my comment in Nico's answer. Be sure to floor the results. – shell Nov 05 '19 at 18:39
  • Nico's solution is correct, but remember you must use FLOATS for screenx, screeny, tileWidthHalf, and tileHeightHalf or there is not enough precision. I was using ints and it did not work correctly. Changed these to floats and it works perfectly. – chewinggum Apr 13 '20 at 05:27

2 Answers2

5

I don't see how you came up with this solution. You have to solve the system of equation, which gives the following solution:

x = 0.5 * ( screenX / tileWidthHalf + screenY / tileHeightHalf)
y = 0.5 * (-screenX / tileWidthHalf + screenY / tileHeightHalf)

If you need the tile index, use floor as in your code.

I can only guess what your alignment of the tiles in the coordinate system looks like. But from the screenshot you posted in the comments, I assume that you need to swap screenX with (screenX - tileWidthHalf) to get accurate values.

Nico Schertler
  • 32,049
  • 4
  • 39
  • 70
  • I've tried this as well but it seems to produce different values for each tile's left and right side. I'm wondering if the discrepancy comes from the sprite's anchor points being their top left, and not center? – helion3 Sep 27 '16 at 18:17
  • I've labeled the tiles for easier debugging but changing the anchor point didn't help anything. https://puu.sh/rpOic/2782995e22.png - the left half of tile 0,0 reports as -1,0, the right half is correct. The left half of tile `1,0` reports as `-1, 1`, the middle reports as `1, 0`, and the right side reports as `1, 2` – helion3 Sep 27 '16 at 18:35
  • Somehow, this image does not match your formulas unless you have a non-standard coordinate system. Your code suggests that the origin is the left corner, the x-axis points to the upper right, and the y-axis to the lower right. Have you tried to leave the rounding away and see what values you get? I assumed that the tile is drawn in the positive direction of both axes. – Nico Schertler Sep 27 '16 at 18:45
  • I'm not sure in which way the coordinate system is non-standard, rendering may be different because this is webgl, so 0,0 is the top left of the canvas, rather than bottom left in some other systems. Even with different rounding this math doesn't seem to work well. I can get much closer with the math from an article on isometric math: `var x = _.floor((sx / tileWidthHalf + sy / tileHeightHalf) / 2); var y = _.floor((sy / tileHeightHalf - (sx / tileWidthHalf)) / 2);` - thought weirdly this is only accurate for the left half of every tile, so there's an offset issue. – helion3 Sep 27 '16 at 18:59
  • The article I'm referring to is http://clintbellanger.net/articles/isometric_math/ – helion3 Sep 27 '16 at 19:00
  • 1
    I updated the solution for the new code. Ah, I just saw that this is exactly the solution from the blog article. If it is only true for a part of the tiles, then it is a pivot and rounding problem. But since you haven't written anything about that, I cannot tell you what might be wrong. – Nico Schertler Sep 27 '16 at 19:09
  • I'm not sure what else could be the issue. I'm using the coordinate-to-screen method the article does, I'm looping through row/cols three times each, and aside from rendering sprites anchored at 0,0, I don't see any other differences or areas where everything would be off. Here's a reduced copy of my experimental code: https://pste.me/#/UhKOU/mJZDmw8mJAMOdwd38XHSztHsmkUUcttR – helion3 Sep 27 '16 at 19:19
  • That extra subtraction did the trick! I have no idea why though... I'm not clear what could possibly have been pushing everything off by half a tile. – helion3 Sep 27 '16 at 19:37
  • 1
    It's because of a mismatch between your drawing and the coordinate system. You just have to keep in mind that the position of a tile does not actually lie in the tile. – Nico Schertler Sep 27 '16 at 21:25
  • Excellent answer. This is the only answer that gives accurate results out of many many solutions provided on the internet. For those following the article: http://clintbellanger.net/articles/isometric_math/, you MUST change screenX to (screenX - tileWidthHalf) as suggested by Nico. And do not use the answer given in that article, but rather use Nico's. x is then: x = 0.5 * ( (screenX - tileWidthHalf) / tileWidthHalf + screenY / tileHeightHalf); And y is then: 0.5 * (-(screenX - tileWidthHalf) / tileWidthHalf + screenY / tileHeightHalf); – shell Nov 05 '19 at 18:32
2

I'm writing w for your tileWidth and h for tileHeight. Using this notation, you essentially have

screenX = (x - y)*(w/2) = (w/2)*x + (-w/2)*y
screenY = (x + y)*(h/2) = (h/2)*x + (h/2)*y

This is a linear transformation which you can also write in matrix notation like this:

⎛x⎞ ↦ ⎛w/2 -w/2⎞ ⎛x⎞
⎝y⎠   ⎝h/2  h/2⎠ ⎝y⎠

To reverse the operation you need to invert this. There is a simple formula for the inverse of a 2×2 matrix. Swap the top left and bottom right entries. Negate the other two. Divide everything by the determinant. Use that and you have the inverse transformation:

⎛x⎞ ↦ 2/ ⎛ h/2 w/2⎞ ⎛x⎞ = ⎛ 1/w 1/h⎞ ⎛x⎞
⎝y⎠   wh ⎝-h/2 w/2⎠ ⎝y⎠   ⎝-1/w 1/h⎠ ⎝y⎠

So in your notation you get

x = screenY / tileHeight + screenX / tileWidth
y = screenY / tileHeight - screenX / tileWidth

which is essentially what Nico wrote as well. Turning non-integer coordinates back to integers depends on where you place the reference point for each tile.

MvG
  • 57,380
  • 22
  • 148
  • 276