10

I have a tile-based isometric world and I can calculate which tile is underneath specific (mouse) coordinates by using the following calculations:

function isoTo2D(pt:Point):Point{
  var tempPt:Point = new Point(0, 0);
  tempPt.x = (2 * pt.y + pt.x) / 2;
  tempPt.y = (2 * pt.y - pt.x) / 2;
  return(tempPt);
}

function getTileCoordinates(pt:Point, tileHeight:Number):Point{
  var tempPt:Point = new Point(0, 0);
  tempPt.x = Math.floor(pt.x / tileHeight);
  tempPt.y = Math.floor(pt.y / tileHeight);
  return(tempPt);
}

(Taken from http://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511, this is a flash implementation but the maths is the same)

However, my problem comes in when I have tiles that have different elevation levels: enter image description here

enter image description here

In these scenarios, due to the height of some tiles which have a higher elevation, the tiles (or portions of tiles) behind are covered up and shouldn't be able to be selected by the mouse, instead selecting the tile which is in front of it. How can I calculate the tile by mouse coordinates taking into account the tiles' elevation?

I'm using a javascript and canvas implementation.

johnnyRose
  • 7,310
  • 17
  • 40
  • 61
user1094553
  • 786
  • 5
  • 15
  • Well, your tiles are on a 3D plane and your mouse is on a 2D plane, so you'll need to do some linear algebra projection probably using the angle you are viewing the tiles at. – OneCricketeer Mar 14 '16 at 01:18
  • I'm not using a 3d plane - it's a 2d plane mimicking a 3d plane – user1094553 Mar 14 '16 at 01:32
  • It's the mimicking part that's important. Pretend you could "rotate" your world. Then you'd be able to see different tiles, right? Tilt up the angle so you are looking straight down? Then you could easily find the tile your mouse is on. So either you take the tilt and rotation angles into account, or maybe some bounding box listener for mouse enter and leave events, or ditch the mouse entirely and just use the keyboard to select a tile. – OneCricketeer Mar 14 '16 at 02:41

4 Answers4

2

There is a technique of capturing object under the mouse on a canvas without needing to recalculate mouse coordinates into your "world" coordinates. This is not perfect, has some drawbacks and restrictions, yet it does it's job in some simple cases.

1) Position another canvas atop of your main canvas and set it's opacity to 0. Make sure your second canvas has the same size and overlaps your main one.

2) Whenever you draw your interactive objects to the main canvas, draw and fill the same objects on the second canvas, but using one unique color per object (from #000000 to #ffffff)

3) Set mouse event handling to the second canvas.

4) Use getPixel on the second canvas at mouse position to get the "id" of the object clicked/hovered over.

Main advantage is WYSIWYG principle, so (if everything is done properly) you can be sure, that objects on the main canvas are in the same place as on the second canvas, so you don't need to worry about canvas resizing or object depth (like in your case) calculations to get the right object.

Main drawback is need to "double-render" the whole scene, yet it can be optimized by not drawing on the second canvas when it's not necessary, like:

  • in "idling" scene state, when interactive objects are staying on their places and wait for user action.

  • in "locked" scene state, when some stuff is animated or smth. and user is not allowed to interact with objects.

Main restriction is a maximum number of interactive objects on the scene (up to #ffffff or 16777215 objects).

So... Not reccomended for:

  • Games with big amount of interactive objects on a scene. (bad performance)

  • Fast-paced games, where interactive objects are constantly moved/created/destroyed.(bad performance, issues with re-using id's)

Good for:

  • GUI's handling

  • Turn-based games / slow-paced puzzle games.

haldagan
  • 911
  • 5
  • 16
  • This is a beautiful answer. There is a very similar new question here: https://stackoverflow.com/questions/21842814/mouse-position-to-isometric-tile-including-height/49216758#49216758 – Anil Vaitla Mar 11 '18 at 04:54
1

Your hit test function will need to have access to all your tiles in order to determine which one is hit. It will then perform test hits starting with the tallest elevation.

Assuming that you only have discreet (integer) tile heights, the general algorithm would be like this (pseudo code, assuming that tiles is a two-dimensional array of object with an elevation property):

function getTile(mousePt, tiles) {
    var maxElevation = getMaxElevation(tiles);
    var minElevation = getMinElevation(tiles);
    var elevation;
    for (elevation = maxElevation; elevation >= minElevation; elevation--) {
        var pt = getTileCoordinates(mousePt, elevation);
        if (tiles[pt.x][pt.y].elevation === elevation) {
            return pt;
        }
    }
    return null; // not tile hit
}

This code would need to be adjusted for arbitrary elevations and could be optimized to skip elevation that don't contain any tiles.

Note that my pseudocode ignores vertical sides of a tile and clicks on them will select the (lower elevation) tile obscured by the vertical side. If vertical tiles need to be accounted for, then a more generic surface hit detection approach will be needed. You could visit every tile (from closest to farthest away) and test whether the mouse coordinates are in the "roof" or in one of the viewer facing "wall" polygons.

Stepan Riha
  • 1,736
  • 14
  • 12
  • This is an interesting method. What can be done for the sides of elevated tiles though? The first thought I had was check if the mouse is within the rectangle between the elevated tile and the same tile at ground level, but if there's a less elevated tile in front, it would work incorrectly, selecting the tile behind (which has a higher elevation) instead of correctly selecting the tile in front (slightly lower elevation). – user1094553 Mar 31 '16 at 16:17
  • You're right, this approach doesn't address clicking on a vertical ("wall") surface, and only deals with clicks on the horizontal ("roof") surface. The problem description does not say what the behavior should be when a "wall" is clicked. Should it be a noop or the same as a click on the "roof" of that tile? I picked the "easy" behavior and treat walls as click-throughs. ;-) – Stepan Riha Mar 31 '16 at 17:14
  • 1
    it should be the same as a click on the roof of the tile. – user1094553 Mar 31 '16 at 18:03
0

If map is not rotatable and exatly same as picture you posted here,

When you are drawing polygons, save each tile's polygon(s) in a polygon array. Then sort the array only once using distance of them(their tile) to you(closest first, farthest last) while keeping them grouped by tile index.

When click event happens, get x,y coordinates of mouse, and do point in polygon test starting from first element array until last element. When hit, stop at that element.

No matter how high a tile is, will not hide any tile that is closer to you(or even same distance to you).

Point in polygon test is already solved:

Point in Polygon Algorithm

How can I determine whether a 2D Point is within a Polygon?

Point in polygon

You can even check every pixel of canvas once with this function and save results into an 2d array of points, vect2[x][y] which gives i,j indexes of tiles from x,y coordinates of mouse, then use this as a very fast index finder.

Pros:

  • fast and parallelizable using webworkers(if there are millions of tiles)
  • scalable to multiple isometric maps using arrays of arrays of polygons sorted by distance to you.
  • Elevation doesnt decrease performance because of only 3 per tile maximum.
  • Doesn't need any conversion to isometric to 2d. Just the coordinates of corners of polygons on canvas and coordinates of mouse on the same canvas.

Cons:

  • You need coordinates of each corner if you haven't already.
  • Clicking a corner will pick closest tile to you while it is on four tiles at the same time.
Community
  • 1
  • 1
huseyin tugrul buyukisik
  • 11,469
  • 4
  • 45
  • 97
-2

The answer, oddly, is written up in the Wikipedia page, in the section titled "Mapping Screen to World Coordinates". Rather than try to describe the graphics, just read the section three times.

You will need to determine exactly which isomorphic projection you are using, often by measuring the tile size on the screen with a ruler.

Charles Merriam
  • 19,908
  • 6
  • 73
  • 83
  • 2
    if I understood the article correctly, that is precisely the method I'm using to calculate the location of the tile. However, my question comes in when some tiles have a higher elevation, and the sides of the tile as well as the tile itself must be selectable by the mouse, and the tile behind shouldn't be able to be selected if it is hidden behind another. – user1094553 Mar 31 '16 at 16:10
  • For that there is no shortcut; you must calculate the entire set of tiles which could be at and then take the most forward one, usually the one with lowest row + col. The exact function to minimize will depend on your projection and numbering, but will be (min or max) of (+/-) row (+/- col). The 'cheap' method is to use a z value on the screen or in the color pallette to encode the tile number. – Charles Merriam Apr 01 '16 at 03:12