Get row and col of mouse click on Isometric map

I have been searching and trying for days and I cant seem to get anything going with this.

I have a map thats 10 rows x 10 cols stored in an array and its gets drawn starting with 0,0 of the array at the top of the diamond then it draws from there going down and to the right. It then goes to the next row, which would be to the down and to the left of the previous tile and works its way down and to the right. It continues this until all the tiles are drawn.

My tiles are 32 wide by 16 high.

This is the function I use to draw the Isometric map…


//Draw Map
x = 144;
y = 2;
if(tilesLoaded = true){
	for(int i=0;i<rows;i++){
		x = 144 - (i*(tileWidth/2));
		y = 2 + (i*(tileHeight/2));
		for(int j=0;j<cols;j++){
			
			g.drawImage(tile[mapArray[i][j]].img,x,y,null);
			x = x + tileWidth/2;
			y = y + tileHeight/2;
			
			//Draw Coords if set to true
			//if(showCoords) g.drawString(""+j+","+i,(j*tileWidth)+4,(i*tileHeight)+16);
		}
	}
}

I realize this question has been asked a few times but I cant understand the solutions I’ve found. I would really appreciate any help anyone can offer.

Thanks!

Ok, never programmed or played an isometric game, so not sure on correct/mathematical solution but I guess this could easily be achieved by Shapes and its contains method (in absence of a more elegant solution).

eg. Some crappy pseudo code (in your mouseclick method)

for no_cols
for no_rows
Shape shp = … // (Create your diamond shape object offsetting all your x/y coords of course).
if (shp.contains(mouseX, mouseY) {
// You’ve got ur coords :smiley:
}
}
}

In my isometric games I typically just have two public functions in my Level class (as the perspective might be different every level).


//Converts the passed grid-based point to an isometric point.
public Vector2 convertPointToIsometric(Vector2 point)
{
    //This is the coordinate of the top-left usable space. It's like transforming the origin on a graph.
    //I only have this because there is a lot of wasted space with an isometric transformation.
    point.add(isometricOffset);

    //Now that the offset is applied, go from there to the center of the playing area.
    //You need to rotate from the center when applying the isometric transform.
    point.subtract(isometricPivot);

    //Do the actual rotation with the point. If you call this function a lot, it's a good idea to
    //cache the sine and cosine values. A usual isometricRotation is 45 degrees.
    point.rotate(Math.cos(isometricRotation), Math.sin(isometricRotation));

    //Scale the Y position so it looks like it's squished into the distance.
    //Typical isometric scales are 0.666 and 0.5. I use 0.57.
    point.y *= isometricScale.

    //Now we want to go back to the top left corner (we don't want to start from the pivot).
    point.add(isometricPivot);
}

//Converts the passed isometric point to a grid-based point.
//This is literally exactly the same except everything is done in reverse order and opposite operations.
public Vector2 convertPointFromIsometric(Vector2 point)
{
    //Go from the corner to the pivot.
    point.subtract(isometricPivot);

    //Unscale the Y position so it's back to grid proportions.
    //Typical isometric scales are 0.666 and 0.5. I use 0.57.
    point.y /= isometricScale.

    //Do the actual rotation with the point. If you call this function a lot, it's a good idea to
    //cache the sine and cosine values. A usual isometricRotation is 45 degrees.
    point.rotate(Math.cos(isometricRotation), -Math.sin(isometricRotation));

    //Now that we're back to normal, un-apply the pivot.
    point.add(isometricPivot);

    //Finally, subtract the offset so that we're back at (0,0).
    point.subtract(isometricOffset);
}

If you need to know how to do any of those functions listed above, just ask. It’s all simple vector math.

The best way to think about an isometric game is this:

  • All logic exists in a regular non-transformed space. In other words, to move something to the right, you just add to its X. To move something down, just add to its Y (or subtract, depending on your axes).
  • When something is drawn, its “real” position is translated to a viewed isometric position. Then you just draw it at that position, and you also rotate it by the isometric rotation amount.
  • When the user clicks somewhere on the screen, simply translate that point from an isometric point back to a regular non-transformed point. You can then use this point to collide with objects, etc.

