earlygrey / shapedrawer

A library for libGDX that draws simple shapes using a batch.
MIT License
187 stars 31 forks source link

Use repeateable textures as color. #36

Open thelsing opened 3 years ago

thelsing commented 3 years ago

I really like this lib. Would it be possible to use textures as colors?

My goal is to draw something like this:

Unbenannt

But not just in this shape but also all other shapes supported by shapedrawer.

earlygrey commented 3 years ago

This would likely be a bit of work, as shape drawer would additionally have to calculate the correct texture coordinates for each triangle (currently all the texture coordinates are fixed). You could probably have the user define the size in world coordinates of their desired repeating texture area, then shape drawer would divide each vertex coordinate by the size to get the texture coordinate, and repeating behaviour would be defined by the Texture's TextureWrap setting.

However for what you want I'd suggest checking out masking, using shape drawer to define the shape https://github.com/mattdesl/lwjgl-basics/wiki/LibGDX-Masking.

I'll leave this issue open in case anyone has any thoughts or ideas on potential implementation.

thelsing commented 3 years ago

Is it possible to get the polygon from path method? I could put this into a RepeatablePolygonSprite and paint the sprite.

earlygrey commented 3 years ago

It's not, but this is something I've considered adding, though I'm not sure exactly how it would work. The simplest version would be something where you draw whatever you want, then have some kind of access to the underlying float[] array, probably with some kind of convenience method like extractTriangles() or something. But keep in mind everything is stored as triangles, even lines are broken up into two triangles. So if you want to call polygon() and get the vertices of the actual polygon, that would require some changes to how it's currently done.

