[SOLVED] Gaps between tiles in motion?

Like in topic. I have problem with gaps between tiles in motion. When you move camera you can see white gaps between tiles. You can sometimes see them when camera is staying in one place, too. I can’t find any solution for this problem, I hav found only one way to make it less visible (replacing GL_NICEST with GL_NEAREST in texture creating texParameters, increasing rendering coordinates by 0,1f - after this changes you can see this bug only when camera is moving).

EDIT: Problem is solved. Thanks for all help!

What are you using GL_NICEST for?!

Most of the time “bleeding” has to do with which pixels GL samples from when rendering. See here:

Now my texture creating code and displaying textures looks like this:

    public static int create(TextureHolder tex) {
        int uniqueId = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, uniqueId);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        return uniqueId;
    }

    public static void draw(Tex tex, float x, float y, float angle) {
        glBindTexture(GL_TEXTURE_2D, tex.ID);
        glPushMatrix();
            glTranslatef(x, y, 0);
            glRotated(angle/(Math.PI/180), 0, 0, 1);
            glBegin(GL_QUADS);
                glColor4f(1,1,1,1);
                glTexCoord2f(tex.minBindX,tex.maxBindY); glVertex2f(-tex.width/2-0.1f, -tex.height/2-0.1f);
                glTexCoord2f(tex.minBindX,tex.minBindY); glVertex2f(-tex.width/2-0.1f, tex.height/2+0.1f);
                glTexCoord2f(tex.maxBindX,tex.minBindY); glVertex2f(tex.width/2+0.1f, tex.height/2+0.1f);
                glTexCoord2f(tex.maxBindX,tex.maxBindY); glVertex2f(tex.width/2+0.1f, -tex.height/2-0.1f);
            glEnd();
        glPopMatrix();
    }

This leads to white lines “blinking” when scrolling and sometimes when camera is not moving anyway, like in screen below:

http://img526.imageshack.us/img526/7030/beztytuuvqr.png

Seems like floating point rounding errors in your vertex positions. Try to make each tile slightly larger than they are.

Already tried it, this makes nothing but more problems with another things and doesn’t fix main problem.

Try to feed the coordinates directly into glVertex() instead of moving the matrix. That should also be faster.

Could you explain this?

You need to calculate mathematically identical vertices the exact same way, or floating point errors will make them slightly different. For example, x*2 might not exactly equal x+x. It’s all a lot more complicated since you translate a matrix inbetween and all.

Basically, change your tiles so that they’re drawn like this:


//mapWidth and mapHeight are the size of the tile map in tiles, not pixels.
glBegin(GL_QUADS);
for(int y = 0; y < mapHeight; y++){
    for(int x = 0; x < mapWidth; x++){
        glTexCoord2f(...); glVertex2f(x * tileWidth, y * tileHeight);
        glTexCoord2f(...); glVertex2f((x + 1) * tileWidth, y * tileHeight);
        glTexCoord2f(...); glVertex2f((x + 1) * tileWidth, (y + 1) * tileHeight);
        glTexCoord2f(...); glVertex2f(x * tileWidth, (y + 1) * tileHeight);
    }
}
glEnd();

Note how the vertex positions are calculated. It’s important that you write it the same way as I did here. Do NOT write [icode]x * tileWidth + tileWidth[/icode] since that is exactly what you need to avoid.

Despite the changes I don’t see any difference. Maybe is this related to camera? (game must be zoomable or at least scrollable)

If that still gives you seams, then you’re not doing it correctly. 3D games never have seams in their models, right? Could you post your tile rendering code again?

All codes taking part in rendering:

Texture creation and rendering

