Search code examples
javalibgdxglsl

Adding thickness to a 2D sprite when turning


In my game, my entities turn like a piece of paper, as shown here at half-speed: https://i.sstatic.net/SmDNQ.jpg

I want to give the entities a bit of thickness when they turn, making them more cardboard-thin than paper-thin.
I thought about using a Pixmap to detect and extend the edge pixels and give the image some Three-Dimensionality. I also considered duplicating the image along the x-axis to give the same effect. Of the two ideas, the Pixmap holds out the most promise in my mind. However, I'm wondering if there's a better solution.

I'm using a GLSL shader to give the entities highlights and shadows while turning, as you saw in the gif. I think that with the right knowledge, I could achieve what I'm going for using the same shader program.

My shader looks like this:

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 v_color;
varying vec2 v_texCoords;

uniform sampler2D u_texture;
uniform vec2 u_resolution;

uniform vec3 color;

void main()
{
    vec4 col = vec4(color, 0.0);
    gl_FragColor = texture2D(u_texture, v_texCoords) * v_color + col;
}

I think that one might be able to make calculations based on the uniform vec3 color that I pass it (with its values ranging from 0, 0, 0 to 1, 1, 1. 1's being highlight and 0's being shadow). Unfortunately, I don't have the understanding of shaders to do so.
If any of you have the know-how, could you steer me in the right direction? Or let me know if I should just stick to the Pixmap idea.

Edit: I'm trying to stay away from using a 3D model because I'm 6.4k lines of code deep using a 2d Orthographic Camera.
Edit 2: I figured that the reflection shader wouldn't look good if I tried making the sprite look 3D. I scrapped the shader, went with the Pixmap idea, and plan on implementing shadows and reflections to the pixmap without any shader. Though it looks good so far without reflections.


Solution

  • I ended up going with my pixmap idea. I want to share my code so that others can know how I got 2D thickness to work.

    Please note that in the following code:

    dir is a floating point value in the range -1.0 to 1.0. It tells the program where the sprite is in its turn. -1 means facing fully left. 1 meaning right. 0 means that it's 'facing' the camera.

    right is a boolean that tells the program which direction the entity is turning. true means that the entity is turning from left to right. false means from right to left.

    The Code:

    private Texture getTurningImage(TextureRegion input, int thickness)
    {
        if(Math.abs(dir) < 0.1)
            dir = (right ? 1 : -1) * 0.1f;
        Texture texture = input.getTexture();
        if (!texture.getTextureData().isPrepared()) 
        {
            texture.getTextureData().prepare();
        }
            
        Pixmap pixmap = texture.getTextureData().consumePixmap();
            
        Pixmap p = new Pixmap(64, 64, Pixmap.Format.RGBA8888);
        p.setFilter(Pixmap.Filter.NearestNeighbour);
            
        Pixmap texCopy = new Pixmap(input.getRegionWidth(), input.getRegionHeight(), Pixmap.Format.RGBA8888);
    
        // getting a texture out of the input region. I can't use input.getTexture()
        // because it's an animated sprite sheet
        for (int x = 0; x < input.getRegionWidth(); x++) 
        {
            for (int y = 0; y < input.getRegionHeight(); y++) 
            {
                int colorInt = pixmap.getPixel(input.getRegionX() + x, input.getRegionY() + y);
                Color c = new Color(colorInt);
                colorInt = Color.rgba8888(c);
                texCopy.drawPixel(x, y, colorInt);
            }
        }
        pixmap.dispose();
    
        float offsetVal = Math.round(thickness/2.0) * (float) -Math.cos((dir * Math.PI)/2);
        if(offsetVal > -1.23/Math.pow(10, 16))
        {
            offsetVal = 0;
        }
    
        // generate the pixel colors we'll use for the side view
        Pixmap sideProfile = new Pixmap(1, 64, Pixmap.Format.RGBA8888);
    
        for (int y = 0; y < texCopy.getHeight(); y++) 
        {
            for (int x = 0; x < texCopy.getWidth(); x++) 
            {
                int colorInt = texCopy.getPixel(x, y);
                if(new Color(colorInt).a != 0 && new Color(texCopy.getPixel(x + 1, y)).a == 0)
                {
                    Color c = new Color(colorInt);
                    c.mul(.8f); // darken the color
                    c.a = 1;
                    colorInt = Color.rgba8888(c);
                    sideProfile.drawPixel(0, y, colorInt);
                    continue;
                }
            }
        }
    
        // drawing the bottom layer
        p.drawPixmap(texCopy, 0, 0, 64, 64, (int) (Math.round(-offsetVal) + (64 - texCopy.getWidth()*Math.abs(dir))/2), 0, (int)(64*Math.abs(dir)), 64);
    
        // drawing the middle (connecting) layer
        // based on the edge pixels of the bottom layer, then translated to be in the middle
        for (int y = 0; y < p.getHeight(); y++) 
        {
            int colorInt = sideProfile.getPixel(0, y);
            for (int x = 0; x < p.getWidth(); x++) 
            {
                if(new Color(p.getPixel(x, y)).a != 0 && new Color(p.getPixel(x + 1, y)).a == 0)
                {
                    for(int i = 0; i <= 2 * Math.round(Math.abs(offsetVal)); i++) // the for the length between the top and bottom
                    {
                        p.drawPixel(x + i - 2 * (int)Math.round(Math.abs(offsetVal)), y, colorInt);
                    }
                }
            }
        }
    
        // drawing the top layer
        p.drawPixmap(texCopy, 0, 0, 64, 64, (int) (Math.round(offsetVal) + (64 - texCopy.getWidth()*Math.abs(dir))/2), 0, (int)(64*Math.abs(dir)), 64);
    
        // flip if facing left
        if(dir < 0)
        {
            p = flipPixmap(p);
        }
    
        return new Texture(p);
    }
    

    My flipPixmap method looks like this (stolen from stack overflow):

    private Pixmap flipPixmap(Pixmap src) 
    {
        final int width = src.getWidth();
        final int height = src.getHeight();
        Pixmap flipped = new Pixmap(width, height, src.getFormat());
    
        for (int x = 0; x < width; x++) 
        {
            for (int y = 0; y < height; y++) 
            {
                flipped.drawPixel(x, y, src.getPixel(width - x - 1, y));
            }
        }
        return flipped;
    }
    

    Here's the result :D https://i.sstatic.net/ejhKg.jpg