I had a quick look at RepeatablePolygonSprite and it looks like that expects the actual polygon vertices, which are then chopped up into triangles via a triangulator. However it calculates the repeating segments itself (I think I was incorrect above - letting the Texture wrap it wouldn't work with a Texture Region). Shape drawer already splits into triangles while drawing, and there doesn't seem to be a way to directly give RepeatablePolygonSprite the triangle vertices, so you'd in theory have to go back to polygon vertices somehow, then let RepeatablePolygonSprite chop it up again. Though it's overall not much code so it may be much less than I'd thought to just directly implement.

thelsing commented 3 years ago

I'll delay this part of my project then. Maybe you'll get around to implement this. When I'm done with the other parts of my project I'll try to get the triangles from shapedrawer and stuff them into the RepeateablePolygonSprite. I'll file a pull request then.

thelsing commented 1 year ago

I'm now at the stage where I need this to work somehow. Can you give me a pointer where this should implemented in this lib, please?

earlygrey commented 1 year ago

What exactly do you need? For example you need to be able to draw a path (with joining) and extract the vertices as a float[] or something?

thelsing commented 1 year ago

The best solution would be if I could just change the TextureRegion that shapedrawer uses.

Next best would be to the the vertices as float[]. This could be the return value of drawing operations or a separate method that fetches the vertices. Important is that nothing is actually drawn an I only the the calculated polygon.

earlygrey commented 1 year ago

I've added a method to the Drawing class that allows you to extract the XY coordinates of the drawing.

https://github.com/earlygrey/shapedrawer/compare/master...extract-vertices

You'd use it via something like

drawer.startRecording();
drawer.getBrush().polygon().vertices(vertices).draw();
Drawing drawing = drawer.stopRecording();
FloatArray coordinates = new FloatArray();
drawing.getTransformedXYCoordinates(coordinates);
float[] vertices = coordinates.toArray();

You should then just be able to make a RepeatablePolygonSprite.

I haven't tested this at all so let me know if it looks right, but it should be the minimum needed. If you've got any suggestions for something to add let me know!

thelsing commented 1 year ago

Thank you very much I will try it out at the weekend.

thelsing commented 1 year ago

What I'm doing now is:

        var pix = new Pixmap(image, 0, image.length);

        var region = new TextureRegion(new Texture(pix));
        region.flip(false, true);
        pix.dispose();

        drawer.startRecording();
        drawer.path(tmpFloat.toArray(), pen.getThickness(), JoinType.POINTY, false);
        var drawing = drawer.stopRecording();
        var coordinates = new FloatArray();
        drawing.getTransformedXYCoordinates(coordinates);

        var sprite = new RepeatablePolygonSprite();
        sprite.setPolygon(region, coordinates.toArray());
        // polygonSprite.setTextureRegion(textureRegion);
        sprite.draw((PolygonSpriteBatch) drawer.getBatch());

But I don't get any coordinates back.

thelsing commented 1 year ago

Maybe it's easier to try to understand the path code and use it to create the polygon from the path myself.

earlygrey commented 1 year ago

Sorry there was a typo in my code, it's fixed and should work now, I gave it a quick test.

thelsing commented 1 year ago

Thanks for the fix. I get vertices now, but the order is not correct for me: I use this test code:

@Override
    public void render() {
        Gdx.gl.glClearColor(0.15f, 0.15f, 0.2f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();

        float x = 100;
        float y = 100;
        float h = 500;
        float w = 600;
        float wall = 100;

        var path = new float[] {
                x+wall/2,y+wall/2,
                x+wall/2,y+h-wall/2,
                x+w-wall/2,y+h-wall/2,
                x+w-wall/2,y+wall/2,
        };

        float[] vertices = new float[] {
                x,y,
                x,y + h,
                x+w,y+h,
                x+w,y,
                x,y,
                x+wall,y+wall,
                x+w-wall,y+wall,
                x+w-wall,y+h-wall,
                x+wall,y+h-wall,
                x+wall,y+wall
        };
        sprite.setVertices(vertices);
        var region = new TextureRegion(image);
        region.flip(false, true);

        drawer.startRecording();
        drawer.path(path, wall, space.earlygrey.shapedrawer.JoinType.POINTY, false);
        var drawing = drawer.stopRecording();
        var coordinates = new FloatArray();
        drawing.getTransformedXYCoordinates(coordinates);

        //sprite.setTextureRegion(region);
        //sprite.draw(batch);
        //batch.draw(image, 140, 210);

        batch.end();
        shapeRenderer.begin();
        //sprite.drawDebug(shapeRenderer, Color.CORAL);
        shapeRenderer.setColor(Color.RED);
        for(var i = 1; i<path.length; i+=2)
            shapeRenderer.circle(path[i-1], path[i], 5);

        shapeRenderer.polyline(path);
        shapeRenderer.setColor(Color.BLUE);
        shapeRenderer.polyline(vertices);
//      shapeRenderer.setColor(Color.GREEN);
//      shapeRenderer.polyline(coordinates.toArray());
        shapeRenderer.end();

    }

I want:

image

But I get:

image

BTW I don't use the RepeateablePolygonSprite of libgdx but the one from here

earlygrey commented 1 year ago

So in this implementation I'm not sure the coordinates are that useful for what you want, because they're actually in the "sprite batch" arrangement - which means in groups of four points defining a quad.

You can see if you use this

        drawing.getTransformedXYCoordinates(coordinates);
        for (int i = 0; i < coordinates.size; i += 8) {
            drawer.getBrush().polygon()
                    .addVertex(coordinates.get(i), coordinates.get(i + 1))
                    .addVertex(coordinates.get(i+2), coordinates.get(i + 3))
                    .addVertex(coordinates.get(i+4), coordinates.get(i + 5))
                    .addVertex(coordinates.get(i+6), coordinates.get(i + 7))
                    .color(Color.PINK)
                    .draw();
        }

to draw each quad, the coordinates are all there, just not really in the expected arrangement.

I also just realised that you may actually want the enclosing polygon vertices, not the triangles/quads making it up (I also realised this in my earlier comments but apparently forgot). This is a bit more complex, as the drawn vertices don't in general form a polygon, only in eg this case of a continuous path.

I wonder if it would make sense to add some methods to this new brush/pen API I added (eg the code above), so you instead of calling draw() could do something like

drawer.getPen().polyLine().vertices(points).extractVertices(myFloatArray);

which maybe internally just uses a Drawing, but at least would know what to do with the vertices based on the shape. Or at that point maybe it just makes sense to fully implement it and allow the user to set a texture to repeat.

That's a fair bit more work, and I'm not sure the best way to do it. If you want for now you could try to calculate the polygon vertices from the coordinates returned by the Drawing, but the calculation would need to be specific for the exact thing you drew (eg path with pointy join).

thelsing commented 1 year ago

I think sorting the vertices in a proper oriented polygon is nearly impossible.

I managed to modify your path code() to create vertices in a way that I can use them in RepeateablePolygonSprite. I'll try to add rounded joints next. Maybe this is helpful when you add something like this to shapedrawer. In the meantime it would be helpful if you could made the Joiner class public.

thelsing commented 1 year ago

I have now code that creates the vertices needed for a path now see here. I can also create rounded corners.

I dropped the idea of using a RepeateablePolygonSprite and just use a PolygonSprite with a Texture set to repeat. RepeatablePolygonSprite only works in reliable way for convex polygons which most of my paths aren't.

Is this method suitable for shapedrawer? If so where should it go?