That’s pretty much it. In general, it’s good to remember that any sort of fun graphical transformation that you perform should remain separate from the logic. It’s all visual, so why make your life very difficult (and slow) by calculated a rotated axis to actually move along or something like that? Think of it like a 3D camera - you only move your objects as expected in the X,Y,Z directions, but the camera itself (usually unseen to the programmer) does a lot of weird transformations and things to make the objects appear the correct way. You wouldn’t actually do that directly with the positions or it would be a nightmare. The same concept applies for all graphical transformations, even if they’re simple 2D ones.

Thanks for the input. I appreciate the help.

SteveyO - I was thinking about something similiar to your idea. One I was looking at was to create a shape based on the mouse click coordinates, then rotate and squish the shape and return the new coordinates. Then check if those coords are inside a tile or something along those lines. I also thought of checking for collisions or going that route. I may still try one of these or your method.

DemonPants - The ideas and things your explaining there are a bit over my head. I have no idea about vector math and I cant remember anything about sin and cosine. I havent ever worked with vectors or points, so I would have to read up on them. Currently I am not doing any kind of transformation to create the ISO perspective. The tiles I use are already rotated and squished, then drawn to the screen based off an array. I will have to spend some time looking over your suggestion.

I will post back my findings.
Thanks again for the suggestions

[quote=“Xyle,post:4,topic:34873”]
Au contraire, you are doing a transformation. They had to be rotated and squished at some point, no? :slight_smile:

It sounds like you want to make the logic all happen on a rotated and squished plane as well (to match your tiles). I will once again say that this is a very bad idea and will cause you lots of headaches in the future. You will need to remember to rotate and squish every single action you do in your game (moving units, etc.). You want to pretend everything is on a very simple non-rotated-squished plane within logic, and then only draw them otherwise.

So it would look something like this:


public class Entity
{
    private Vector2 pos;
    private BufferedImage image;

    //Move this Entity around. Just adding to dx will move it down-right, subtracting will move up-left.
    //Just adding to dy will move it down-left, subtracting will move it up-right.
    public void move(float dx, float dy)
    {
        pos.x += dx;
        pos.y += dy;
    }

    //Draw this Entity.
    public void draw(Graphics2D g)
    {
        //Here is the only place that we need to use rotated and squished coordinates.
        Vector2 transformedPoint = convertPointToIsometric(pos);

        //Draw it with our transformed position.
        g.drawImage(image, transformedPoint.x, transformedPoint.y, null);
    }
}

And here is the code for all the Vector2 class.


public class Vector2
{
    public float x, y;

    public Vector2(float setX, float setY)
    {
        x = setX;
        y = setY;
    }

    public void add(Vector2 other)
    {
        x += other.x;
        y += other.y;
    }

    public void subtract(Vector2 other)
    {
        x -= other.x;
        y -= other.y;
    }

    public void rotate(float cosine, float sine)
    {
        float newX = x * cosine - y * sine;
        float newY = x * sine   + y * cosine;
	
        x = newX;
        y = newY;
    }
}

There are obviously plenty more things you can put in a vector class (like multiplication, division, dot product, etc.), but I just put in the things you would need for the code I put above.

If that still doesn’t make sense to you, I can draw you a picture that would probably help a lot.

I definitely see your very well made point here and I can easily see the benefit of working with a standard ortho grid setup then just using the isometric functions to only display the map. I spent all day yesterday just figuring out how to paint the selected tile and it really threw me for a loop. I was making the iso tiles in gimp, basically just drawing in the rendered iso tile area which was 32x16, save it as a png and draw it on the map. I did draw on a normal 32x32, rotated it and scaled its height by .5, but it didnt look much different then just drawing on the tile that was already rotated.

Just to make sure I am understanding you, you are saying to perform all the calculations, movements, placements, checks and game logic on a standard 10x10 grid, then convert the output to an isometric map and render it, right?

I like this idea, this would really speed up things. I cant believe all the searching and checking suggestions from google, no one mentioned this. When I google “convert 2d mouse coordinates “Isometric”” all the return sites for the first 6 pages show visited links, lol. Thats a lot of reading.

