Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm having problems understanding how to apply a texture to a non-rectangular object. The following code creates textures such as this:

enter image description here

from the debug renderer I think I've got the physical shape of the "earth" correct. However, I don't know how to apply a texture to it. I have a 50x50 pixel image (in the environment constructor as "dirt.png"), that I want to apply to the hills. I have a vague idea that this seems to involve the mesh class and possibly a ShapeRenderer, but the little i'm finding online is just confusing me.

Bellow is code from the class that makes and regulates the terrain and the code in a separate file that is supposed to render it (but crashes on the mesh.render() call). Any pointers would be appreciated.

public class Environment extends Actor{
      Pixmap sky;
      public Texture groundTexture;
      Texture skyTexture;
      double tankypos; //TODO delete, temp
      public Tank etank; //TODO delete, temp
      int destructionRes; // how wide is a static pixel
      private final float viewWidth;
      private final float viewHeight;
      private ChainShape terrain;
      public Texture dirtTexture;
      private World world;
      public Mesh terrainMesh;

      private static final String LOG = Environment.class.getSimpleName();

      // Constructor
      public Environment(Tank tank, FileHandle sfileHandle, float w, float h, int destructionRes) {
        world = new World(new Vector2(0, -10), true);
        this.destructionRes = destructionRes;
        sky = new Pixmap(sfileHandle);
        viewWidth = w;
        viewHeight = h;
        skyTexture = new Texture(sky);
        terrain = new ChainShape();
        genTerrain((int)w, (int)h, 6);

        Texture tankSprite = new Texture(Gdx.files.internal("TankSpriteBase.png"));
        Texture turretSprite = new Texture(Gdx.files.internal("TankSpriteTurret.png"));
        tank = new Tank(0, true, tankSprite, turretSprite);
        Rectangle tankrect = new Rectangle(300, (int)tankypos, 44, 45);
        tank.setRect(tankrect);

        BodyDef terrainDef = new BodyDef();  
        terrainDef.type = BodyType.StaticBody;  
        terrainDef.position.set(0, 0);  
        Body terrainBody = world.createBody(terrainDef);

        FixtureDef fixtureDef = new FixtureDef();  
        fixtureDef.shape = terrain;
        terrainBody.createFixture(fixtureDef);

        BodyDef tankDef = new BodyDef();
        Rectangle rect = tank.getRect();
        tankDef.type = BodyType.DynamicBody;
        tankDef.position.set(0,0);
        tankDef.position.x = rect.x;
        tankDef.position.y = rect.y;
        Body tankBody = world.createBody(tankDef);

        FixtureDef tankFixture = new FixtureDef();
        PolygonShape shape = new PolygonShape();
        shape.setAsBox(rect.width*WORLD_TO_BOX, rect.height*WORLD_TO_BOX);
        fixtureDef.shape = shape;

        dirtTexture = new Texture(Gdx.files.internal("dirt.png"));
        etank = tank;
      }

      private void genTerrain(int w, int h, int hillnessFactor){
            int width = w;
            int height = h;

            Random rand = new Random();
            //min and max bracket the freq's of the sin/cos series
            //The higher the max the hillier the environment
            int min = 1;

            //allocating horizon for screen width
            Vector2[] horizon = new Vector2[width+2];
            horizon[0] = new Vector2(0,0);
            double[] skyline =  new double[width]; //TODO skyline necessary as an array?

            //ratio of amplitude of screen height to landscape variation
            double r = (int) 2.0/5.0;

            //number of terms to be used in sine/cosine series
            int n = 4;
            int[] f = new int[n*2];

            //calculating omegas for sine series
            for(int i = 0; i < n*2 ; i ++){             
                f[i] =  rand.nextInt(hillnessFactor - min + 1) + min;
            }

            //amp is the amplitude of the series
            int amp =  (int) (r*height);
            double lastPoint = 0.0;                             
            for(int i = 0 ; i < width; i ++){
                skyline[i] = 0;

                for(int j = 0; j < n; j++){
                    skyline[i] += ( Math.sin( (f[j]*Math.PI*i/height) ) +  Math.cos(f[j+n]*Math.PI*i/height) );
                }

                skyline[i] *= amp/(n*2);
                skyline[i] += (height/2);
                skyline[i] = (int)skyline[i]; //TODO Possible un-necessary float to int to float conversions
                tankypos = skyline[i];
                horizon[i+1] = new Vector2((float)i, (float)skyline[i]);
                if(i == width) lastPoint = skyline[i];
            }
            horizon[width+1] = new Vector2(800, (float)lastPoint);
            terrain.createChain(horizon);
            terrain.createLoop(horizon);

                    //I have no idea if the following does anything useful :(
            terrainMesh = new Mesh(true, (width+2)*2, (width+2)*2, 
                    new VertexAttribute(Usage.Position, (width+2)*2, "a_position"));
            float[] vertices = new float[(width+2)*2];
            short[] indices = new short[(width+2)*2];
            for(int i=0; i < (width+2); i+=2){
                vertices[i] = horizon[i].x;
                vertices[i+1] = horizon[i].y;
                indices[i] = (short)i;
                indices[i+1] = (short)(i+1);
            }
            terrainMesh.setVertices(vertices);
            terrainMesh.setIndices(indices);
      }

Here is the code that is (supposed to) render the terrain.

@Override
public void render(float delta) {
    Gdx.gl.glClearColor(1, 1, 1, 1);
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

    // tell the camera to update its matrices.
    camera.update();

    // tell the SpriteBatch to render in the
    // coordinate system specified by the camera.

    backgroundStage.draw();
    backgroundStage.act(delta);

    uistage.draw();
    uistage.act(delta);

    batch.begin();
    debugRenderer.render(this.ground.getWorld(), camera.combined);
    batch.end();

    //Gdx.graphics.getGL10().glEnable(GL10.GL_TEXTURE_2D);
    ground.dirtTexture.bind();
    ground.terrainMesh.render(GL10.GL_TRIANGLE_FAN); //I'm particularly lost on this
    ground.step();
}
share|improve this question
    
I found a solution for this but it causes slight stuttering and does not work when scaled down to the World scale. Did you ever find a solution? https://stackoverflow.com/questions/22254727/libgdx-polygonspritebatch-pixelate‌​d-terrain-at-world-scale –  truck0321 Mar 12 at 18:52
    
Take a look at iforce2d's description of how he rendered terrain in Downhill Supreme. It might be useful. –  NauticalMile Jun 29 at 13:35

1 Answer 1

Well, you need to do a lot of things in order to texture random terrain. At this point, you don't even have texture coordinates in your vertices. But assuming you can implement all those other required steps, the general method is simple.

  1. Use a texture mode that wraps both X and Y (in libgdx,Texture.TextureWrap.Repeat)
  2. Make the texture coordinates proportional to world position of the vertex.

(Now that I think of it, it might be possible to use "excessive cleverness" to avoid the texture coordinates, and program your shader to sample the texture based on the vertex world position. But somehow that seems like a generally bad idea.)


Some of my implementation:

private Mesh MakeMesh(float[] heights) {

    // SEGMENT_WIDTH = magic number for space between heights

    float[] verts = new float[10 * heights.length];

    for (int i = 1; i <= (heights.length); i++){

        verts[10 * i - 10] = (i - 1) * SEGMENT_WIDTH;
        verts[10 * i - 9] = heights[i - 1];
        verts[10 * i - 8] = 0;
        verts[10 * i - 7] = (i - 1);
        verts[10 * i - 6] = 0;

        verts[10 * i - 5] = (i - 1) * SEGMENT_WIDTH;
        verts[10 * i - 4] = 0;
        verts[10 * i - 3] = 0;
        verts[10 * i - 2] = (i - 1);
        verts[10 * i - 1] = heights[i - 1] / SEGMENT_WIDTH;
    }

    short[] indices = new short[6 * heights.length];

    for (short i = 1; i < heights.length; i++) {
        indices[6 * i - 6] = (short) (i * 2 - 2);
        indices[6 * i - 5] = (short) (i * 2 - 1);
        indices[6 * i - 4] = (short) (i * 2 - 0);
        indices[6 * i - 3] = (short) (i * 2 - 1);
        indices[6 * i - 2] = (short) (i * 2 - 0);
        indices[6 * i - 1] = (short) (i * 2 + 1);
    }

    mesh = new Mesh(Mesh.VertexDataType.VertexArray, 
        true, verts.length / 5, indices.length, 
        VertexAttribute.Position(), VertexAttribute.TexCoords(0));
    mesh.setVertices(verts);
    mesh.setIndices(indices);

    return mesh;
}

And drawing:

public void draw(Matrix4 worldView){

    texture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat);
    texture.bind(0);

    shader.begin();
    shader.setUniformMatrix("u_worldView", worldView);
    shader.setUniformi("u_texture", 0);
    mesh.render(shader, GL20.GL_TRIANGLES);
    shader.end();
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.