Modifying textures using libGDX Pixmap in runtime - Explained

We have previously shown a bit how we were using LibGDX Pixmap to modify textures in runtime here and here for a game prototype we were doing. In this post I want to share more detail of how we do that. The objective was to make destructible terrain like in Worms 2.

Introduction

When you work with OpenGL textures, you can't directly modify their pixels whenever you want since they are on OpenGL context. To modify them you have to upload an array of bytes using glTexImage2D or glTexSubImage2D. The problem is you have to maintain on the application side an array of bytes representing the modifications you want to do.

To simplify working with byte arrays representing images, LibGDX provides a useful class named Pixmap which is a map of pixels kept in local memory with some methods to interact with a native library to perform all modifications with better performance.

Moving data from Pixmap to OpenGL Texture

In our prototypes, we wanted to remove part of the terrain whenever a missile touches it, like a Worms 2 explosion. That means we need some way to detect the collisions between the missile and the terrain and then a way to remove pixels from a texture.

We simplified the first problem by getting the color of the pixel only for the missile's position and checking if it was transparent or not. A more correct solution could be using a bitmap mask to check collisions between pixels but we wanted to simplify the work for now.

For the second problem, given a radius of explosion of the missile, we used the pixmap fillCircle method by previously setting the color to (0,0,0,0) (fully transparent) and disabled Pixmap blending to override those pixels.

But that only modified the pixmap data, now we needed to modify the OpenGL texture. To do that, we called OpenGL glTexImage2D using the bytes of the pixmap as the new texture data and that worked correctly.

Transforming from world coordinates to Pixmap coordinates

One problem when working with pixmaps is we have to map world coordinates (the position of the missile for example) to coordinates inside the Pixmap.


This image shows the coordinate system of the Pixmap, it goes from 0 to width in x and 0 to height in y.


This image shows how we normally need to move, rotate and resize the Pixmap in a game.

To solve this, we are using a LibGDX Sprite to maintain the Pixmap transformation, so we can easily move, rotate and scale it. Then, we can use that information to project a world coordinate to Pixmap coordinate by applying the inverse transform, here is the code:

	public void project(Vector2 position, float x, float y) {
		position.set(x, y);

		float centerX = sprite.getX() + sprite.getOriginX();
		float centerY = sprite.getY() + sprite.getOriginY();

		position.add(-centerX, -centerY);

		position.rotate(-sprite.getRotation());

		float scaleX = pixmap.getWidth() / sprite.getWidth();
		float scaleY = pixmap.getHeight() / sprite.getHeight();

		position.x *= scaleX;
		position.y *= scaleY;

		position.add( //
				pixmap.getWidth() * 0.5f, //
				-pixmap.getHeight() * 0.5f //
		);

		position.y *= -1f;
	}

(note: it is the first version at least, it could have bugs and could be improved also)

To simplify our work with all this stuff, we created a class named PixmapHelper which manage a Pixmap, a Texture and a Sprite, so we could move the Sprite wherever we wanted to and if we modify the pixmap through the PixmapHelper then the Texture was automatically updated and hence the Sprite (since it uses internally the Texture).

The next video shows how we tested the previous work in a prototype were we simulated cluster bombs (similar to Worms 2):

Some adjustments to improve performance

Instead of always working with a full size Pixmap by modifying it and then moved to the OpenGL texture, we created smaller Pixmaps of fixed sizes: 32x32, 64x64, etc. Then, each time we needed to make an explosion, we used the best Pixmap for that explosion and then we called glTexSubImage2D instead glTexImage2D to avoid updating untouched pixels. One limitation of this modification is we have to create and maintain several fixed size pixmaps depending on the modification size. Our current greater quad is 256x256 (almost never used).

Then, we changed to store each modification instead performing them in the moment the PixmapHelper erase method was called, and we added an update method which performs all modifications together. This improvement allow us to call Pixmap update method when we wanted, maybe one in three game updates or things like that.

Conclusion

Despite using LibGDX Pixmap for better performance, moving data to and from OpenGL context is not a cheap operation, on Android devices this could means some pauses when refreshing the modified textures with the new data. However, there is a lot of space for performance improvement, some ideas are to work only with pixmap of less bits instead RGBA8888 and use that one as the collisions context and as the mask of the real image (even using shaders), between other ideas.

Finally, the technique looks really nice and we believe that it could be used without problems for a simple game but it is not ready yet to manage a greater game like Worms 2.

Hope you like the technique and if you use it, maybe even also share your findings.

P.S.: In case you were wondering: yes, I love Worms 2.

VN:F [1.9.22_1171]
Rating: 4.0/5 (4 votes cast)
Modifying textures using libGDX Pixmap in runtime - Explained, 4.0 out of 5 based on 4 ratings

Tags: , , , ,

  • Anonymous

    Use an alpha texture (8-bit), and use shader to combine that with the RGBA texture. Tada, memory transfer reduced by 4.

  • Anonymous

    Nice tutorial and nice video: I've always loved Worms 2... on a tablet could be a fun game to play with friends

  • arielsan

    That was the idea in mind, I have not so clear how to do it but seems the reasonable step

  • http://www.facebook.com/shane.burke.37 Shane Burke

    Great post, but I have an issue. Instead of me seeing what's behind the pixmap that's being modifies, I just see black.

  • Shane

    Never mind. I fixed it, with a different approach.

  • arielsan

    I just read your comments, nice you fixed it. Feel free to share your approach with us :D

  • momo

    hi nice post! have a little problem with erasing pixmap! i'm trying to move a sprite on a pixmap to erase it but i got nothing can you help me please?? this is my code
    Blending blending = Pixmap.getBlending();            pixmap.setColor(1f, 1f, 1f, 0f);            Pixmap.setBlending(Blending.None);            pixmap.fillCircle( X,  Y, 40);            texture.draw(pixmap, 0, 0);Pixmap.setBlending(blending);

    it erase the pixmap but apply the color in parameter in setColor without applying the alpha value so i can't see the background under the pixmap
    any idea?

  • arielsan

     Hi momo,

    It is strange, your code is similar to the PixmapHelper class I did and that one works. If you can, take a look at that class at https://github.com/gemserk/angryships/blob/c497c2786594380dd195096a2af521e98566526a/angryships-core/src/main/java/com/gemserk/prototypes/pixmap/PixmapHelper.java again to see any differences, maybe the texture.draw() should be called after the PIxmap.setBlending(...) but I am not sure.

  • momo

    thanks for your answer Arielsan ;)

    yes!  your code inspired me, but i don't know what's wrong i took a look to your source code and i think you just draw a circle on the pixmap in order to clear the background. When i did the same thing,it does not work. The second problem is when i draw the pixmap on texture, it decrease the framerate 

  • arielsan

    About the framerate, yeah, drawing all the texture each time is a bit heavy, that is why I tried to do it drawing only regions of the texture and to only update the texture if something changed, not in each update of the game loop. Take that in mind.

  • momo

    ok thanks! but for modifying pixmap i have two blending possible (the default one source and None) then when i apply the blending None it disable the alpha value and erase the pixmap by using the setcolor value, but when i apply the the default one (source), it apply the alpha value but not on the pixmap!! how can i solve this issue?
    here is my code: i'm trying to replace directly pixmap values  for (int i = 0; i < 100; i++) {          for (int j = 0; j < 100; j++) {          pixmap.drawPixel(i + X, j + Y, Color.rgba8888(1, 1, 1, 0));           }   }Pixmap.setBlending(blending);Gdx.gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, pixmap.getGLInternalFormat(), pixmap.getWidth(), pixmap.getHeight(), 0, pixmap.getGLFormat(),                                pixmap.getGLType(), pixmap.getPixels());texture.draw(pixmap, 0, 0);

  • arielsan

    Hi again momo, I believe you can't override the previous alpha value using the Blending.SourceOver, what you can do is make this in two steps, in the first one you use None and erase what is behind and in the second step you can draw using SourceOver. Seems a bit bad for performance so I am not sure if it is viable. Btw, you can try asking other developers in #badlogic channel at IRC or in the LibGDX forum http://www.badlogicgames.com/forum/

  • momo

    Hi! finally we find a solution :)) it consist just to use frameBuffer like this example 
    http://stackoverflow.com/questions/7551669/libgdx-spritebatch-render-to-texture we  to draw elements in frameBufferRegion and  at last draw region on spritebatch so you got the same framerate :if (m_fbo == null) {
      m_fbo = new FrameBuffer(Format.RGBA8888, (int) (width), (int) (height), false);
      m_fboRegion = new TextureRegion(m_fbo.getColorBufferTexture());
      m_fboRegion.flip(false, false);

      m_fbo.begin();
      director.getSpriteBatch().begin();
      director.getSpriteBatch().draw(foreground, 0, 0);
      director.getSpriteBatch().end();
      m_fbo.end();

      }and on the draw method do thisbatch.draw(m_fboRegion, 0, 0);

  • momo

    on your touch drag :

    m_fbo.begin(); director.getSpriteBatch().begin(); director.getSpriteBatch().setBlendFunction(GL20.GL_ZERO, GL20.GL_ONE_MINUS_SRC_ALPHA); director.getSpriteBatch().enableBlending();  // Gdx.gl.glDisable(GL20.GL_DEPTH_TEST);  Gdx.gl.glColorMask(false, false, false, true); director.getSpriteBatch().draw(pen, touchX, touchY);  Gdx.gl.glColorMask(true, true, true, true);  director.getSpriteBatch().end();  m_fbo.end(); director.getSpriteBatch().setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

  • Allen

    Hi
    Im a beginner in LibGdx
    Is there any way we can rotate pixmap?

  • Allen

    I mean like is there any function like
    pixmap.rotate() ?

  • Ariel Coppes

    You can specify that when rendering it, you don't need to rotate the original texture data. For example, you can create a Sprite using a texture with the pixmap data, rotate it and render it.

  • Leonardo Zimbres

    loving to read about.