I will spend some time here to try to implement your suggestions and supplied code. I know I’m already upside down though because even constantly going back to your post yesterday, I couldn’t really understand how your achieving this.

So I will go ahead and throw the questions out there right off the bat (Again, I really, really appreciate the help!)


//This is the coordinate of the top-left usable space. It's like transforming the origin on a graph.
//I only have this because there is a lot of wasted space with an isometric transformation.
point.add(isometricOffset);

I dont know what this is for and what isometricOffset would be. I can see your adding this to a instantiated vector2 object. If the Vector2 just got instantiated, then the original x and y would be 0, 0.

“This is the coordinate of the top-left usable space”, which means that all your really doing is adding, in the case of a regular 2d grid, the origin which would be 0,0, the top left coordinate? Why are you calling it isometricOffset?

so basically point.add(isoetricOffset) is setting the initial values of x, y of a newly instantiated Vector2 object, right?

Please say correct! Then I can move on, hehehehe. :0

Actually, for the sake of your sanity, just totally forget the offset for now. You can view it as more of a “fudge factor” for when you want to have the isometric space be different from a direct translation. Without an offset, you will get an exact translation, which may or may not suit your purposes. It’s probably a good idea to just start with the rotation and the squish, and then if you need to move the translated coordinates around to match some, add an offset in.

I originally used the functions I provided for a game that has logic overlaid on a giant rendered background. A level editor allows you to drag around a transformed grid until you get it matched up to the background image, then it spits out the values (like the isometricOffset and isometricPivot) that you’ll need. Doing this manually can take quite a bit of tweaking. Since you seem to be doing your game more in a tile-based sort of way (every element drawn is its own tile), you really shouldn’t have to worry about offsets at all. Oh, and all the values I used in the function (isometricOffset, isometricPivot, isometricScale) I was assuming were class members that were already set elsewhere. So the isometricOffset could be (0,0) , but would more likely be something pulled out of a level file or typed in manually (like (115,568.5) or something else wacky).

The math I put in is pretty much the same as what you’ve been doing, except you probably did it in an image program or something. Either way, the basic thing to keep in mind is what you’ve figured out - just keep all the logic in a very simple non-rotated 2D grid, then transform it all when you draw it.

I still cant get this. I dont understand the math. In reality if you have a point or coordinates and you rotate them 45 degrees, then squish the y coord, you should get an accurate map coordinate in lieu of a real time screen coordinate. I have tried your code just to convert the mouse click coordinates to iso map coordinates and they are no where close to what they should be as well as every internet search code I have found, which tells me my brain is not accepting the data.

Here is a pic of my map which is 10 rows x 10 cols in a simple array
The map array draws the images based on the code above which reference iso tiles that are already rotated and scaled on the y axis.

When a user clicks 160,0 in real world coordinates, the map should say the user clicked 0,0 in iso coordinates as displayed in the pic.
My tiles or cells that contain the tiles are 32 pixels wide and 16 pixels tall.

Here is the pic…

Demonpants, I tried your code that converts coords to iso coords, but they didnt give me the coorect values. I realize that in the end I will opt to use the (figure everything in a 2d grid, then just draw everything in an iso view) state, but right now I really need to find a solution to this part of my endeaver. Here is a the code I used to try and figure the iso coords of the mouse click…


	//Converts the passed grid-based point to an isometric point.
	public Vector2 convertPointToIsometric(Vector2 point)
	{	//Do the actual rotation with the point. If you call this function a lot, it's a good idea to
	    //cache the sine and cosine values. A usual isometricRotation is 45 degrees.
	    //point.rotate((float)Math.cos(45), (float)Math.sin(45));
	    point.rotate(45,45);
	
	    //Scale the Y position so it looks like it's squished into the distance.
	    //Typical isometric scales are 0.666 and 0.5. I use 0.57.
	    point.y *= 0.5;
	    
	    return point;
	}

