•      Sign In
• Create Account

Isometric tile coordinates

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

16 replies to this topic

#1PINGELS  Members

122
Like
0Likes
Like

Posted 25 April 2007 - 11:35 AM

How do I find the coordinate position on an isometric grid that is slanted at an angle (see image) The grid is based on the following: tilerowXrow = 29; tilerowXcol = 49; tilerowYrow = 25; tilerowYcol = 14; // xPosition = (rowNum*tilerowXrow)+(columnNum*tilerowXcol); yPosition = (columnNum*-tilerowYcol)+(rowNum*tilerowYrow) What I need to know is how do I get the x and y position based on the mouse position instead of the column and row number Thanks

#2Tom  GDNet+

352
Like
0Likes
Like

Posted 25 April 2007 - 12:59 PM

I don't really know what you're asking here. For one thing, you have X and Y values for both row and column, which I'm finding difficult to interpret. Are those actual screen coordinates? Shouldn't you have one value each for row and column (e.g., row 20, column 17)? Also, I don't recognize your plotting algorithm, again because it uses two separate row/column values, which I've never seen. And also, you have no image.

TANSTAAFL, in his book, recommends using a "mouse map," which is a bitmap matching your isometric tile with different values for each corner. It works, but a much easier (and faster) way to implement mouse feedback is to reverse-engineer your rendering algorithm. In the case of a diamond renderer (which I use because it's easier to handle all around), the algorithm looks like this:

recTileWidth = 1! / TileWidth
recTileHeight = 1! / TileHeight
tileX = (mouseX * recTileWidth + mouseY * recTileHeight) \ 1&
tileY = (mouseY * recTileHeight - mouseX * recTileWidth) \ 1&

This code generates coarse tile coordinates for the tile your cursor is over, and it is accurate to the pixel. Bear in mind this only works with the associated plotting algorithm. If your algorithm is different, you'll have to develop your own, but we can help with that. Also, this algorithm does not take into account a world-space anchor for scrolling maps; I'll add that later.

I've not developed an algorithm that returns fine (per-pixel) world coordinates because I haven't needed one, but it shouldn't be difficult.

#3Deyja  Members

920
Like
0Likes
Like

Posted 25 April 2007 - 04:35 PM

WorldCoordinate tile_origin(TileCoordinate tc){	return WorldCoordinate(		(tc.x - tc.y) * tile_width / 2,		(tc.x + tc.y) * tile_height / 2);}TileCoordinate tile_at(WorldCoordinate wc){	int x = (int)floorf( (float)wc.x / (float)tile_width);	int y = (int)floorf( (float)wc.y / (float)tile_height);	int rx = wc.x - (x * tile_width);	int ry = wc.y - (y * tile_height);	int f_x = x + y;	int f_y = y - x;	switch (Flat::index_surface(mouse_map,rx,ry).c)	{		case MouseMapColors::NORTHWEST :			f_x -= 1;			break;		case MouseMapColors::NORTHEAST :			f_y -= 1;			break;		case MouseMapColors::SOUTHEAST :			f_x += 1;			break;		case MouseMapColors::SOUTHWEST :			f_y += 1;			break;		default :			break;	}	return TileCoordinate(f_x,f_y);}

I hope my code is fairly self explanatory. The map in the bottom-right of this screenshot is the iso map style this code works with. The mousemap is there in the image.

#4PINGELS  Members

122
Like
0Likes
Like

Posted 25 April 2007 - 06:50 PM

The game I am writing is in Flash ActionScript. I place the original tiles (only 2d space in Flash) by doing:

tilerowXrow = 29;
tilerowXcol = 49;
tilerowYrow = 25;
tilerowYcol = 14;
//
for (r=0; r<mapRows; r++) {
for (c=0; c<mapColumns; c++) {
gameMC["t"+num]._x = mapStartX+(r*tilerowXrow)+(c*tilerowXcol);;
gameMC["t"+num]._y = mapStartY+(c*-tilerowYcol)+(r*tilerowYrow);
num ++
}
//

//
Believe it or not but that actually works (although that might not be the best methode but then I am not a programmer).

The problem I have is when the user moves the mouse over the tiles I need to know which tile it is and what the coordinate position of that tile is. I can do collusion detection but that seems a bit over the top so I though there should be some formula of determining the tile coordinate through the mouse x/y positions. Hope this makes more sense ... my appologies, I should have made the context of my question more clear.

#5Deyja  Members

920
Like
0Likes
Like

Posted 26 April 2007 - 12:39 AM

Okay, what you've got there is a diametric perspective. The basic idea of mouse maps still applies, you just can't divide the map in a regular grid. If you used an actual isometric perspective, you'd discover that the math was much, much easier. I've never actually worked with a diametric perspective, so I can't share code.

Instead of going the mouse map router, you could try rotating and skewing the mouse coordinate into a space where the map is a regular, orthogonal, grid. Then you would determine the tile the same way you would on any old square tilemap.

Here, this illustrates the basic idea, where the red arrow is the y axis, and the blue arrow is a x axis. A rotation and a shear will transform the mouse coordinate (in screen space) into a world space where the tiles are orthogonal.

#6PINGELS  Members

122
Like
0Likes
Like

Posted 26 April 2007 - 04:18 AM

Thanks Deyja, that's been very informative. I appreciate all your effort.

#7Tom  GDNet+

352
Like
0Likes
Like

Posted 27 April 2007 - 12:41 PM

The term you're thinking is "dimetric" (not diametric, which means "opposite"), and that is incorrect. This type of projection is called "trimetric" because none of the three angles are the same. It's been used in the Fallout series and SimCity 4. (Fallout even extended the map format to use hexes, which blows my mind to this day.)

I'm still trying to decipher your algorithm, but at first glance it seems a very inefficient way to store map data. You can extrapolate a tile index if you know the number of rows a columns, which you clearly do, but I'm wondering why you simply don't use a two-dimensional array, where elements refer to the tile's row and column, respectively. (I don't know anything about ActionScript, so I don't know its syntactic or structural limitations.) Regardless, your plotting algorithm baffles me! I still don't understand why you have row and column values for both X and Y. Typically, X is column, and Y is row.

On the other hand, if it works, go for it.

If you find a solution to your problem, I'd like to see it! I never did understand how Fallout works.

#8Deyja  Members

920
Like
0Likes
Like

Posted 28 April 2007 - 12:02 AM

Quote:
 The term you're thinking is "dimetric" (not diametric, which means "opposite"), and that is incorrect. This type of projection is called "trimetric" because none of the three angles are the same.

I'm looking and looking, and I just can't find this third angle you're talking about. In fact, I'm pretty certain that opposite angles in parallelograms are always the same. Heck, that's why it's called diametric!

Also,

http://dictionary.reference.com/browse/dimetric
http://dictionary.reference.com/browse/diametric

Oh snap!

#9Tom  GDNet+

352
Like
0Likes
Like

Posted 28 April 2007 - 11:36 AM

Sorry, here are some details:

http://en.wikipedia.org/wiki/Axonometric_projection

Check out the related articles. I'm just trying to be informative, not argumentative!

PINGELS: Where did you come up with those Xrow/Ycol, etc. values? They look pretty arbitrary to me. If we know how the plotting algorithm works, we can probably help you generate a mouse feedback algorithm.

#10Deyja  Members

920
Like
0Likes
Like

Posted 28 April 2007 - 02:25 PM

You can't call it trimetric unless it has a third axis. And it doesn't.

#11Tom  GDNet+

352
Like
0Likes
Like

Posted 02 May 2007 - 11:26 AM

The third axis is vertical.

#12ARC inc  Members

100
Like
0Likes
Like

Posted 02 May 2007 - 12:15 PM

WorldCoordinate tile_origin(TileCoordinate tc)
{
return WorldCoordinate(
(tc.x - tc.y) * tile_width / 2,
(tc.x + tc.y) * tile_height / 2);
}

TileCoordinate tile_at(WorldCoordinate wc)
{
int x = (int)floorf( (float)wc.x / (float)tile_width);
int y = (int)floorf( (float)wc.y / (float)tile_height);
int rx = wc.x - (x * tile_width);
int ry = wc.y - (y * tile_height);

int f_x = x + y;
int f_y = y - x;

switch (Flat::index_surface(mouse_map,rx,ry).c)
{
case MouseMapColors::NORTHWEST :
f_x -= 1;
break;
case MouseMapColors::NORTHEAST :
f_y -= 1;
break;
case MouseMapColors::SOUTHEAST :
f_x += 1;
break;
case MouseMapColors::SOUTHWEST :
f_y += 1;
break;
default :
break;
}

return TileCoordinate(f_x,f_y);
}
--------------------------------------------------------------------------------
that code does not work for me, I don't know what I did but it does not work.

#13Kris Schnee  Members

126
Like
0Likes
Like

Posted 06 May 2007 - 02:37 AM

The way I did it using an isometric map was to, when the player clicked on the map:
-Redraw the map as normal, but to a hidden graphics surface and using a solid color for each tile. The color is based on each tile's XY coordinates, eg. tile (4,2) might be color (0,4,2).
-Get the color of the pixel where the player clicked.
-Translate that color back into coordinates.

That method gets around the problem of oddly-shaped tiles, and even works if you're using the isometric tiles for a pseudo-3D landscape where some of the tiles are "above" the "ground." Also it looks cool if you draw this hidden map to the actual screen instead of a hidden surface, using colors multiplied by ten or so to get a visible color gradient. 8)

I could post the code, but it's in Python.

#14ARC inc  Members

100
Like
0Likes
Like

Posted 07 May 2007 - 11:03 AM

Well thats why it wont work for me, I'm using VB6.0

#15Woron kar DeDulle  Members

139
Like
0Likes
Like

Posted 26 May 2007 - 08:27 AM

trimetric, hm, maybe that is the reason why we found no resources for our problems searching for isometric tile coordinates with different angles.

Anyway

Few months ago I wrote such a "TileSystem" for our little browser game in Java.
If it would help you, I will send it to you or post the code here.

All you need for calculating positions of tiles (and translating mouse positions to cells) is the x and y coordinate of the tile in the array, the screen position and a method to calculate if the tile a point is inside the tile polygon (would be easy if you use squares :) ).

How that?
Forget to work only on the screen, positions are calculated for the complete map and then translated with the screen position (top left corner of the screen on the complete map) to the final screen positions (just because in the first post was something in relation with the mouse position).

For the other way round I used a binary search. I created a polygon for half the map, checked if it is in, if yes, split the polygon again, if not use the other side (described in a very short and rough way).
Complete TileSystem needs only a few int in the memory and you can easily change the angles but it does not support rotating (like in the Anno series with look north, east, south or west).

Why we haven't used mouse maps? Because applets just haven 16 MB RAM by default and we fought against every useless byte in the RAM.

That it works you can see here but

BEFORE YOU TRY
this is an prototype. You have to use firefox for trying (never found out why IE has problems with the param tag of an applet) and you need Java Runtime Enviroment 1.5 or higher and you have to accept the applets signature.
Language is german (just mentioned, never needed) because there is no server running behind producing producing products....
and may it takes time to load

http://orpheus.fh-hagenberg.at/students/mtd/mtd04007/Solgrad/

[Edited by - Woron kar DeDulle on May 30, 2007 3:27:46 AM]

#16Woron kar DeDulle  Members

139
Like
0Likes
Like

Posted 29 May 2007 - 10:06 PM

@Deyja: how do I write such a cool code snippet in here?

well, got the ok so here is the code.

It is the Java tile system (if isometric, dimetric or trimetric, guess the last), no mouse maps are required for it, but this was done at the cost of performace. Eg if the player moves the mouse every time there has to be run through the map to find the pointed cell, but this is not called automatically, so be carefull with the implementation (if someone uses it).

Features:

* calculation of the position of the tiles on screen
(for that a screen position is needed with x and y position of the top left corner of the screen on the complete map)

* calculating the polygon representing the tile
* calculating a suggestion for the center of the tile
* calculating the boundingbox of a tile
* check if a given point is in the tile
(here again the point is meant as the point on the complete map, not on the screen)

* checks if mouse points on the map
* calculates the currently pointed cell
(again, the given point is the point on the complete map, not on the screen).

Suggestions or critics are always welcome and feel free to use if you like it and, if it is possible, mention Solgrad anywhere.

<code>
//----------------------------------------------------------------------------
// Solgrad TileSystem
//----------------------------------------------------------------------------
package shared.game;

import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;

public class TileSystem {

public static final int MAP_SIZE_LIMIT = 200;

//singleton elements
private static TileSystem curTS = null;

public static void createTileSystem(int angleA, int angleB,
short tileLength, short borderSize, int cellCountX, int cellCountY)
throws InvalidTileSystemValueException {

//checkValues
if (angleA < 0 || angleA > 90) {
throw new InvalidTileSystemValueException("angleA", "" + angleA,
"liegt zwischen 0 und 90 (beides inklusive).");
}

if (angleB < 0 || angleB > 90) {
throw new InvalidTileSystemValueException("angleB", "" + angleB,
"liegt zwischen 0 und 90 (beides inklusive).");
}

if (borderSize < 0) {
throw new InvalidTileSystemValueException("borderSize", "" + borderSize,
"ist größer oder gleich 0.");
}

if (cellCountX <= 0 || cellCountX > MAP_SIZE_LIMIT) {
throw new InvalidTileSystemValueException("cellCountX", "" + cellCountX,
"liegt zwischen 1 und " + MAP_SIZE_LIMIT + " (beides inklusive).");
}

if (cellCountY <= 0 || cellCountY > MAP_SIZE_LIMIT) {
throw new InvalidTileSystemValueException("cellCountY", ""+ cellCountY,
"liegt zwischen 1 und " + MAP_SIZE_LIMIT + " (beides inklusive). ");
}

if (curTS == null) {
curTS = new TileSystem(angleA, angleB, tileLength, borderSize,
cellCountX, cellCountY);
}

}

public static TileSystem get() {
return curTS;
}

//required values
private int angleA; //angle from the bottom to the left
private int angleB; //angle fromt the bottom to the right
private short tileLength; //length of a tile
private short borderSize; //distance between screen and map
private int cellCountX; //map size along x axis
private int cellCountY; //map size along y axis
// 0/0 is here the left edge of the map

//calculated help values
private int tCosA; //
private int tCosB; // cos of A, cos of B, sin of A, sin of B
private int tSinA; // multiplicated with the
private int tSinB; // tileLength

//calculated useable values
private int tileWidth;
private int tileHeight;
private Polygon masterTilePolygon;

private int mapWidth;
private int mapHeight;
private Polygon mapPolygon;

//map elements
private Polygon[][] polygonsOnMap;
private int[][] cellXPositionsOnMap;
private int[][] cellYPositionsOnMap;

private TileSystem(int angleA, int angleB, short tileLength,
short borderSize, int cellCountX, int cellCountY) {
this.angleA = angleA;
this.angleB = angleB;
this.tileLength = tileLength;
this.borderSize = borderSize;
this.cellCountX = cellCountX;
this.cellCountY = cellCountY;

//calculate help values
tCosA = (int)(Math.cos(Math.toRadians(this.angleA)) * this.tileLength);
tSinA = (int)(Math.sin(Math.toRadians(this.angleA)) * this.tileLength);
tCosB = (int)(Math.cos(Math.toRadians(this.angleB)) * this.tileLength);
tSinB = (int)(Math.sin(Math.toRadians(this.angleB)) * this.tileLength);

//calculate useable values
tileWidth = tCosB + tCosA;
tileHeight = tSinA + tSinB;

//mapHeight = tSinB * cellCountX + tSinA * cellCountY + 2 * borderSize;
//mapWidth = tCosA * cellCountY + tCosB * cellCountX + 2 * borderSize;
mapHeight = tSinB * cellCountX + tSinA * cellCountY + 2 * borderSize;
mapWidth = tCosA * cellCountY + tCosB * cellCountX + 2 * borderSize;

int[] xCos = {0, tCosB, tileWidth, tCosA};
int[] yCos = {tSinB, 0, tSinA, tileHeight};
masterTilePolygon = new Polygon(xCos, yCos, 4);

//calculating positions on map
cellXPositionsOnMap = new int[cellCountY][cellCountX];
cellYPositionsOnMap = new int[cellCountY][cellCountX];
for (int x = 0; x < cellCountX-1; x++) {
for (int y = 0; y < cellCountY-1; y++) {
cellXPositionsOnMap[y][x] = calcTilePosX(x,y);
cellYPositionsOnMap[y][x] = calcTilePosY(x,y);
}
}

//calculating tile polygons
polygonsOnMap = new Polygon[cellCountY][cellCountX];
for (int y = 0; y < cellCountY; y++) {
for (int x = 0; x < cellCountX; x++) {
polygonsOnMap[y][x] = calcTilePolygon(x,y);
}
}

int[] xPos = { getTilePolygon(0,0).xpoints[0],
getTilePolygon(cellCountX-1, 0).xpoints[1],
getTilePolygon(cellCountX-1, cellCountY-1).xpoints[2],
getTilePolygon(0, cellCountY-1).xpoints[3]};

int[] yPos = { getTilePolygon(0,0).ypoints[0],
getTilePolygon(cellCountX-1, 0).ypoints[1],
getTilePolygon(cellCountX-1, cellCountY-1).ypoints[2],
getTilePolygon(0, cellCountY-1).ypoints[3]};

mapPolygon = new Polygon(xPos, yPos, 4);
}

//getters for required values
public int getAngleA() { return angleA; }
public int getAngleB() { return angleB; }
public short getBorderSize() { return borderSize; }
public short getTileLength() { return tileLength; }
public Polygon getTilePolygon(int x, int y) { return polygonsOnMap[y][x]; }
public int getTileWidth() { return tileWidth; }
public int getTileHeight() { return tileHeight; }
public int getTilePosX(int x, int y) { return cellXPositionsOnMap[y][x]; }
public int getTilePosY(int x, int y) { return cellYPositionsOnMap[y][x]; }
public Polygon getMapPolygon() { return mapPolygon; }
public int getMapCellSizeWidth() { return cellCountX; }
public int getMapCellSizeHeigth() { return cellCountY; }
public int getMapHeigth() { return mapHeight; }
public int getMapWidth() { return mapWidth; }

//********************************************************
// cell functionality
//********************************************************
//x and y are the positions in the array
public int calcTilePosX(int x, int y) {
//return borderSize + (x * tCosB * tileLength) + (y * tCosA * tileLength);
//if (x == 0 && y == 0) System.out.println(x + "/" + y + " X: " + (borderSize + (x * tCosB) + (y * tCosA)));
return borderSize + (x * tCosB) + (y * tCosA);

}

//x and y are the positions in the array
public int calcTilePosY(int x, int y) {
//return borderSize + (cellCountX - x - 1) * (tSinB * tileLength) + (y * (tSinA * tileLength));
//if (x == 0 && y == 0) System.out.println(x + "/" + y + " Y: " + (borderSize + (cellCountX - x - 1) * (tSinB) + (y * (tSinA))));
return borderSize + (cellCountX - x - 1) * (tSinB) + (y * (tSinA));
}

//x and y are the positions in the array
public Polygon calcTilePolygon(int x, int y) {

int[] xCos = {masterTilePolygon.xpoints[0] + getTilePosX(x,y),
masterTilePolygon.xpoints[1] + getTilePosX(x,y),
masterTilePolygon.xpoints[2] + getTilePosX(x,y),
masterTilePolygon.xpoints[3] + getTilePosX(x,y)};

int[] yCos = {masterTilePolygon.ypoints[0] + getTilePosY(x,y),
masterTilePolygon.ypoints[1] + getTilePosY(x,y),
masterTilePolygon.ypoints[2] + getTilePosY(x,y),
masterTilePolygon.ypoints[3] + getTilePosY(x,y)};

return new Polygon(xCos, yCos, 4);
}

//x and y are the positions in the array
public Rectangle calcTileBoundingBox(int x, int y) {
return new Rectangle(getTilePosX(x,y),getTilePosY(x,y), tileWidth, tileHeight);
}

//x and y are the positions in the array
public Point calcTileCenter(int x, int y) {
return new Point(getTilePosX(x,y) + tileWidth/2, getTilePosY(x,y) + tileHeight/2);
}

public boolean checkIfInTile(int x, int y, Point mousePosOnMap) {

if (!calcTileBoundingBox(x,y).contains(mousePosOnMap)) {
return false;
}

return getTilePolygon(x,y).contains(mousePosOnMap);
}

//*************************************************************
// map functionality
//*************************************************************

//functionality

public boolean validXIndex(int x) { return x > 0 && x < cellCountX; }
public boolean validYIndex(int y) { return y > 0 && y < cellCountY; }

//mouse position on the map has to be calculated outside
public boolean checkIfInMap(Point mousePosOnMap) {
return mapPolygon.contains(mousePosOnMap);
}

public Polygon calcMapSplitPolygon(int xStart, int xStop, int yStart, int yStop) {

int[] xPos = {
getTilePolygon(xStart, yStart).xpoints[0],
getTilePolygon(xStop, yStart).xpoints[1],
getTilePolygon(xStop, yStop).xpoints[2],
getTilePolygon(xStart, yStop).xpoints[3]
};

int[] yPos = {
getTilePolygon(xStart, yStart).ypoints[0],
getTilePolygon(xStop, yStart).ypoints[1],
getTilePolygon(xStop, yStop).ypoints[2],
getTilePolygon(xStart, yStop).ypoints[3]
};

return new Polygon(xPos, yPos, 4);
}

//mouse position on the map has to be calculated outside
public Point calcCurrentCellPos(Point mousePosOnMap) {

if (!mapPolygon.contains(mousePosOnMap)) { // if point is outside of map
return null;
}

// binary cell search, search xPos
int left = 0;
int right = cellCountX;
int searchPos;

while (right > left) {
searchPos = (right - left) / 2;
if (calcMapSplitPolygon(left, left + searchPos, 0, cellCountY).contains(mousePosOnMap)) {
right = left + searchPos;
} else {
left = left + searchPos + 1;
}
}
int xCo = left;

// search yPos
left = 0;
right = cellCountY;

while (right > left) {
searchPos = (right - left) / 2;
if (calcMapSplitPolygon(xCo, xCo, left, left + searchPos).contains(mousePosOnMap)) {
right = left + searchPos;
} else {
left = left + searchPos + 1;
}
}
int yCo = left;

return new Point(xCo, yCo);
}

public static Polygon movePolygon(Polygon g, int x, int y) {
Polygon h = new Polygon(g.xpoints, g.ypoints, g.npoints);
h.translate(x,y);
return h;
}
}
</code>

#17AngleWyrm  Members

554
Like
0Likes
Like

Posted 15 June 2007 - 10:05 PM

An interesting article on hex map coordinates

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.