Java-Gaming.org Hi !
 Featured games (90) games approved by the League of Dukes Games in Showcase (754) Games in Android Showcase (229) games submitted by our members Games in WIP (842) games currently in development
 News: Read the Java Gaming Resources, or peek at the official Java tutorials
 Home Help Search Login Register
Pages: [1]
 ignore  |  Print
 [SOLVED] Drawing isometric tiles inside a screen ??  (Read 39377 times) 0 Members and 1 Guest are viewing this topic.
jonjava
 « Posted 2011-10-15 18:35:13 »

Here's a link to an Applet ( source included ) so you can see the stuff discussed in action:

SOURCE: http://jonjavaweb.orgfree.com/IsoWorldSource.zip

In Normal tiled games, maps/rooms/worlds are saved and portrayed through a 2d Array like this:

Isometric games are practically no different in how these maps/rooms/worlds are stored. The most notable difference is, of course, how these maps/worlds appear on screen. This is achieved through different projection. I.e, the same 2d Array, just viewed differently. This is mostly done by turning the tiled map 45 degrees in either direction and substituting the tile with a new isometric tile image.

There are different ways and styles on how you can draw isometric tiles. However, this doesn't matter in calculations. What matters is the width and height of the isometric tile. NOTE: Not always the same as the Images width or height.

Here we have a Sprite/Image of a pink isometric tile that is 34 pixels wide and 17 pixels high. However, this tile's intended Width and Height are 32 and 16 respectively. Notice the jagged lines that are produced when using the Images Width/Height instead of the intended Isometric Tile Width/Height.

A 2d Array projected Isometrically

(reference: )

Let's start by going through the array and figuring out the isometric coordinates. We will calculate the isometric position of the blue (Top Left) corner for each tile.

Let's insert the isometric projection into a coordinate system and see what conclusions we can draw from it.

[size=6pt][EDIT]: Added Formula to convert Isometric positions back to 2D-Array positions.[/size]

In this case we have a screen whose Top Left corner points to a tile (pink!). Now even if the screen moves around a bit, the Top Left corner still points to the exact same tile!

Now that we can find out where to draw the isometric tiles based on the 2d Array, let's try it out:

Half the tiles are off-screen! To fix this issue we need to add a Horizontall Offset and Vertical Offset Variable. The hOffset makes sure no tile is draw behind the screen to the left, and the vOffset drags all the tiles down so it's easier on the eyes:

Now here's my question! Consider a large map

How do I make sure to draw only the tiles within the screen as to not waste memory cpu (less iterations)? The map is stored in a 2D Array.