and here is the code i used to call the function…


	public void convert2Iso(int tX, int tY){
		Vector2 tVect = new Vector2(tX,tY);
		Vector2 tVect2 = convertPointToIsometric(tVect);
		System.out.println("MapX: "+tVect2.x+",MapY: "+tVect2.y);

	}

This is using your Vector2 class of course. All I was trying to do was to convert a 2d mouse click coordinate to an iso coordinate where the iso map 0,0 starts at ortho map 144,0.

Man this is tough.

thanks for any and all help though!!

I’m guessing that your main problem is two things.

One is that you plugged 45 and 45 into the rotate function, rather than the sine and cosine values. And actually the example I gave you is wrong too, because you need to use radians rather than degrees. So, the line of code should be:


//PI / 8 radians is equivalent to 45 degrees.
point.rotate( Math.cos(Math.PI / 8.0f), Math.sin(Math.PI / 8.0f) );

But yeah, you definitely don’t want to put straight-up degree or radians in there, you need to put in the calculate cosine and sine values. Alternatively, you can adjust the rotate function so that it just takes a measurement, but then you can’t cache the sine and cosine which will be very very slow to calculate every time.

The other problem is that the mouse coordinates, because they are already in the user’s visual space, should actually be translated from isometric. You can consider the mouse to already be translated, as the user is viewing an isometric map and they are the one manipulating the mouse. Thinking of the 3D example again, if you have a bunch of objects in 3D space and you want the user to be able to click to select one of them, you need to do a raycast to find what they clicked on. This is, effectively, transforming the player’s mouse coordinate (which, as they view it, is hovering right over top of what they click, but in engine space the object obviously isn’t) to a point in 3D space. You’re once again taking the same idea into your simpler isometric transformation.

Does that make sense?


//PI / 8 radians is equivalent to 45 degrees.

45 degrees != PI/8 radians
45 degrees = PI/4 radians

Well, I’ve spent about 8 hours or so today on this. I tried mapping the coordinates in excel and found out that I couldnt map out the map coordinates the way I wanted to, which of course means Im thinking wrong about something here. I wanted a tile coordinates to read as 0,0 from the top of the diamond to 16,0 to the bottom right of the diamond
and
0,0 from the top of the diamond to 0,16 to the bottom left of the diamond.
When I tried mapping these coordinates out in an excel file. I couldnt do it…

Heres a pic of the real world coordinates…

I believe due to the staggering, it cant be represented the way I was wanting it to. I do understand the coordinates under the mouse are the real coordinates and the user clicks what he sees. I did rotate the point in the code you provided using the cosine and sine. I didnt get the results I was looking for so I was doing some trial and error, hehehe. Since then I went back to my original program which draws a standard grid. I rotated that and tried scaling down the y of the rendered image and that didnt turn out right. I then attempting to rotate and scale each tile then draw the image, which I couldnt get to render correctly. I did learn about the radians and degrees by making this attempt.

In the end, I looked at it and asked myself, what is the exact reason for needing the Map Coordinates to be 0,0 at the top of the diamond, 16,0 at the bottom right? I’m not really going to be using those coordinates for anything so why am I spending so much time trying to figure them out? I will try out your code though just to see what it gives me.

Again I really appreciate the time and effort your putting in here. Thank you.
I will post any futher progress on this.

Crap, yes, this is true. That’s what happens when I write posts too quickly. Thanks for the catch.

Now that we have a tutorial section, Xyle, I was thinking I might make a quick skeleton project that allows a player to move around an isometric grid with the keyboard or something like that. If you can stand to wait for a few days, I can have the program done and you can have a look at it.

Ohhh man that would be truly outstanding!!
Please, please put in mouse clicks to select tiles!!

I just spent about 14 hours today of trial and error, rereading this post and scouring the web for other ideas and still cannot get the tile pick corrected.

I realized that the reason you need some sort of map coordinates is to let you know what tile your choosing, duh! The problem is, there are different types of coordinate systems people use for this and trying to make one work for my map isnt getting it done.

Of course I have more than a few days, I’ve been going over this same problem now for about 4 days and I’m still no where closer than when I started.

Thank you very much for the time and effort!

