简体   繁体   中英

How to draw precise borders around filled polygons in libGDX?

I'm trying to draw a smaller filled polygon (which are always convex not self-intersecting quadrangles in my case) over another filled polygon to get an impression of a border around that filled polygon. To do that I've found out how to calculate the centroid of quadrangle - it will be a point around which the smaller filled polygon will be scaled down. I've copied the algorithm from here: https://math.stackexchange.com/a/2878092

And here's a full code of the class:

public class BodyPartActor extends Actor {
    private static TextureRegion textureRegion = Globals.getTextureRegion();
    private static Color borderColor = Assets.getSkin().getColor("pink");
    private static Color bodyPartColor = Assets.getSkin().getColor("green");
    private static short[] triangles = new short[] {
            0, 1, 2,         // Two triangles using vertex indices.
            0, 2, 3          // Take care of the counter-clockwise direction.
    };
    private PolygonRegion polygonRegion;
    private BodyPart bodyPart;
    private Vector2 centroid;

    public BodyPartActor(BodyPart bodyPart) {
        this.bodyPart = bodyPart;
        polygonRegion = new PolygonRegion(textureRegion, this.bodyPart.getVertices(), triangles);
        centroid = getCentroidOfQuadrangle();
    }

    private Vector2 getCentroidOfQuadrangle() {
        float[] v = bodyPart.getVertices();
        Vector2 centroidA = getCentroidOfTriangle(new float[]{v[0], v[1], v[2], v[3], v[4], v[5]});
        Vector2 centroidB = getCentroidOfTriangle(new float[]{v[2], v[3], v[4], v[5], v[6], v[7]});
        Vector2 centroidC = getCentroidOfTriangle(new float[]{v[4], v[5], v[6], v[7], v[0], v[1]});
        Vector2 centroidD = getCentroidOfTriangle(new float[]{v[6], v[7], v[0], v[1], v[2], v[3]});
        return getPointOfLinesIntersection(centroidA, centroidC, centroidB, centroidD);
    }

    private Vector2 getCentroidOfTriangle(float[] vertices) {
        return new Vector2 (
                (vertices[0] + vertices[2] + vertices[4]) / 3,
                (vertices[1] + vertices[3] + vertices[5]) / 3
        );
    }

    private Vector2 getPointOfLinesIntersection(Vector2 startLineA, Vector2 endLineA, Vector2 startLineB,
                                                Vector2 endLineB) {
        float detOfLineA = det2x2(startLineA.x, startLineA.y, endLineA.x, endLineA.y);
        float detOfLineB = det2x2(startLineB.x, startLineB.y, endLineB.x, endLineB.y);

        float xDeltaOfLineA = startLineA.x - endLineA.x;
        float xDeltaOfLineB = startLineB.x - endLineB.x;
        float yDeltaOfLineA = startLineA.y - endLineA.y;
        float yDeltaOfLineB = startLineB.y - endLineB.y;

        float xNom = det2x2(detOfLineA, xDeltaOfLineA, detOfLineB, xDeltaOfLineB);
        float yNom = det2x2(detOfLineA, yDeltaOfLineA, detOfLineB, yDeltaOfLineB);
        float deNom = det2x2(xDeltaOfLineA, yDeltaOfLineA, xDeltaOfLineB, yDeltaOfLineB);

        return new Vector2(xNom / deNom, yNom / deNom);
    }

    /**
     * Calculate determinant of a 2x2 matrix:
     * |a b|
     * |c d|
     */
    private float det2x2(float a, float b, float c, float d) {
        return (a * d) - (b * c);
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        float x = bodyPart.getBody().getPosition().x;
        float y = bodyPart.getBody().getPosition().y;
        float width = polygonRegion.getRegion().getRegionWidth();
        float height = polygonRegion.getRegion().getRegionHeight();
        float originX = centroid.x;
        float originY = centroid.y;
        float rotation = calculateRotation(originX, originY);

        PolygonSpriteBatch polygonSpriteBatch = (PolygonSpriteBatch) batch;
        polygonSpriteBatch.setColor(borderColor.r, borderColor.g, borderColor.b, borderColor.a * parentAlpha);
        polygonSpriteBatch.draw(polygonRegion, x, y, originX, originY, width, height, 1f, 1f, rotation);
        polygonSpriteBatch.setColor(bodyPartColor.r, bodyPartColor.g, bodyPartColor.b, bodyPartColor.a * parentAlpha);
        polygonSpriteBatch.draw(polygonRegion, x, y, originX, originY, width, height, 0.9f, 0.9f, rotation);
    }