I've fiddled around with my own solution for days now and it just doesn't work. I need some fresh ideas.

 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22 `public void paintDiamond(Graphics g, int x, int y, int w, int h) {      // Paint a diamond shape taken from the 2D surface array      // shows up as a rectangle in isometric view      /* [ i ]       *   x   *       *  x x  *       * x x x * [ j ]       *  x x  *       *   x   *       */       int px = x/Tile_W; // position x       int py = y/Tile_H; // position y       int dpw = (w - x) / Tile_W; // delta position width       int dph = (h - y) / Tile_H; // delta position height              for(int i=px; i

Basically only the highlighted tiles should be drawn

loom_weaver

JGO Coder

Medals: 17

 « Reply #1 - Posted 2011-10-15 18:45:42 »

You will need to iterate over an axis-aligned rectangle overlaying your original 2D-array.

Then for each tile in the axis-aligned rectangle you will need to check to see if it is on-screen or not.  The number of visible tiles will be less as they are a diamond within your axis-aligned rectangle.

Both the iteration and the on-screen check is easy to do and speedy.  I don't understand how and where you think you'll be able to save memory.
jonjava
 « Reply #2 - Posted 2011-10-15 20:27:11 »

Thanks for replying to my question. I was wondering if you could clarify what you mean by

Quote
"You will need to iterate over an axis-aligned rectangle overlaying your original 2D-array."

Here's what I gather from your post:

Pick an "estimate" box of values (small array) from the BIG 2D-Array. Iterate through this "estimate" smaller 2D-Array and check each iteration if this element is inside or outside of the Screen.

 Games published by our own members! Check 'em out!
theagentd
 « Reply #3 - Posted 2011-10-15 21:12:53 »

Find the tile position of the tiles in the corners of the screen, then draw the map line by line between these positions?

 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28 `int startX = topLeftCornerX(), startY = topLeftCornerY();int minX = botLeftCornerX();int maxX = topRightCornerX() + 1;int maxY = botRightCornerY() + 1;int currentMinX = startX, currentMaxX = startX+1;boolean bouncedRight = false, bouncedLeft = false;for(int y = startY; y < endY; y++){    for(int x = currentMinX; x < currentMaxX; x++){        tiles[y][x].draw();    }    if(!bouncedLeft){        currentMinX--;        if(currentMinX == minX){            bouncedLeft = true;        }    }else{        currentMinX++;    }    if(!bouncedRight){        currentMaxX++;        if(currentMaxX == maxX){            bouncedRight = true;        }    }else{        currentMinX--;    }}`

Warning: I wrote that when posting. Not tested at all.

Myomyomyo.
jonjava
 « Reply #4 - Posted 2011-10-16 11:27:25 »

I've tried implementing your method, here's the general idea:

In my code I also use a Buffer variable when they reach the min and max points in the j position. So that it doesn't immediately start decreasing, only until the buffer is gone. I've tried commentating as well as possible.

 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88 `   private int isoToI(int x, int y) {                // converts world isometric coordinates into the i position of the 2D-Array      return (((y/Tile_HH) + (x/Tile_HW)) /2);   }   private int isoToJ(int x, int y) {                // converts world isometric coordinates into the j position of the 2D-Array      return (((y/Tile_HH) - (x/Tile_HW)) /2);   }      public void paintScreen(Graphics g, Screen scr) {      // Screen is a class that holds data      // start position of the screen, and its width and height      // paint only the isometric tiles within the canvas      int iStart = isoToI(scr.x(), scr.y());      int jStart = isoToJ(scr.x(), scr.y());      int iMax = isoToI(scr.x()+scr.width(), scr.y()+scr.height()) +1;      int jMax = isoToJ(scr.x(), scr.y()+scr.height()) +1;      int jMin = isoToJ(scr.x()+scr.width(), scr.y());            boolean nBump = false, mBump = false;      int n = 1, nStart = 1, nBuffer = 1;      int m = 1, mStart = 1, mBuffer = 1;            for(int i=iStart; i < iMax; i++) {            // Testing Purposes      // Sleep 1 second each i iteration and paint the screen         // TODO      // .......                     for(int j=jStart-n; j < jStart+m; j++) {            // paint the column            colArray[ hWrap(i) ][ vWrap(j) ].paintAll(g);                        // adjust m and n to keep us within the screen                        // adjust n            if(!nBump) {               //we have not yet reached the lowest j point               // increment n to go even lower next iteration               n++;               // Check if we have reached the lowest j point               if( (jStart-n) == jMin) {                  nBump = true;                     //System.out.println("Bump N");               }            } else {                  // we have reached the deepest j and are now going back                  // start decreasing after the buffer is gone                  if(nBuffer>0) {                     nBuffer--;                  } else {                     // The buffer is gone, start decreasing n each iteration                     n--;                     // check that n never exceeds its starting point                     if(n0) {                     mBuffer--;                  } else {                     // The Buffer is gone                     // decrease m each iteration                     m--;                     // check that m never exceeds its starting point                     if(m

Results in:

theagentd
 « Reply #5 - Posted 2011-10-16 12:31:10 »

I have a hard time debugging code for you in my head. Either do the debugging yourself or post something I can run.
EDIT: You should only have the paintAll calls in the inner loop. That is most likely your problem. You want to check for bouncing every y, not for every x.
EDIT2: To clarify:
 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92 `    private int isoToI(int x, int y) {        // converts world isometric coordinates into the i position of the 2D-Array        return (((y / Tile_HH) + (x / Tile_HW)) / 2);    }    private int isoToJ(int x, int y) {        // converts world isometric coordinates into the j position of the 2D-Array        return (((y / Tile_HH) - (x / Tile_HW)) / 2);    }    public void paintScreen() {        // Screen is a class that holds data        // start position of the screen, and its width and height        // paint only the isometric tiles within the canvas        int iStart = isoToI(scr.x(), scr.y());        int jStart = isoToJ(scr.x(), scr.y());        int iMax = isoToI(scr.x() + scr.width(), scr.y() + scr.height()) + 1;        int jMax = isoToJ(scr.x(), scr.y() + scr.height()) + 1;        int jMin = isoToJ(scr.x() + scr.width(), scr.y());        boolean nBump = false, mBump = false;        int n = 1, nStart = 1, nBuffer = 1;        int m = 1, mStart = 1, mBuffer = 1;        for (int i = iStart; i < iMax; i++) {            // Testing Purposes            // Sleep 1 second each i iteration and paint the screen            // TODO            // .......            for (int j = jStart - n; j < jStart + m; j++) {                // paint the column                colArray[ hWrap(i)][ vWrap(j)].paintAll(g);            }            // adjust m and n to keep us within the screen            // adjust n            if (!nBump) {                //we have not yet reached the lowest j point                // increment n to go even lower next iteration                n++;                // Check if we have reached the lowest j point                if ((jStart - n) == jMin) {                    nBump = true;                    //System.out.println("Bump N");                }            } else {                // we have reached the deepest j and are now going back                // start decreasing after the buffer is gone                if (nBuffer > 0) {                    nBuffer--;                } else {                    // The buffer is gone, start decreasing n each iteration                    n--;                    // check that n never exceeds its starting point                    if (n < nStart) {                        n = nStart;                    }                }            }            // adjust m            if (!mBump) {                // we have not yet reached the HIGHEST j point                // increasee m to go even higher next iteration                m++;                // Check if we have reached the highest j point                if ((jStart + m) == jMax) {                    mBump = true;                    //System.out.println("Bump M");                }            } else {                // we have reached the maximum j point                // and are now moving back.                // start decreasing m after the buffer is gone                if (mBuffer > 0) {                    mBuffer--;                } else {                    // The Buffer is gone                    // decrease m each iteration                    m--;                    // check that m never exceeds its starting point                    if (m < mStart) {                        m = mStart;                    }                }            }        }    }}`

Myomyomyo.
jonjava
 « Reply #6 - Posted 2011-10-16 17:44:05 »

Duhh, of course! I even had the adjustments schedules for the outer loop in my picture, and even so I went head and included it inside the inner loop. Thanks! Your method works perfectly!

[EDIT]: I've no time now to clarify but it works quite flawlessy with a few tweaks - I'll the solution more in depth tomorrow.

theagentd
 « Reply #7 - Posted 2011-10-17 09:02:07 »

Great that it works! I'm a little suspicious about the if(n < nStart) and the same for m, as that would possibly make it cut into to screen when you the screen scrolls to the edge of the map, but maybe I'm just being paranoid. If it proves to be a problem, move the bounds checking to the for(int j = ...) loop and only loop through the tiles that actually exist. xD
I'm pretty sure that the direct algorithm I posted actually culls too much, so that you can see through the tiles near the screen edges. Either increase the screen size in all direction by half a tile or increase the size of the cull region when calculating iStart, jStart, e.t.c. Basically just decrease the minimums by 1 and increase the maximums by 1 (or 2 if it's a length depending on the minimum).
Glad I could help! =D