For what it’s worth, there’s a chapter in Killer Game Programming that discusses isometric tile maps.
Simon

I’ve got the next 3 days off from work (starting tomorrow, due to a game release), so I’ll probably be able to make this sometime tomorrow. I’ll make sure to let you know when it’s up.

Demonpants - Awesome! Cant wait to see it!

Dishmoth - I appreciate the info. KGP was one of the resources I referenced, even though it was a staggered map, which uses 1 gif file for the map and not indiviual tiles and uses keystrokes to move the player. In my multiple days of looking at this, I have read, re-read and made tons of attempts to apply other peoples formulas and ideas and I am not 1 bit closer to picking an iso tile on my map. I am thinking about using a brute force approach, even though they are always inefficient, I just need to get something to work.

Thanks for the input!

You might try the solution this site lists (at the bottom of the page)

http://trac.bookofhook.com/bookofhook/trac.cgi/wiki/OverviewOfIsometricEngineDevelopment

make sure to take note:

[quote]The basic equation is simplistic in that it doesn’t take into account any translation (scrolling) or other offsets for toolbars and user interface, but those are easy enough to substitute back in.
[/quote]

Thank you for the help. I did check that page out in my never ending scourge of the net to locate a solution. One of the problems with the solution he offers

“The fundamental realization is that you can just invert the map-to-screen transformation (used during rendering).”

I am not doing this, I am not transforming anything, just basically painting a rectangle in a staggered fashion.

I did look into actually using square 32x32 tiles , rotating, scaling then painting them, but the results were a bit beyond my limited capabilities. It was easier to create the iso tile in gimp, then just draw the alpha’d rectangle to the screen.

I will keep plowing though and I will re-re-re-re-read the article. Thanks!

Okay, here comes some maths. Hold on tight!

We’ll use x and y to label the pixels on the screen, and i and j to label the rows and columns of the tile map, just like in the code you posted at the start. In particular 0 <= i < 10, and j similarly.

We’ll take the ‘origin’ as the pixel right at the top of the map as it appears on the screen. Label it (x0,y0). For your code,
x0 = 144 + tileWidth/2
y0 = 2
(Note that this point is mid-way along the top edge of the top rectangle, not at the rectangle’s top-left corner.)

The top pixel for a tile can then be worked out as
x = x0 - itileWidth/2 + jtileWidth/2
y = y0 + itileHeight/2 + jtileHeight/2
You can see that for top tile (i,j)=(0,0), the top pixel is just the origin, (x,y)=(x0,y0).

Now if we do a bit of algebra,
(x - x0)/(tileWidth/2) = -i + j
(y - y0)/(tileHeight/2) = i + j
and then
i = (y - y0)/tileHeight - (x - x0)/tileWidth
j = (y - y0)/tileHeight + (x - x0)/tileWidth

So, given a pixel location (x,y), the equations above tell you which tile (i,j) the pixel is part of.

To turn these equations into code you’ll need to be careful converting between floating-point numbers and integers. Something like the following:

int x = ??,
y = ??;
int tileWidth = ??,
tileHeight = ??;
int x0 = 144 + tileWidth/2,
y0 = 2;
int i = (int)Math.floor( (y - y0)/(double)tileHeight - (x - x0)/(double)tileWidth ),
j = (int)Math.floor( (y - y0)/(double)tileHeight + (x - x0)/(double)tileWidth );

Finally, check that ( i >= 0 && i < 10 && j >= 0 && j < 10 ) to be sure that the pixel (x,y) is part of your map. Then you’re good to go with the tile’s row and column!

Is that something like what you’re after? It’s really just what Demonpants was saying, but without the vectors.

Simon

It’s best to get your head around the maths, but there is another way!
You could use a lightweight 3D engine (eg Jpct) to locate and render the tiles in ‘world space’ then use the poly-picking facilities of the engine to tell you which tile is under the pointer. This has the advantage of allowing irregular heights, different sized tiles &c. It also allows you to easily rotate the view without having to account for this in your maths, and saves you having to think about z-order when drawing the scene.
It’s a bit sledgehammer-to-crack-a-nuttish, but it works!