4
\$\begingroup\$

From the image below, I have represented a circle by a closed bezier curve made of two cubic bezier segments. I am aware that a bezier curve cannot represent a perfect circle. How can I calculate the distance from the anchor points to the control points based on the distance from both anchor points to give a close approximation of a circle?

Also how can I use the calculated distance to find the positions for the control points?

enter image description here

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Please don't forget to search for existing information on your problem area before posting new questions - especially when you're asking about something fundamental that's surely been answered before.

That said, I'd like to take a different approach than the answers I linked above.

These past answers seem to choose their control points so that the midpoint of the Bézier curve lies at the midpoint of the circular arc. That means that the half-curve on either side of the midpoint bulge out beyond the circle.

That's not bad, but we can do better, in terms of minimizing the maximum error. If we undershoot a little at the midpoint, we reduce the overshoot on either side, keeping our Bézier curve closer to the circle overall.

To do this, instead of kissing the arc at \$t = \frac 1 2\$, we'll cross it at \$t = \frac 1 3\$ and \$t = \frac 2 3\$. Our curve will weave back-and-forth across the circle we want to follow instead of staying strictly outside it.

I'll spare you the full derivation, but to do that, we want our control handles to have the following length:

$$l = \frac {3\sqrt{\sin^2\theta - 70 \cos \theta + 70} - 17 \sin \theta}{15 - 12 \cos \theta} \cdot r $$

Where \$\theta\$ is the angle of the circular arc (180° or \$\pi\$ radians if you use just two anchor points at opposite ends of the diameter), and \$r\$ is the radius of the circle (half the distance between the anchor points if they're at opposite ends of a diameter)

For the 180° case that translates to \$l = \frac {2 \sqrt {35}} 9 \cdot r \approx 1.314684396 \cdot r \$, with a maximum error of about 1.40%

(For comparison, the answers linked above use \$l = \frac 4 3 \cdot r \approx 1.333333 \cdot r\$ which suffers an error of about 1.83%)

If you care about fidelity, I'd strongly recommend using more than two segments to represent the circle. Even just 3 segments brings the error down to 0.12%, 4 segments brings it to 0.02%

If your Bézier anchor point is at \$A_i = (c_x + r \cdot \cos {\alpha}, c_y + r \cdot \sin \alpha)\$, with \$(c_x, c_y)\$ the center of the circle, and \$\alpha\$ the angle of the point counter-clockwise from the positive x-axis, then its corresponding anchor point is at \$C_i = A_i \pm l \cdot (-\sin \alpha, \cos \alpha)\$, with the sign of the \$\pm\$ being + for the start point, and - for the end.


Here's an animation of this method in action:

Animation showing Bézier curves wrapping around a circle

And here's the code I used:

// OnValidate is called by the editor automatically when the user modifies 
// the inspector parameters like center, radius, and circleCoverageDegrees.
// Here it's used to update our array of Bézier control points.
private void OnValidate() {
    BezierCircle(center, radius, circleCoverageDegrees, 0, bezierPoints, 0);
    BezierCircle(center, radius, 0, -circleCoverageDegrees, bezierPoints, 3);
}

// This method populates 4 cubic Bézier control points into an array,
// so that the resulting curve approximates a circular arc.
static void BezierCircle(
               Vector2 center, float radius, 
               float startAngleDegrees, float endAngleDegrees,
               Vector2[] points, int startIndex
) {
    // First, compute how long our tangents should be to best fit this arc.
    float angle = Mathf.Abs(endAngleDegrees - startAngleDegrees) * Mathf.Deg2Rad;
    float sine = Mathf.Sin(angle);
    float cosine = Mathf.Cos(angle);
    float tangentScale = radius * (3 * Mathf.Sqrt(sine * sine - 70f * cosine + 70) - 17 * sine) / (15 - 12 * cosine);
    tangentScale *= Mathf.Sign(endAngleDegrees - startAngleDegrees);

    // Place the start anchor point and its control point relative to it.
    angle = Mathf.Deg2Rad * startAngleDegrees;
    sine = Mathf.Sin(angle);
    cosine = Mathf.Cos(angle);

    points[startIndex] = center + radius * new Vector2(cosine, sine);
    points[startIndex + 1] = points[startIndex] + tangentScale * new Vector2(-sine, cosine);

    // Place the end anchor point and its control point relative to it.
    angle = Mathf.Deg2Rad * endAngleDegrees;
    sine = Mathf.Sin(angle);
    cosine = Mathf.Cos(angle);

    points[startIndex + 3] = center + radius * new Vector2(cosine, sine);
    points[startIndex + 2] = points[startIndex + 3] - tangentScale * new Vector2(-sine, cosine);
}
\$\endgroup\$
10
  • \$\begingroup\$ Sorry, I haven't been on the forum for a while so I didn't see your answer. I seem to be having issues with how to calculate the exact position of the control points given the anchor point. Please see what I've done in pastebin.com/YahvYcth. \$\endgroup\$
    – Zizo
    Feb 4, 2019 at 18:29
  • \$\begingroup\$ To be more specific I'm not too sure how to use the distance to get the precise control points \$\endgroup\$
    – Zizo
    Feb 4, 2019 at 18:49
  • \$\begingroup\$ If you want a control point position, not a distance, then it might be wise to edit your question to ask for a control point position, instead of asking for the distance as it does now. \$\endgroup\$
    – DMGregory
    Feb 4, 2019 at 20:29
  • \$\begingroup\$ Actually I need both the length and how to set the control points positions at the found length. Because of the close relation of both, I rather edited the question to include the control point positions. \$\endgroup\$
    – Zizo
    Feb 5, 2019 at 12:05
  • \$\begingroup\$ I found this that helped me position the control points gamedev.stackexchange.com/questions/164135/… \$\endgroup\$
    – Zizo
    Feb 8, 2019 at 21:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .