9

I'm having some trouble with isometric walls.

I'm drawing isometric floor tiles using the back to front rendering method, and it works fine. I also keep my floor tiles lined up properly in a nice grid. The code (and this seems to be pretty much standard for isometric floor drawing) is as follows:

for(int x = 0; x < 6; x++){
    for(int y  = 3; y >=0 ; y--){

        int xCo = (y+x)*(tileWidth/2);
        int yCo = (x-y)*(tileHeight/2);
        tile.draw(g, xCo, yCo);
    }            
}

This makes a nice little floor:

multiple floor tiles

The grid is constructed from this tile:

A single tile

Unfortunately, when I use the same logic for walls, it all goes right to hell.

for(int x = 0; x < 6; x++){
    for(int y  = 3; y >= 0 ; y--){

        int xCo = (y+x)*(wallWidth()/2);
        int yCo = (x-y)*(wallHeight()/2);
        walls.draw(g, xCo, yCo);    
}
}

I'm using this as my wall tile:

Single wall tile

(it's a placeholder from google image search, but it should work all the same)

This is the result I get:

Multiple wall tiles

The rendering order for my walls is clearly correct, nearer walls render on top of farther away walls, which is what I want, but the positioning is also painfully incorrect, and I don't know exactly how I ought to correct that short of using pixel values.

For reference, I did do a trial using hardcoded pixel vals, because I found that from one wall's right corner to the next wall's right corner, the change was exactly (200, -100) pixels. When I made my rendering loop account for that

int xCo = (x+y)*200;
int yCo = (y-x)*-100;

it worked fine, but that is not a viable solution because it doesn't allow for any versatility.

So, looking for suggestions on how to make my isometric walls line up. What am I doing wrong?

Thanks!

Kevin
  • 74,910
  • 12
  • 133
  • 166
  • Thanks for making my pictures visible. I posted on gamedev.stackexchange as well, and they are super restrictive about links and images, so I didn't try here. –  Jan 23 '13 at 20:56
  • I noticed a pattern with your walls: the front top horizontal edge of the first wall is collinear with the front bottom horizontal edge of the third wall. Also, the front right vertical edge of the first wall is collinear with the back left vertical edge of the third wall. The same is true of the second and fourth walls. So they are sort of lining up. Just not in the way that you want. – Kevin Jan 23 '13 at 20:56
  • Haha yes, that is true. I'll take some solace in that fact, but I'm not sure what to make of it apart from "my method is only half-wrong" –  Jan 23 '13 at 20:59
  • 3
    The floor tile edges meet at the midpoint of the image. The wall edges do not. I recommend xCo = (x+y)*(wallWidth()*9/10) and yCo = (y-x)*(wallHeight()/3). – Apprentice Queue Jan 23 '13 at 21:02
  • 1
    I'd like to see your implementation of `wallWidth()` and `wallHeight()`. is the wallWidth equal to the width of the wall tile? Or just the width of the front side, excluding the black border on the left? Likewise, is wallHeight the height of the image, or the distance between the right bottom corner and the right top corner, or something else? – Kevin Jan 23 '13 at 21:06
  • It is important to note the wallHeight and tileHeight are two different things. I would rename tileHeight to tileDepth and wallWidth to wallLength. I would use constants for your isometric offsets. – BevynQ Jan 23 '13 at 21:07
  • Apprentice Queue - your method is super close, thanks! I tried your method (but changed y-x to x-y) and it looks like [this](http://i.imgur.com/RdaBYl1.png). For reference, your initial code had y-x and that looks like [this](http://i.imgur.com/llajsTr.png) –  Jan 23 '13 at 21:14
  • Kevin - wallWidth() and wallHeight() return the width and height of the image, they aren't any sort of fun isometric math. Just like the integers tileWidth and tileHeight are just the width and height of the .png file that holds the tile image. –  Jan 23 '13 at 21:15

3 Answers3

2

You can't simply use the same drawing code for walls as for floors because walls and floors are not in the same plane: floors are flat (horizontal) while walls are vertical. So you have to draw them slightly differently.

Your x and y coordinates in the floor case mean something like "left/right" and "forward/backward" in terms of the placement of the tiles. For bricks, left and right still make sense, but we want to replace forward/backward with up and down to reflect the vertical direction. So our "y" gets a new meaning.

Now, in Maths, the y axis usually points upwards while in 2D computer graphics it points down. You can take your pick - the code below assumes that it points upwards so that y = 0 means "at the floor level".

So let's start thinking about order. The example brick you posted is for a wall that would be a the (upper) left end of a floor. Because of the black parts of the brick (the depth of the wall), we have to make sure that we draw the bricks that are further right first so that the black depth on the left side will be covered by bricks that are closer. The same argument applies for the black on the top part of the wall, we have to draw the lower bricks first.

If we stick with the x- and y-directions as discussed before (x goes from left to right, y goes from bottom to top), this means that we have to run both our for-loops in negative directions:

    for (int y = 3; y >= 0; y--) {
        for (int x = 5; x >= 0; x--) {
            ...
        }
    }

The main question is now how much we have to offset the drawing of each brick with respect to the other bricks. Let's do that one direction at a time, starting with the x direction.

Let's imagine just two bricks next to each other:

two bricks horizontally

The left of the two has the black depth part visible but the right one shouldn't show it. Thus we cannot simply offset the right image by the full width of the PNG. In fact, assuming that the bricks line up with your floor tiles, the width of the actual front part of the wall should be the same as half the width of a tile.

int xCo = x * tileWidth / 2;

The black wall depth on the left should not be ignored, because we probably want to offset each brick a little bit to the left so that the x-coordinate of the front corner of the wall lines up with the floor tiles, not the x-coordinate of the back corner.

Now, the y-coordinate of each brick is a bit trickier because it does not only depend on the brick row, it also depends on the x-coordinate: the further right the higher up we should draw. But let's ignore the x-direction for a moment and try to simply draw a column of bricks:

two bricks vertically

Again, the delta between the y-coordinates of the two bricks is not the full height of the PNG. Unlike in the left/right case where we assumed that bricks line up with tiles which allowed us to use tileWidth as the delta-x, bricks can have arbitrary heights. But we can still compute the actual brick height from the image height because we know that the depth on the left side and the depth on the top have to line up.

If we look at the little transparent triangle in the upper right corner of the brick PNG, we notice that the ratio of its width and height must be the same as the ratio of a floor tile's width and height. That allows us to compute an yoffset from the xoffset computed above, and to use that to infer the actual height of the brick:

int yoffset = xoffset * tileHeight / tileWidth;
int wallHeight = wallHeight() - tileHeight / 2 - yoffset;

Note that this only works under the assumption that there's no empty space at the border of the PNG and it might still fail due to rounding errors. So you might add a Math.ceil() (or simply + 1) here if necessary.

So for simple columns, we're good to go now: we can simply multiply our y variable with the above wallHeight. But as noted before, the x-position of a brick also influences the y pixel coordinate. If we look at the first picture again with the two bricks next to each other, how much did we have to move the right brick up to line up with the left brick? Well, this one is actually easy, because it's the same as with floor tiles: half the height of a tile!

So we're all set. If we put everything together, we end up with a bit of code like this:

int xoffset = wallWidth() - tileWidth / 2;
int yoffset = xoffset * tileHeight / tileWidth;
int wallHeight = wallHeight() - tileHeight / 2 - yoffset;

for (int y = 3; y >= 0; y--) {
    for (int x = 5; x >= 0; x--) {

        int xCo = x * tileWidth / 2;
        int yCo = y * wallHeight - x * tileHeight / 2;

        walls.draw(g, xCo - xoffset, yCo - yoffset);
    }
}

(I'm assuming that wallWidth() and wallHeight() return the width and height of the brick PNG.)

Note that the three constants before the for loops can be moved out of the actual drawing code - they only depend on image properties and other constants and don't have to be re-computed every time we draw the wall.

Thomas
  • 17,016
  • 4
  • 46
  • 70
  • So, first I want to thank you for an incredibly thorough answer. You're some kind of wizard. But I'm thick, and I need some help still. First thing's first, I don't understand what's going on here: "In fact, assuming that the bricks line up with your floor tiles, the width of the actual front part of the wall should be the same as half the width of a tile." If they are lined up, shouldn't their widths be the same? Also, could you explain how to get the wall's y-coordinate if I want them lined up instead of stacked up? Your first image of the two walls side by side is what I'm going for. –  Jan 24 '13 at 04:15
  • Also, how should I handle x-coordinates if walls are not lined up with floor tiles? How can I handle walls independently, basically. –  Jan 24 '13 at 04:17
  • 1
    No worries - if you don't need the walls stacked up, then just replace the outer for-loop with `int y = 0`. Other than that, have you tried out the code and see if it does something close to what you want? It might be easiest for you to just copy and paste it, and then start playing around with it. --- Regarding your question with the widths: the problem here is that sometimes I use the word width to refer to the width of a wall or tile, and sometimes to the width of the image. Because of the isometric view these two widths are different. In the quoted sentence, I'm referring to image widths. – Thomas Jan 24 '13 at 04:35
  • 1
    As for your second question, the basic drawing algorithm does not change, you just need to find the right position for the wall. If you look at the formulas for `xCo` and `yCo` you see that they will make the wall that has `x = 0` and `y = 0` be drawn at the pixel coordinates `(0, 0)` minus the offsets. If you can figure out where that one wall should _really_ be drawn in terms of pixel coordinates, then you can just add these coordinates to the call of `walls.draw(...)`. If one of the walls is placed correctly, all others should be, too. – Thomas Jan 24 '13 at 04:47
  • I think my biggest problem is that I'm trying to draw these without regards to tileHeight or tileWidth at all, and I just don't see the connection (why they're in the offsets, xCo, wallHeight, etc). The size of my walls and tiles currently have no relation, so I can't translate the math from tileWidth/2 to something related to walls. I'm trying to grasp this conceptually, not just find a functional hack that someone else wrote for me. –  Jan 24 '13 at 04:59
  • 1
    I suggest that we continue this discussion in chat: http://chat.stackoverflow.com/rooms/23269/isometric-walls – Thomas Jan 24 '13 at 05:06
  • 1
    let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/23270/discussion-between-thomas-and-battlebarnes) – Thomas Jan 24 '13 at 05:14
0

If you look at the floor tile which is shaped like a diamond, moving it up by a half width and across by half length two edges will align.
The wall tile is not a diamond so by moving half width and across by half length the edges you want to match will not align.

Given u = distance to move across and v = distance to move up and A the isometric angle

v = u*tan(A)

u in this case is the width of the front face of the image.

if face(the textured bit) of the wall image matches the edge length of the floor tile this gives

int offset = ?;// this is the width of the black stripe on the image.

for(int x = 0; x < 6; x++){
    for(int y  = 3; y >=0 ; y--){

        int xCo = ((y+x+1)*(tileWidth/2))-offset;
        int yCo = (x-y)*(tileHeight/2);
        wall.draw(g, xCo, yCo);
    }            
}
BevynQ
  • 8,089
  • 4
  • 25
  • 37
  • I can work with this. I'm on my phone, so I'll test it later, but thanks! I want to make sure I'm clear on terms here: When you say "if the face of the image matches the edge length of the floor tile" what exactly are you referring to? There are too many possible meaning there for me to be sure (the width of the image which is rectangular, the actual length of the tile which is hard to find out and annoying because it isn't square, etc). Thanks again! –  Jan 23 '13 at 22:08
  • 1
    I meant the texture on the wall image, as opposed to the black depth part. By the edge of the floor tile I mean the line between two adjacent points on the diamond, not the sides of the rectangle that contains the image. – BevynQ Jan 23 '13 at 22:15
  • so I just need to make the offset some consistent fraction of the image width, so that it can accept multiple images made to the same spec, right? –  Jan 23 '13 at 22:32
  • sorry fixed it had offset in wrong place should be ((y+x+1)*(tileWidth/2)-offset – BevynQ Jan 24 '13 at 20:12
0

In an isometric realm, there are three axes you can move in - Z for up and down, X and Y for your 'diagonals'.

First, let's imagine the pixel representation of a 1 unit by 1 unit by 1 unit isometric cube, with all sides represented equally long:

It would be A pixels high on the Z axis. Its other edges would also be A pixels in length, but rotated 60 degrees - so it would be sin(30)*A pixels high and cos(30)*A pixels long in X and Y directions - aka, 0.5*A and sqrt(3)/2 *A.

So, to position an isometric cube-sized object in X, Y and Z we must translate its on screen x and y by the following:

y += Z*A
x += X*A/2 - Y*A/2
y += (X+Y)*A*sqrt(3)/2

As long as the assumptions I made hold this should work.

EDIT: By the way, A will have to be hard coded if the image has depth and thus you can't automatically extract A from the image dimensions.

Patashu
  • 21,443
  • 3
  • 45
  • 53