Myomyomyo.
jonjava
 « Reply #8 - Posted 2011-10-17 14:00:55 »

Great that it works! I'm a little suspicious about the if(n < nStart) and the same for m, as that would possibly make it cut into to screen when you the screen scrolls to the edge of the map, but maybe I'm just being paranoid. If it proves to be a problem, move the bounds checking to the for(int j = ...) loop and only loop through the tiles that actually exist. xD
I'm pretty sure that the direct algorithm I posted actually culls too much, so that you can see through the tiles near the screen edges. Either increase the screen size in all direction by half a tile or increase the size of the cull region when calculating iStart, jStart, e.t.c. Basically just decrease the minimums by 1 and increase the maximums by 1 (or 2 if it's a length depending on the minimum).
Glad I could help! =D

You have a good eye. What you point out is absolutely correct. Limiting n and m to not go below their starting point is stupid (as I learned through testing). It prevents the algorithm to work as inteded.

About culling too much, you're correct, sort of. The algorithm works fine but as you said it culls a bit too much for the block to perfectly surround the screen. These are fixed by simple adjustments to the screen size or jStart,iStart, mBuffer, nBuffer values like you said.

It may not be obvious, but increasing the nBuffer by 1, adds a row of Tiles to the RIGHT side of the screen, and mBuffer similarly adds a row of Tiles BELOW the screen:

So the way I "got it just right" was by tweaking the iStart, jStart and n,m, mBuffer etc values:

 1  2  3  4  5  6  7  8  9 `      int iStart = isoToI(sx, sy) -1;      int jStart = isoToJ(sx, sy);      int iMax = isoToI(sx+sw, sy+sh) +1;      int jMax = isoToJ(sx, sy+sh) +2;      int jMin = isoToJ(sx+sw, sy);            boolean nBump = false, mBump = false;      int n = 0, nStart = 0, nBuffer = 1;      int m = 1, mStart = 0, mBuffer = 0;`

Now this is all fine and good. And we have now answered my original question. The correct Tiles are picked up from the 2D-Array to represent a screen in the isometric world.

Now that we know which tiles from the 2D-Array to use, we can paint them! This works fine if our screen x and y positions are 0. But when we start moving the screen around this happens:

1). The tiles move WITH the screen. It does what it's supposed to do, because the screen position changes, so should the tiles used to represent the new position of the screen:

[SOLUTION]: If we want the visible screen/canvas to stay in place, we simply alter the horizontal and vertical offsets to cancel out the screens x and y position.[size=8pt]( that we're already familiar with from the OP )[/size]. I.e: horOffset = -screen.xPosition(); verOffset = -screen.yPosition();

So our paint method would look something like this

 1  2  3  4 `int horOff = screen.xPosition();int verOff = screen.yPosition();drawImage(sprite, isoX() - horOff,                             isoY() - verOff, null);`

Here's my finished code, the differences to the previous code are simple:
1) Close the inner loop before adjusting n and m;
2) remove the limitation of n and m going below their starting points
3) adjusting the starting position

 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75 `   public void paintScreen(Graphics g, Screen scr) {      // Screen is a class that holds data      // start position of the screen, and its width and height      // paint only the isometric tiles within the canvas            // We need to even out the screen size to fit each isometric tile      // to avoid small gaps      int sx = scr.x();      int sy = scr.y();      int sw = scr.width();      int sh = scr.height();            int iStart = isoToI(sx, sy) -1;      int jStart = isoToJ(sx, sy);      int iMax = isoToI(sx+sw, sy+sh) +1;      int jMax = isoToJ(sx, sy+sh) +2;      int jMin = isoToJ(sx+sw, sy);            boolean nBump = false, mBump = false;      int n = 0, nStart = 0, nBuffer = 1;      int m = 1, mStart = 0, mBuffer = 0;            for(int i=iStart; i < iMax; i++) {         for(int j=jStart-n; j < jStart+m; j++) {            // paint the column            colArray[ hWrap(i) ][ vWrap(j) ].paintAll(g);         }            // adjust m and n to keep us within the screen                        // adjust n            if(!nBump) {               //we have not yet reached the lowest j point               // increment n to go even lower next iteration               n++;               // Check if we have reached the lowest j point               if( (jStart-n) == jMin) {                  nBump = true;                     //System.out.println("Bump N");               }            } else {                  // we have reached the deepest j and are now going back                  // start decreasing after the buffer is gone                  if(nBuffer>0) {                     nBuffer--;                  } else {                     // The buffer is gone, start decreasing n each iteration                     n--;                  }               }                        // adjust m            if(!mBump) {               // we have not yet reached the HIGHEST j point               // increasee m to go even higher next iteration               m++;               // Check if we have reached the highest j point               if( (jStart+m) == jMax) {                  mBump = true;                     //System.out.println("Bump M");               }            } else {                  // we have reached the maximum j point                  // and are now moving back.                  // start decreasing m after the buffer is gone                  if(mBuffer>0) {                     mBuffer--;                  } else {                     // The Buffer is gone                     // decrease m each iteration                     m--;                  }               }      } // for loop ends         }// paintScreen() Method ends`

