ejectamenta
ejectamenta

Reputation: 1087

libgdx drawing arc curve

The arc function of libgdx instead of drawing a arc draws a pie segment (ie. has 2 lines connecting to the arc's origin)

shapeRenderer.begin(ShapeType.Line);
shapeRenderer.arc(x, y, radius, 30, 120);
shapeRenderer.end();

Is there a solution to this problem so that libgdx can draw an arc curve similar to the html5 canvas arc function?

Upvotes: 3

Views: 4798

Answers (4)

KodyVanRy
KodyVanRy

Reputation: 1108

Here's a simple solution that uses kotlin extensions.

/** Draws an arc with 'stroke' of given width  */
fun ShapeRenderer.strokeArc(strokeWidth: Float, x: Float, y: Float, radius: Float, start: Float, degrees: Float, sampling: Float = 2f, color: Color = Color.WHITE) {
    val segments = ((6 * Math.cbrt(radius.toDouble()) * (Math.abs(degrees) / 360.0f)) * sampling).toInt()
    val colorBits = color.toFloatBits()

    for (i in 0 until segments) {
        val x1 = radius * MathUtils.cosDeg(start + (degrees / segments) * i)
        val y1 = radius * MathUtils.sinDeg(start + (degrees / segments) * i)

        val x2 = (radius - strokeWidth) * MathUtils.cosDeg(start + (degrees / segments) * i)
        val y2 = (radius - strokeWidth) * MathUtils.sinDeg(start + (degrees / segments) * i)

        val x3 = radius * MathUtils.cosDeg(start + (degrees / segments) * (i + 1))
        val y3 = radius * MathUtils.sinDeg(start + (degrees / segments) * (i + 1))

        val x4 = (radius - strokeWidth) * MathUtils.cosDeg(start + (degrees / segments) * (i + 1))
        val y4 = (radius - strokeWidth) * MathUtils.sinDeg(start + (degrees / segments) * (i + 1))

        renderer.color(colorBits)
        renderer.vertex(x + x1, y + y1, 0f)
        renderer.color(colorBits)
        renderer.vertex(x + x3, y + y3, 0f)
        renderer.color(colorBits)
        renderer.vertex(x + x2, y + y2, 0f)

        renderer.color(colorBits)
        renderer.vertex(x + x3, y + y3, 0f)
        renderer.color(colorBits)
        renderer.vertex(x + x2, y + y2, 0f)
        renderer.color(colorBits)
        renderer.vertex(x + x4, y + y4, 0f)
    }
}

To make better sense of this and why the built in solution does what it does. ShapeRenderer draws in triangles (just as OpenGL does) So you need 3 vertexes to draw a triangle. Here we are drawing one segment at a time. Each segment is 2 triangles to make a rectangle. Enough rectangles starts to look like a smooth circle.

The way the built in ShapeRenderer.arc draws is much like a pie. If you have enough slices with straight edges it starts to look like a full pie rather than a bunch of triangles to the center.

I hope this makes some sense and allows others to draw their own custom shapes and learn for the future!

Upvotes: 2

EntangledLoops
EntangledLoops

Reputation: 2119

I think you and other responders have posted the current best solution(s). An alternative "hack" for anyone who

  1. doesn't care so much about performance
  2. has a static background

would be to render over the 2 lines drawn with identically drawn background-colored lines to cover them up afterwards. Yes, it's lame but quick-and-dirty small-code solution.

Upvotes: 1

ejectamenta
ejectamenta

Reputation: 1087

In the end I sub classed the ShapeRenderer Class

public class Arc extends ShapeRenderer{

    private final ImmediateModeRenderer renderer;
    private final Color color = new Color(1, 1, 1, 1);

    public Arc(){
        renderer = super.getRenderer();
    }

    /** Draws an arc using {@link ShapeType#Line} or {@link ShapeType#Filled}. */
    public void arc (float x, float y, float radius, float start, float degrees) {
    int segments = (int)(6 * (float)Math.cbrt(radius) * (degrees / 360.0f));

    if (segments <= 0) throw new IllegalArgumentException("segments must be > 0.");
    float colorBits = color.toFloatBits();
    float theta = (2 * MathUtils.PI * (degrees / 360.0f)) / segments;
    float cos = MathUtils.cos(theta);
    float sin = MathUtils.sin(theta);
    float cx = radius * MathUtils.cos(start * MathUtils.degreesToRadians);
    float cy = radius * MathUtils.sin(start * MathUtils.degreesToRadians);

    for (int i = 0; i < segments; i++) {
        renderer.color(colorBits);
        renderer.vertex(x + cx, y + cy, 0);
        float temp = cx;
        cx = cos * cx - sin * cy;
        cy = sin * temp + cos * cy;
        renderer.color(colorBits);
        renderer.vertex(x + cx, y + cy, 0);
    }
  }
}

Then calling it like so

    Arc a = new Arc();
    a.setProjectionMatrix(cam.combined);
    a.begin(ShapeType.Line);
    a.arc(10, 10, 10, 30, 120);
    a.end();

The angle's are weird to get right, not sure if that's my code or libGDX

Upvotes: 3

Jongware
Jongware

Reputation: 22447

Reading the source code, this seems built-in behavior:

/** Draws an arc using {@link ShapeType#Line} or {@link ShapeType#Filled}. */
public void arc (float x, float y, float radius, float start, float degrees, int segments) {
    if (segments <= 0) throw new IllegalArgumentException("segments must be > 0.");
    float colorBits = color.toFloatBits();
    float theta = (2 * MathUtils.PI * (degrees / 360.0f)) / segments;
    float cos = MathUtils.cos(theta);
    float sin = MathUtils.sin(theta);
    float cx = radius * MathUtils.cos(start * MathUtils.degreesToRadians);
    float cy = radius * MathUtils.sin(start * MathUtils.degreesToRadians);

    if (shapeType == ShapeType.Line) {
        check(ShapeType.Line, ShapeType.Filled, segments * 2 + 2);

        renderer.color(colorBits);
        renderer.vertex(x, y, 0);           <--- CENTER
        renderer.color(colorBits);
        renderer.vertex(x + cx, y + cy, 0); <--- LINE TO START POINT
        for (int i = 0; i < segments; i++) {
            renderer.color(colorBits);
            renderer.vertex(x + cx, y + cy, 0);
            float temp = cx;
            cx = cos * cx - sin * cy;
            cy = sin * temp + cos * cy;
            renderer.color(colorBits);
            renderer.vertex(x + cx, y + cy, 0);
        }
        renderer.color(colorBits);
        renderer.vertex(x + cx, y + cy, 0); <-- LINE TO END POINT
...

Easiest is probably to copy the entire function and at least throw out two of the lines I marked: CENTER and LINE TO END POINT, along with the accompanying renderer.color(.. line above each.

(You can also delete the LINE TO START POINT – it appears to set the starting point for the curve, but that's actually also done inside the loop, so it's redundant.)

The function has a second part for a filled "arc" (I agree, it should have properly been named "pie" or "wedge"), but you don't need that here because it would do exactly the same.

If you get it to work, you could propose it to libgdx's maintainers, for example on libgdx's Contributions Forum.

Upvotes: 4

Related Questions