Reputation: 1087
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
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
Reputation: 2119
I think you and other responders have posted the current best solution(s). An alternative "hack" for anyone who
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
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
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