Eli Delventhal

JGO Kernel

Medals: 42
Projects: 11
Exp: 10 years

Game Engineer

 « Reply #9 - Posted 2011-10-17 15:05:39 »

Maybe it's slower, but I've always just looped through the vertices of each space and any are onscreen then draw the whole diamond. Works fine.

See my work:
OTC Software
 Games published by our own members! Check 'em out!
jonjava
 « Reply #10 - Posted 2011-10-17 15:26:34 »

Maybe it's slower, but I've always just looped through the vertices of each space and any are onscreen then draw the whole diamond. Works fine.

The vertices of each space.. I've little to no clue what that means - English is my third language:(. Could you clarify what you mean? I'm very much interested in hearing about your method! Regardless if it's slower, but especially if it turns out to be faster :D

Eli Delventhal

JGO Kernel

Medals: 42
Projects: 11
Exp: 10 years

Game Engineer

 « Reply #11 - Posted 2011-10-18 16:21:25 »

Well, all my logic is stored as if there is a non-isometric grid, i.e. isometric transformations are only applied to what is drawn to the screen. To do so, you can loop through all points in your grid, and do the isometric transformation on each point. Then draw anything that is onscreen.

 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17 `for (int i = 0; i < grid.length; i++){    int x = i * GRID_SIZE;    for (int j = 0; j < grid[0].length; j++)    {        int y = j * GRID_SIZE;        Vector2 isometricTopLeft = translateToIsometric(x,y);        Vector2 isometricTopRight = translateToIsometric(x + GRID_SIZE, y);        Vector2 isometricBottomRight = translateToIsometric(x + GRID_SIZE, y + GRID_SIZE);        Vector2 isometricBottomLeft = translateToIsometric(x, y + GRID_SIZE);        if (pointIsOnScreen(isometricTopLeft) || pointIsOnScreen(isometricTopRight) || pointIsOnScreen(isometricBottomRight) || pointIsOnScreen(isometricBottomLeft))        {            drawQuad(isometricTopLeft, isometricTopRight, isometricBottomRight, isometricBottomLeft);        }    }}`

Granted that's not exactly how I do it, but you should get the idea.

See my work:
OTC Software
theagentd
 « Reply #12 - Posted 2011-10-18 16:50:40 »

That is really ineffective. The number of checks is equal to the number of tiles on the whole map. The algorithm I proposed only draws the visible tiles in constant time no matter how big the map is.

Myomyomyo.
Eli Delventhal

JGO Kernel

Medals: 42
Projects: 11
Exp: 10 years

Game Engineer

 « Reply #13 - Posted 2011-10-18 17:48:36 »

That is really ineffective. The number of checks is equal to the number of tiles on the whole map. The algorithm I proposed only draws the visible tiles in constant time no matter how big the map is.
Yes, I said it was less efficient. I do stuff like caching the isometric transformations and the like, and you just end up with a few cheap comparisons each render. It's certainly simpler.

Come to think of it what makes more sense than that is just translating the screen corners from isometric space into grid space, then you exactly have the indices you need to draw. Maybe that's what you guys are doing - I didn't really look at the code.