public static int create(TextureHolder tex) {
        int uniqueId = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, uniqueId);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        return uniqueId;
    }
    
    public static void draw(Tex tex, float x, float y, float angle) {
        glBindTexture(GL_TEXTURE_2D, tex.ID);
        glPushMatrix();
            glTranslatef(x, y, 0);
            glRotated(angle/(Math.PI/180), 0, 0, 1);
            glBegin(GL_QUADS);
                glColor4f(1,1,1,1);
                glTexCoord2f(tex.minBindX,tex.maxBindY); glVertex2f(-tex.width/2-0.1f, -tex.height/2-0.1f);
                glTexCoord2f(tex.minBindX,tex.minBindY); glVertex2f(-tex.width/2-0.1f, tex.height/2+0.1f);
                glTexCoord2f(tex.maxBindX,tex.minBindY); glVertex2f(tex.width/2+0.1f, tex.height/2+0.1f);
                glTexCoord2f(tex.maxBindX,tex.maxBindY); glVertex2f(tex.width/2+0.1f, -tex.height/2-0.1f);
            glEnd();
        glPopMatrix();
    }
    
    public static void drawTiles(Tex tex, float x, float y, int rect) {
        glBindTexture(GL_TEXTURE_2D, tex.ID);
        glBegin(GL_QUADS);
            glColor4f(1,1,1,1);
            glTexCoord2f(tex.minBindX,tex.maxBindY); glVertex2f(x*rect, y*rect);
            glTexCoord2f(tex.minBindX,tex.minBindY); glVertex2f(x*rect, (y+1)*rect);
            glTexCoord2f(tex.maxBindX,tex.minBindY); glVertex2f((x+1)*rect, (y+1)*rect);
            glTexCoord2f(tex.maxBindX,tex.maxBindY); glVertex2f((x+1)*rect, y*rect);
        glEnd();
    }

Whole camera class

package Core.Camera;

import Core.Logic.Logic;
import org.lwjgl.opengl.Display;
import static org.lwjgl.opengl.GL11.*;

public class Camera {
    
    public static float cameraX = 0;
    public static float cameraY = 0;
    protected static float cameraThrustX = 0;
    protected static float cameraThrustY = 0;
    protected static int cameraThrustXMax = 5;
    protected static int cameraThrustYMax = 5;
    
    public static void interfaceCamera() {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, Display.getWidth(), 0, Display.getHeight(), 1, -1);
        glMatrixMode(GL_MODELVIEW);
    }
    
    public static void gameCamera(int width, int height) {
        if (Logic.mouse.x<15 || Logic.keyboard.LeftArrow) {
            cameraThrustX-=0.1;
            if (cameraThrustX<-cameraThrustXMax) {cameraThrustX=-cameraThrustXMax;}
        }
        else if (Logic.mouse.x>Display.getWidth()-15 || Logic.keyboard.rightArrow) {
            cameraThrustX+=0.1;
            if (cameraThrustX>cameraThrustXMax) {cameraThrustX=cameraThrustXMax;}
        }
        else {
            if (cameraThrustX>0.1) {cameraThrustX-=0.1;}
            else if (cameraThrustX<-0.1) {cameraThrustX+=0.1;}
            else {cameraThrustX=0;}
        }
        if (Logic.mouse.y<15 || Logic.keyboard.downArrow) {
            cameraThrustY-=0.1;
            if (cameraThrustY<-cameraThrustYMax) {cameraThrustY=-cameraThrustYMax;}
        }
        else if (Logic.mouse.y>Display.getHeight()-15 || Logic.keyboard.upArrow) {
            cameraThrustY+=0.1;
            if (cameraThrustY>cameraThrustYMax) {cameraThrustY=cameraThrustYMax;}
        }
        else {
            if (cameraThrustY>0.1) {cameraThrustY-=0.1;}
            else if (cameraThrustY<-0.1) {cameraThrustY+=0.1;}
            else {cameraThrustY=0;}
        }
        cameraX+=cameraThrustX;
        cameraY+=cameraThrustY;
        
        if (cameraX<-10) {cameraX=-10;}
        else if (cameraX>width*20-10-Display.getWidth()) {cameraX=width*20-10-Display.getWidth();}
        
        if (cameraY<-10) {cameraY=-10;}
        else if (cameraY>height*20-10-Display.getHeight()) {cameraY=height*20-10-Display.getHeight();}
        
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(cameraX, Display.getWidth()+cameraX, cameraY, Display.getHeight()+cameraY, 1, -1);
        glMatrixMode(GL_MODELVIEW);
    }
    
    public static void gameCameraStop(int width, int height) {
        if ((Logic.mouse.x<15 && Logic.mouse.y<15) || Logic.keyboard.LeftArrow) {
            cameraThrustX-=0.1;
            if (cameraThrustX<-cameraThrustXMax) {cameraThrustX=-cameraThrustXMax;}
        }
        else if ((Logic.mouse.x>Display.getWidth()-15 && Logic.mouse.y<15) || Logic.keyboard.rightArrow) {
            cameraThrustX+=0.1;
            if (cameraThrustX>cameraThrustXMax) {cameraThrustX=cameraThrustXMax;}
        }
        else {
            if (cameraThrustX>0.1) {cameraThrustX-=0.1;}
            else if (cameraThrustX<-0.1) {cameraThrustX+=0.1;}
            else {cameraThrustX=0;}
        }
        if ((Logic.mouse.x<15 && Logic.mouse.y<15) || (Logic.mouse.x>Display.getWidth()-15 && Logic.mouse.y<15) || Logic.keyboard.downArrow) {
            cameraThrustY-=0.1;
            if (cameraThrustY<-cameraThrustYMax) {cameraThrustY=-cameraThrustYMax;}
        }
        else if (Logic.keyboard.upArrow) {
            cameraThrustY+=0.1;
            if (cameraThrustY>cameraThrustYMax) {cameraThrustY=cameraThrustYMax;}
        }
        else {
            if (cameraThrustY>0.1) {cameraThrustY-=0.1;}
            else if (cameraThrustY<-0.1) {cameraThrustY+=0.1;}
            else {cameraThrustY=0;}
        }
        
        cameraX+=cameraThrustX;
        cameraY+=cameraThrustY;
        
        if (cameraX<-10) {cameraX=-10;}
        else if (cameraX>width*20-10-Display.getWidth()) {cameraX=width*20-10-Display.getWidth();}
        
        if (cameraY<-10) {cameraY=-10;}
        else if (cameraY>height*20-10-Display.getHeight()) {cameraY=height*20-10-Display.getHeight();}
        
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(cameraX, Display.getWidth()+cameraX, cameraY, Display.getHeight()+cameraY, 1, -1);
        glMatrixMode(GL_MODELVIEW);
    }
    
}