    private float calculateRotation(float originX, float originY) {
    // How to calculate when originX and originY are the center of quadrangle???
        return bodyPart.getBody().getAngle() * MathUtils.radiansToDegrees; // Works only if originX and originY are 0.
    }
}

It seems to the calculate centroid correctly, I checked it here : http://eguruchela.com/math/Calculator/polygon-centroid-point but the resulting borders doesn't look okay . I even thought there's some floating-point arithmetic issues in libGDX when calculating vertices positions but I couldn't found any, so I don't know where's the problem. There's also an issue with rotation, because now I'm drawing with origin point at the centroid of quadrangle, so it doesn't render properly (before trying to draw a border, originX and originY were 0, which made correct rendering rotated bodies correct, now it doesn't align how it should).

Do you know how to render properly this border and with proper rotation of the polygon? Or maybe the whole approach to this problem is wrong and there's a better way to do that?

Ok, I have it working now. I used this algorithm: https://math.stackexchange.com/a/2824263 (Method 2). Here's how it looks now . It's not 100% stable though. Some very flat quadrangles are buggy, it's rare if border isn't thick, but if it is it can look like this and even worse when border thickness is setted higher. But for my thin borders it's good enough. Here's the code of the class now:

public class BodyPartActor extends Actor {
    private static TextureRegion textureRegion = Globals.getTextureRegion();
    private static Color borderColor = Assets.getSkin().getColor("bodyPartBorder");
    private static Color fillColor = Assets.getSkin().getColor("bodyPartFill");
    private static short[] triangles = new short[] {
            0, 1, 2,         // Two triangles using vertex indices.
            0, 2, 3          // Take care of the counter-clockwise direction.
    };
    private static float borderThickness = 0.1f;
    private PolygonRegion polygonRegionBorder;
    private PolygonRegion polygonRegionFill;
    private BodyPart bodyPart;

    public BodyPartActor(BodyPart bodyPart) {
        this.bodyPart = bodyPart;
        float[] vertices = this.bodyPart.getVertices();
        polygonRegionBorder = new PolygonRegion(textureRegion, vertices, triangles);
        polygonRegionFill = new PolygonRegion(textureRegion, calculateInnerVertices(vertices), triangles);
    }

    /**
     * @param v vertices of the outer quadrangle
     */
    private float[] calculateInnerVertices(float[] v) {
        Vector2 vA = calculateInnerVertex(v[6], v[7], v[0], v[1], v[2], v[3]);
        Vector2 vB = calculateInnerVertex(v[0], v[1], v[2], v[3], v[4], v[5]);
        Vector2 vC = calculateInnerVertex(v[2], v[3], v[4], v[5], v[6], v[7]);
        Vector2 vD = calculateInnerVertex(v[4], v[5], v[6], v[7], v[0], v[1]);
        return new float[]{vA.x, vA.y, vB.x, vB.y, vC.x, vC.y, vD.x, vD.y};
    }

    /**
     * Positive borderThickness value will produce vertex that is offseted to the inner side of the provided quadrangle.
     * Negative borderThickness value will produce vertex that is on the outer side of the quadrangle.
     */
    private Vector2 calculateInnerVertex(float prevX, float prevY, float curX, float curY, float nextX, float nextY) {
        Vector2 v = new Vector2(nextX - curX, nextY - curY);
        Vector2 w = new Vector2(prevX - curX, prevY - curY);
        Vector2 u = v.cpy().scl(w.len()).add(w.cpy().scl(v.len()));
        float absDetVW = Math.abs((v.x * w.y) - (v.y * w.x));
        return new Vector2(curX, curY).add(u.scl(borderThickness / absDetVW));
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        float x = bodyPart.getBody().getPosition().x;
        float y = bodyPart.getBody().getPosition().y;
        float rotation = bodyPart.getBody().getAngle() * MathUtils.radiansToDegrees;

        PolygonSpriteBatch polygonSpriteBatch = (PolygonSpriteBatch) batch;
        polygonSpriteBatch.setColor(borderColor.r, borderColor.g, borderColor.b, borderColor.a * parentAlpha);
        polygonSpriteBatch.draw(polygonRegionBorder, x, y, 0f, 0f, 1f, 1f, 1f, 1f, rotation);
        polygonSpriteBatch.setColor(fillColor.r, fillColor.g, fillColor.b, fillColor.a * parentAlpha);
        polygonSpriteBatch.draw(polygonRegionFill, x, y, 0f, 0f, 1f, 1f, 1f, 1f, rotation);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM