Search code examples
androidandroid-custom-view

Is there a convenient way to draw arc only using two points and curve radius in Android?


First of all

I searched for it for a long time, and I have already seen many questions including the two:

How to draw Arc between two points on the Canvas?

How to draw a curved line between 2 points on canvas?

Although they seem like the same question, I'm very sure they are not the same. In the first question, the center of the circle is known, and in the second, it draws a Bezier curve not an arc.

Description

Now we have two points A and B and the curve radius given, how to draw the arc as the image shows?

image

Since Path.arcTo's required arguments are RectF, startAngle and sweepAngle, there seems hardly a easy way.

My current solution

I now have my solution, I'll show that in the answer below.

Since the solution is so complex, I wonder if there is a easier way to solve it?


Solution

  • Probably there is no easier way. All what can do would be to refine your solution by geometrical approach. Since the center of circle is always on the perpendicular bisector of the chord, it's not required to solve so generalized equations.

    By the way, it's not clear how you defined Clockwise/Counter-clockwise. Arc's winding direction should be determined independently of node-placements (=A, B's coordinates).

    As is shown in the figure below, on the straight path from A to B, the center O is to be placed righthandside(CW) or lefthandside(CCW). That's all.

    Path.ArcTo with radius and end-points

    And, some more aspects to be altered:

    1. It's better to calculate startAngle by atan2(). Because acos() has singularity at some points.
    2. It's also possible to calculate sweepAngle with asin().

    After all the code can be slightly simplified as follows.

    @Throws(Exception::class)
    private fun Path.arcFromTo2(
        x1: Float, y1: Float, x2: Float, y2: Float, r: Float,
        clockwise: Boolean = true
    ) {
    
        val d = PointF((x2 - x1) * 0.5F, (y2 - y1) * 0.5F)
        val a = d.length()
        if (a > r) throw Exception()
    
        val side = if (clockwise) 1 else -1
    
        val oc = sqrt(r * r - a * a)
        val ox = (x1 + x2) * 0.5F - side * oc * d.y / a
        val oy = (y1 + y2) * 0.5F + side * oc * d.x / a
    
        val startAngle = atan2(y1 - oy, x1 - ox) * 180F / Math.PI.toFloat()
        val sweepAngle = side * 2.0F * asin(a / r) * 180F / Math.PI.toFloat()
    
        arcTo(
            ox - r, oy - r, ox + r, oy + r,
            startAngle, sweepAngle,
            false
        )
    }