See my work:
OTC Software
theagentd
 « Reply #14 - Posted 2011-10-18 17:56:19 »

That is really ineffective. The number of checks is equal to the number of tiles on the whole map. The algorithm I proposed only draws the visible tiles in constant time no matter how big the map is.
Yes, I said it was less efficient. I do stuff like caching the isometric transformations and the like, and you just end up with a few cheap comparisons each render. It's certainly simpler.

Come to think of it what makes more sense than that is just translating the screen corners from isometric space into grid space, then you exactly have the indices you need to draw. Maybe that's what you guys are doing - I didn't really look at the code.
That is exactly what we're doing. *POKE*

Myomyomyo.
Eli Delventhal

JGO Kernel

Medals: 42
Projects: 11
Exp: 10 years

Game Engineer

 « Reply #15 - Posted 2011-10-18 20:12:29 »

That is really ineffective. The number of checks is equal to the number of tiles on the whole map. The algorithm I proposed only draws the visible tiles in constant time no matter how big the map is.
Yes, I said it was less efficient. I do stuff like caching the isometric transformations and the like, and you just end up with a few cheap comparisons each render. It's certainly simpler.

Come to think of it what makes more sense than that is just translating the screen corners from isometric space into grid space, then you exactly have the indices you need to draw. Maybe that's what you guys are doing - I didn't really look at the code.
That is exactly what we're doing. *POKE*
Lawl well good on you.

See my work:
OTC Software
jonjava
 « Reply #16 - Posted 2011-10-19 01:34:05 »

I do stuff like caching the isometric transformations and the like, and you just end up with a few cheap comparisons each render. It's certainly simpler.

Hmmm. I use a method to calculate the Isometric X and Y position based on the 2D-Array every single time each tile is painted on the screen. Saves loads of computational power to simply have 2 additional values to store the Isometric x and y positions and only recalculate if it changes/movement occurs.

Seems so trivial and yet I'd overlooked it, thanks for pointing that out ♥

And what you said about about only "painting" the tiles so they look isometric and in reality they're stored in a 2D-Array is precisely what I was trying to convey in the pictures and explanation in the beginning of my OP.:) I do realise, however, that this topic is a huge pile of TL;DR wall of texty mess. But I learned a lot trying to explain my thoughts in these posts so I don't mind.^^

Eli Delventhal

JGO Kernel

Medals: 42
Projects: 11
Exp: 10 years

Game Engineer

 « Reply #17 - Posted 2011-10-20 21:07:24 »

Cool, glad I helped a little bit, at least. :-)

Also glad that laying your thoughts out here (seriously, this is the most detailed thought laying I have ever seen on JGO ) helped you figure things out.

See my work:
OTC Software
Pages: [1]
 ignore  |  Print

 DesertCoockie (21 views) 2018-05-13 18:23:11 nelsongames (69 views) 2018-04-24 18:15:36 nelsongames (66 views) 2018-04-24 18:14:32 ivj94 (750 views) 2018-03-24 14:47:39 ivj94 (80 views) 2018-03-24 14:46:31 ivj94 (604 views) 2018-03-24 14:43:53 Solater (96 views) 2018-03-17 05:04:08 nelsongames (169 views) 2018-03-05 17:56:34 Gornova (387 views) 2018-03-02 22:15:33 buddyBro (1047 views) 2018-02-28 16:59:18
 Java Gaming Resourcesby philfrei2017-12-05 19:38:37Java Gaming Resourcesby philfrei2017-12-05 19:37:39Java Gaming Resourcesby philfrei2017-12-05 19:36:10Java Gaming Resourcesby philfrei2017-12-05 19:33:10List of Learning Resourcesby elect2017-03-13 14:05:44List of Learning Resourcesby elect2017-03-13 14:04:45SF/X Librariesby philfrei2017-03-02 08:45:19SF/X Librariesby philfrei2017-03-02 08:44:05
 java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org