`drawArc` draws counter-clockwise arcs incorrectly

The problem

The API offers two ways to draw an arc:

playdate.graphics.drawArc(x, y, radius, startAngle, endAngle)

Neither of these methods respect the direction of the arc (and, notably, the latter doesn't support an optional direction argument). For example, this clockwise arc works as expected, and draws a 90 degree arc in the upper right quadrant:

gfx.drawArc(200, 120, 100, 0, 90)

However, this counter-clockwise arc does not. It renders a 270 degree arc in the other three quadrants, as though it were specified as starting at 90 and ending at 360:

gfx.drawArc(200, 120, 100, 90, 0)

The same erroneous results are achieved when creating a playdate.geometry.arc and passing it directly to the other version of the function.

A workaround

This can be worked around, of course, since the direction of the arc doesn't matter once it's drawn. (I surmise that's why the drawing API doesn't even offer the optional direction flag.)

    local arc = geom.arc.new(200, 120, 100, 90, 0)
    print(arc:isClockwise()) -- false
    print(arc:length()) -- 157 (25% of circumference)

    if arc:isClockwise() then
        -- The above arc will draw in this branch since it's CCW. Swapping
        -- the start and end angles causes it to draw correctly.
        gfx.drawArc(arc.x, arc.y, arc.radius, arc.endAngle, arc.startAngle)

Why this should be fixed

If this were purely a graphics API I'd understand the limitation as imposed; but these are geometric models, and their visual representation should accurately reflect their properties. I'm working on a game that is using arcs as part of a physics system, so I've already got them defined elsewhere, and need to draw their representation on screen.

The cruel irony is that the version of the function which accepts the properties as individual arguments is easy to fix. The version which takes an actual pd.geomertry.arc can't be fixed without unpacking its properties to pass to the other version, rendering it effectively useless unless you can guarantee you'll never see a CCW arc.

A final note/question

I commonly find myself needing to translate between world and local sprite coordinates so that I can do physics calculations in world space and draw the various objects within their individual sprites. This causes me more grief than it should, as trying to compute the appropriate offset given the location and (relative) center of the sprite is a chore, and often error prone. Is there a more sensible way to do this?

I bring this up in the context of this thread because it's even more irritating with arcs. Most of the other geometry primitives can be easily offset (points, lines, and rects have offsetBy, vector2Ds can just be subtracted) but arcs (and polygons) lack the conveneince. Adding playdate.geometry.arc:offset and playdate.geometry.arc:offsetBy would be nice.

Right now I'm working around all these limitations by holding onto two separate arc objects — one for calculation, and the other for drawing, with code that looks like this:

    -- we draw relative to our own center point, but collision calculations are in world space;
    -- also, arc drawing doesn't respect direction, so we need to swap start/end angles accordingly
    local cx, cy = self:getCenter()
    self.drawnArc = self.arc:copy()
    self.drawnArc.x -= self.x - self.width * cx
    self.drawnArc.y -= self.y - self.height * cy
    self.drawnArc.startAngle = math.min(self.arc.startAngle, self.arc.endAngle)
    self.drawnArc.endAngle   = math.max(self.arc.startAngle, self.arc.endAngle)
    self.drawEndpoint1 = self.drawnArc:pointOnArc(0)
    self.drawEndpoint2 = self.drawnArc:pointOnArc(self.drawnArc:length())

This could all be so much cleaner if I could just do something like this in draw:

    drawArc(self.arc:offsetBy(-self.x - self.width * cx, -self.y - self.height * cy))

Or even:


This came up once before:

drawArc(arc) ignores the direction of the arc

At last report, it looks like it's still an outstanding issue.

Ah, thanks for the link. I’m not sure why I didn’t find that thread in my search. I’ll drop a note there as a +1.

We've got a fix for this in the pipeline, sorry for the delay. It's taking a while to get everything sorted out on our end.

1 Like

No problem, thanks for the update!

1 Like