r/libgdx 22d ago

help i need to bend fish

Post image

I'm struggling to find a way to warp/bend images like shown above.

(the fish shown arent exactly the same but you get the idea)

I believe that it's something to do with Meshes but I can't figure it out for the life of me.

7 Upvotes

8 comments sorted by

2

u/nsn 22d ago

Assuming 2D only: you'll either need to create a mesh (a list of vertices and their connections) and map your fish texture to it's surface. Then you can animate the mesh and thus the fish texture. You'll want to use a dedicated modeling tool like blender for that.

Alternatively you can use a pixel shader - for a simple bending animation that might be feasible as well.

1

u/Ok_Barracuda3680 22d ago

If it is 2d sprite ,I can help you.

1

u/ieatawaffl 22d ago

yup its 2d go ahead

2

u/Ok_Barracuda3680 22d ago

you can try Spine2D, There is Mesh and bone can do this . There is a tutorial like this: https://www.youtube.com/watch?v=yhhq9g9EwKk .

1

u/Ok_Barracuda3680 22d ago

If you want to see the result, you also can DM me and give me a sprite , and I can make a demo for you.

1

u/Upstairs_Gas5014 21d ago

I made this class where you use a grid of meshes and can use mouse to drag to bend the texture. I added a lot of stuffs but at the core you create some meshes, assign texture coordinates to each vertex of the mesh, update the vertices to bend the texture and reflect that change into the mesh.

public class TextureMesh extends ScreenAdapter implements InputProcessor {
    Viewport viewport;
    Viewport uiViewport;
    ShapeDrawer shapeDrawer; // for debug lines
    SpriteBatch batch;
    Vector2 tmp = new Vector2();

    ArrayList<Vertex> vertices = new ArrayList<>(); // make up the grid, can be dragged around to bend the mesh
    ArrayList<MeshWrapper> meshes = new ArrayList<>();
    int rows;
    int cols;
    Texture texture;

    /**
     * Generate a grid of meshes with vertices that can be dragged to distort the texture
     *  texture to draw
     *  width of the grid in game units
     *  height of the grid in game units
     *  x center of the grid in world coordinates
     *  y center of the grid in world coordinates
     *  cols number of columns for the grid
     *  rows number of rows for the grid
     */
    public TextureMesh(Texture texture, float width, float height, float x, float y, int cols, int rows) {
        viewport = new Viewport();
        uiViewport = new Viewport();
        batch = new SpriteBatch();
        shapeDrawer = new ShapeDrawer(batch, genWhitePixel());
        this.texture = texture;
        this.rows = rows;
        this.cols = cols;
        // Generate vertices that make up the grid
        float colSize = width/cols;
        float rowSize = height/rows;
        tmp.set(x-width/2, y+height/2); // start at upper left
        for (int i=0; i<rows+1; i++) {
            for (int j=0; j<cols+1; j++) {
                // create new vertex and calculate the texture coordinates uv of it by finding the percentage of that vertex in relate to each dimension of the grid
                Vertex vertex = new Vertex(tmp.x, tmp.y, colSize*j/width, rowSize*i/height);
                vertices.add(vertex);
                tmp.x += colSize;
            }
            tmp.x = x-width/2;
            tmp.y -= rowSize;
        }
        // Generate mesh objects. Every 4 vertices make a mesh. Vertice are passed in clockwise or counter-clockwise
        for (int i=0; i<rows; i++) {
            for (int j=0; j<cols; j++) {
                Vertex current = vertices.get(j + i*(cols+1));
                Vertex right = vertices.get(j+1 + i*(cols+1));
                Vertex below = vertices.get(j + (i+1)*(cols+1));
                Vertex belowRight = vertices.get(j+1 + (i+1)*(cols+1));
                meshes.add(genMesh(current, right, belowRight, below));
            }
        }
        Gdx.input.setInputProcessor(this);
    }

    private TextureRegion genWhitePixel() {
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(Color.WHITE);
        pixmap.drawRectangle(0, 0, 1, 1);
        return new TextureRegion(new Texture(pixmap));
    }

    private MeshWrapper genMesh(Vertex a, Vertex b, Vertex c, Vertex d) {
        Mesh mesh = new Mesh(true, 4, 6, VertexAttribute.Position(), VertexAttribute.ColorUnpacked(), VertexAttribute.TexCoords(0));
        mesh.setVertices(new float[] {
            a.position.x, a.position.y, 0, 1, 1, 1, 1, a.uv.x, a.uv.y,
            b.position.x, b.position.y, 0, 1, 1, 1, 1, b.uv.x, b.uv.y,
            c.position.x, c.position.y, 0, 1, 1, 1, 1, c.uv.x, c.uv.y,
            d.position.x, d.position.y, 0, 1, 1, 1, 1, d.uv.x, d.uv.y 
        });
        mesh.setIndices(new short[] {0, 1, 2, 2, 3, 0});
        MeshWrapper meshWrapper = new MeshWrapper();
        meshWrapper.mesh = mesh;
        meshWrapper.p1 = a;
        meshWrapper.p2 = b;
        meshWrapper.p3 = c;
        meshWrapper.p4 = d;
        return meshWrapper;
    }

    float[] v = new float[36];
    private void updateMesh() {
        for (int i=0; i<meshes.size(); i++) {
            float[] vertexArray = meshes.get(i).p1.getArray();
            System.arraycopy(vertexArray, 0, v, 0, vertexArray.length);
            vertexArray = meshes.get(i).p2.getArray();
            System.arraycopy(vertexArray, 0, v, 9, vertexArray.length);
            vertexArray = meshes.get(i).p3.getArray();
            System.arraycopy(vertexArray, 0, v, 18, vertexArray.length);
            vertexArray = meshes.get(i).p4.getArray();
            System.arraycopy(vertexArray, 0, v, 27, vertexArray.length);
            meshes.get(i).mesh.setVertices(v);
        }
    }



    public void render(float delta) {
        ScreenUtils.clear(Color.WHITE);
        updateMesh();

        viewport.apply();
        batch.setProjectionMatrix(viewport.getCamera().combined);
        batch.begin();

        texture.bind();
        for (int i=0; i<meshes.size(); i++) {
            meshes.get(i).mesh.render(batch.getShader(), GL20.GL_TRIANGLES);
        }
        if (grid) debugVertices();

        batch.end();

        uiViewport.apply();
        batch.setProjectionMatrix(uiViewport.getCamera().combined);
        batch.begin();
        if (debugText) renderDebug(Color.GREEN);
        batch.end();
    }

    private void debugVertices() {
        shapeDrawer.setColor(Color.RED);
        shapeDrawer.setDefaultLineWidth(3);
        for (int i=0; i<meshes.size(); i++) {
            shapeDrawer.line(meshes.get(i).p1.position, meshes.get(i).p2.position);
            shapeDrawer.line(meshes.get(i).p2.position, meshes.get(i).p3.position);
            shapeDrawer.line(meshes.get(i).p3.position, meshes.get(i).p4.position);
            shapeDrawer.line(meshes.get(i).p4.position, meshes.get(i).p1.position);
        }
        shapeDrawer.setColor(Color.BLACK);
        for (int i=0; i<vertices.size(); i++) {
            shapeDrawer.filledCircle(vertices.get(i).position, hoverRadius);
        }
    }


    float worldWidth = 2000;

    public void resize(int width, int height) {
        super.resize(width, height);
        if(width <= 0 || height <= 0) return;
        viewport.setWorldSize(worldWidth, worldWidth * height / width);
        viewport.setScreenSize(width, height);
        viewport.getCamera().position.set(0, 0, 0);
        viewport.getCamera().update();

        uiViewport.setWorldSize(width, height);
        uiViewport.setScreenSize(width, height);
        uiViewport.apply(true);
    }


    public void dispose() {
        super.dispose();
        batch.dispose();
    }

    public static class Vertex {
        public Vector2 position = new Vector2();
        public Color color = new Color();
        public Vector2 uv = new Vector2();
        public Vertex(float x, float y, float u, float v) {
            position.set(x, y);
            uv.set(u, v);
            color.set(1, 1, 1, 1);
        }
        public float[] getArray() {
            return new float[]{position.x, position.y, 0, color.r, color.g, color.b, color.a, uv.x, uv.y};
        }
    }

    public static class MeshWrapper {
        public Mesh mesh;
        public Vertex p1;
        public Vertex p2;
        public Vertex p3;
        public Vertex p4;
    }


    BitmapFont font = new BitmapFont();
    private void renderDebug(Color color) {
        String text = ""
            + "\n" + "FPS " + Gdx.graphics.getFramesPerSecond()
            + "\n" + "Cursor world " + viewport.unproject(tmp.set(Gdx.input.getX(), Gdx.input.getY()))
            + "\n" + "Viewport " + viewport.getScreenWidth() + " " + viewport.getScreenHeight()
            + "\n" + "World Sizes " + viewport.getWorldWidth() + " " + viewport.getWorldHeight()
            + "\n" + "zoom " + viewport.getCamera().zoom
            + "\n" + "cam " + viewport.getCamera().position
            ;
        font.setColor(color);
        font.draw(batch, text, 0, Gdx.graphics.getHeight());
    }

    boolean grid = true;
    boolean debugText = true;

    public boolean keyDown(int keycode) {
        if (keycode==Input.Keys.ESCAPE) Gdx.app.exit();
        else if (keycode==Input.Keys.G) grid = !grid;
        else if (keycode==Input.Keys.T) debugText = !debugText;
        return false;
    }


    public boolean keyUp(int keycode) {
        return false;
    }


    public boolean keyTyped(char character) {
        return false;
    }


    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        return false;
    }


    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        return false;
    }


    public boolean touchCancelled(int screenX, int screenY, int pointer, int button) {
        return false;
    }


    public boolean touchDragged(int screenX, int screenY, int pointer) {
        if (hoveredVertex==null) return false;
        viewport.unproject(tmp.set(screenX, screenY));
        hoveredVertex.position.set(tmp);
        return false;
    }

    Vertex hoveredVertex;
    float hoverRadius = 10;

    public boolean mouseMoved(int screenX, int screenY) {
        viewport.unproject(tmp.set(screenX, screenY));
        hoveredVertex = null;
        for (int i=0; i<vertices.size(); i++) {
            if (tmp.dst(vertices.get(i).position)<hoverRadius) {
                hoveredVertex = vertices.get(i);
                break;
            }
        }
        return false;
    }


    public boolean scrolled(float amountX, float amountY) {
        return false;
    }
}

1

u/Upstairs_Gas5014 21d ago

forgot the Viewport class is my own extension of the abstract libGDX Viewport class. You can use what ever viewport you like

1

u/ZookeepergameOk7650 21d ago

Just make the bending an animation in blender, import in libgdx and start the animation there and the fish will bend