4

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

Zizo
  • 109
  • 1
  • 7

1 Answers1

2

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);
}
DMGregory
  • 134,153
  • 22
  • 242
  • 357
  • 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 https://pastebin.com/YahvYcth. – Zizo Feb 04 '19 at 18:29
  • To be more specific I'm not too sure how to use the distance to get the precise control points – Zizo Feb 04 '19 at 18:49
  • 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. – DMGregory Feb 04 '19 at 20:29
  • 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. – Zizo Feb 05 '19 at 12:05
  • I found this that helped me position the control points https://gamedev.stackexchange.com/questions/164135/how-to-set-angle-between-points-of-a-bezier-curve – Zizo Feb 08 '19 at 21:35
  • I just saw your updated answer. I used the formula you provided for Ai and Ci with length 1.314684396 but the control point handles end up more than three times their original length and they don't reduce in length as they rotate. Please see my code at https://pastebin.com/0wARGimT, maybe I'm missing something. Also, what would happen to the control point handles of the stationary anchor point? – Zizo Feb 13 '19 at 15:26
  • Yes, you're using a formula and value for apples to do oranges. This is an answer to your question above, not to your question about rotating control points. You can use this as an ingredient to build a solution to your second question, but you need to apply the math, not just copy it. – DMGregory Feb 13 '19 at 15:39
  • I am a bit lost, especially with applying the math. I would really be grateful if you could explain further how I should be applying the math in the right way. – Zizo Feb 13 '19 at 16:00
  • Thanks for the other solution. Since the your other solution relies heavily on this one, I’ve been trying to get this sorted first before applying the other solution. I realised that I was using a constant length instead of recalculating the length for every angle based on the formula. I have done that here https://pastebin.com/FcEd5esW. But the control point handles seem to be behaving strangely still. Am I still applying this wrongly? – Zizo Feb 14 '19 at 15:36
  • It looks like you're taking the cube root instead of three times the square root. For a cube root, the 3 is a small superscript on the radical sign, not a full-height number to the left of it at the same baseline. – DMGregory Feb 15 '19 at 00:32