Fragments of GameTerrain and Tile (I am using draw there because drawTiles gives the same results):

public void draw() {
        toX = (int) ((Camera.cameraX+Display.getWidth())/20+1);
        if (toX<width) {toX=width;}
        toY = (int) ((Camera.cameraY+Display.getHeight())/20+1);
        if (toY<height) {toY=height;}
        for (int i=(int) Camera.cameraX/20; i<toX; i++) {
            for (int i2=(int) (Camera.cameraY/20); i2<toY; i2++) {
                if (tiles[i][i2]!=null) {
                    tiles[i][i2].draw();
                }
            }
        }
public void draw() {
        Lib.Graphics.Texture.draw(tex, locWidth, locHeight, 0);
    }

How did the rendering look like when you used drawTiles()?

There is 10 pixels “shift” in everything (but it can be easily fixed), at max acceleration there are gaps all the time, blinking gaps while moving and sometimes when nothing is moving, too (you must stop camera in right position, I made this in few approaches).

Yes, but how does the CODE look?

Also, are you using a power-of-two texture?

Power-of-two-texture? No, every part of sprite sheet have size 20X20.

Code with drawTiles - it is the same for GameTerrain, but we can see differences in Tile:


public void draw() {
        Lib.Graphics.Texture.drawTiles(tex, locWidth, locHeight, 20);
    }

Where tex contains ID of texture, locWidth - x coordinate in grid, locHeight - y coordinate, 20 - size of single grid tile.

Are you sure the problem is that there are seems between the tiles? It could be a problem with the texture reading the tile next to it. You can test that by changing the color of the background/clear color. If the seems have that color too, they’re because of the vertex coordinates. Otherwise they’re because of the texture coordinates.

Exactly how is locWidth and locHeight calculated? drawTiles() should take in local tile coordinates in INTS! If you want to offset all tiles by a specific value (cameraX, cameraY I suppose), you can use glTranslatef() to do that in one call before you start rendering tiles the tiles. Just don’t call glTranslatef() once for each tile.

Red background and gaps between tiles are still white. It seems that there is something wrong with texture coordinates.

This is my texture preloading from sprite sheet code:

private Tex preloadSymSprite(SpriteSheet source, float regX, float regY, float rect) {
        regX--;
        regY--;
        bind=new Tex();
        bind.ID = source.ID;
        bind.width = (int) rect;
        bind.height = (int) rect;
        float height = source.height;
        float width = source.width;
        bind.minBindX=(regX*rect)/width;
        bind.minBindY=(regY*rect)/height;
        bind.maxBindX=((regX*rect)+rect)/width;
        bind.maxBindY=((regY*rect)+rect)/height;
        return bind;
    }

Example execution:

private void roads() {
        LT.road.upDown = preloadSymSprite(LT.road.roads, 1, 1, 20);
        LT.road.leftRight = preloadSymSprite(LT.road.roads, 2, 1, 20);
        LT.road.none = preloadSymSprite(LT.road.roads, 3, 1, 20);
        LT.road.all = preloadSymSprite(LT.road.roads, 4, 1, 20);
        LT.road.downRight = preloadSymSprite(LT.road.roads, 1, 2, 20);
        LT.road.downLeft = preloadSymSprite(LT.road.roads, 2, 2, 20);
        LT.road.upRight = preloadSymSprite(LT.road.roads, 1, 3, 20);
        LT.road.upLeft = preloadSymSprite(LT.road.roads, 2, 3, 20);
        LT.road.up = preloadSymSprite(LT.road.roads, 1, 4, 20);
        LT.road.right = preloadSymSprite(LT.road.roads, 2, 4, 20);
        LT.road.down = preloadSymSprite(LT.road.roads, 3, 4, 20);
        LT.road.left = preloadSymSprite(LT.road.roads, 4, 4, 20);
        LT.road.upDownLeft = preloadSymSprite(LT.road.roads, 4, 2, 20);
        LT.road.downLeftRight = preloadSymSprite(LT.road.roads, 3, 2, 20);
        LT.road.upDownRight = preloadSymSprite(LT.road.roads, 3, 3, 20);
        LT.road.upLeftRight = preloadSymSprite(LT.road.roads, 4, 3, 20);
    }

Can be something wrong in there? I checked sprite sheet .png, it seems that there is nothing wrong with it (everything is pixel perfect).

EDIT: It MUST be problem with texture coordinates. Debug drawing code below is perfect. Now question what is the cause of the problem…

public static void draw(Tex tex, float x, float y, float angle) {
        glBindTexture(GL_TEXTURE_2D, tex.ID);
        glPushMatrix();
            glTranslatef(x, y, 0);
            glRotated(angle/(Math.PI/180), 0, 0, 1);
            glBegin(GL_QUADS);
                glColor4f(1,1,1,1);
                glTexCoord2f(tex.minBindX+0.0001f,tex.maxBindY-0.0001f); glVertex2f(-tex.width/2, -tex.height/2);
                glTexCoord2f(tex.minBindX+0.0001f,tex.minBindY+0.0001f); glVertex2f(-tex.width/2, tex.height/2);
                glTexCoord2f(tex.maxBindX-0.0001f,tex.minBindY+0.0001f); glVertex2f(tex.width/2, tex.height/2);
                glTexCoord2f(tex.maxBindX-0.0001f,tex.maxBindY-0.0001f); glVertex2f(tex.width/2, -tex.height/2);
            glEnd();
        glPopMatrix();
    }

Then it’s probably because you’re using a non-power-of-two texture. If you think about how floating point values work, you realize that they can only hold exact coordinates for X/(power of two) values. If you have a non-power-of-two texture, for some pixels the values might be rounded over a tile border and end up on the wrong tile. With power-of-two textures that should be impossible since the rounding follows conventions when sampling pixels on exact texel edges.

EDIT: In other words, try to pad the texture to make it a power-of-two sized.
EDIT2: Or use a texture rectangle instead, which takes unnormalized texture coordinates.

Just a note… if you are using Slick’s texture loader, it will pad non-power-of-two textures for you with black transparent pixels. This may lead to bleeding in some cases, along the edge of the image’s texture region.

For seamless tiles you can also pre-process your sprite sheets and mirror the texture edges, as described in the post I linked earlier. LibGDX’s TexturePacker-GUI can do this for you, or you could write your own tool fairly easily with Java2D.

Of course the ideal solution would be to use texture arrays and GL_CLAMP_TO_EDGE, but that requires GL3+.