What is the best way to approximate the geometric arc of a Bezier curve?

When drawing an arc in 2D using the Bezier curve approximation, how do I calculate two control points, given that you have the center point of the circle, the start and end angle, and radius?

+19
math geometry graphics
Apr 09 '09 at 12:51
source share
8 answers

This is not easy to explain in a StackOverflow post, especially since proving this will include a number of detailed steps. However, what you describe is a common question, and there are a number of detailed explanations. See here and here ; I really love # 2 and have used it before.

+14
Apr 09 '09 at 13:07
source share
+4
Apr 09 '09 at 13:06
source share

Raphael 2.1.0 has support for Arc-> Cubic (path2curve-function), and after fixing the error in normalizing the S and T paths, it seems to work now. I updated the * random path generator * so that it only generates arcs, so it easily tests all possible combinations of paths:

http://jsbin.com/oqojan/53/

Test it, and if any path fails, I will be happy to receive a report.

EDIT: Just realized this is a 3 year old thread ...

+4
Oct 28
source share

A good explanation is given in "Approximation a" Bezier Cubic Curve with Circular Arcs "

In short: using Bezier curves, you can achieve a minimum error of 1.96 Γ— 10 ^ -4, which is pretty good for most applications.

For a positive quadrant arc, use the following points:

p0 = [0, radius] p1 = [radius * K, radius] p2 = [radius, radius * K] p3 = [radius, 0] 

where K is the so-called "magic number", which is an irrational number. It can be approximated as follows:

 K = 0.5522847498 
+3
Jun 15 '16 at 14:03
source share

This is an 8-year-old question, but I was struggling with recently, so I decided to share what I came across. I spent a lot of time trying to use solution (9) from this text and could not get any reasonable numbers from this until I made some from Google and found out that there were apparently some typos in the equations. Using the corrections listed in this blog post , given the start and end points of the arc ([x1, y1] and [x4, y4], and the center of the circle ([xc, yc]), you can get control points for the cubic bezier curve ([ x2, y2] and [x3, y3]) as follows:

 ax = x1 – xc ay = y1 – yc bx = x4 – xc by = y4 – yc q1 = ax * ax + ay * ay q2 = q1 + ax * bx + ay * by k2 = 4/3 * (√(2 * q1 * q2) – q2) / (ax * by – ay * bx) x2 = xc + ax – k2 * ay y2 = yc + ay + k2 * ax x3 = xc + bx + k2 * by y3 = yc + by – k2 * bx 

Hope this helps someone other than me!

+3
Jun 29 '17 at 15:29
source share

I have had success with this general solution for any elliptical arc in the form of a cubic Bezier curve. It even includes the start and end angles in the formulation, so there is no need for additional rotation (which would be a problem for a non-circular ellipse).

+2
Apr 08 2018-11-11T00:
source share

I answer this old question (which should belong to mathematics, so writing formulas would be awful) with some demos.

Suppose that P0 and P3 are your starting and ending points of your arc, P1 and P2 are the control points of the Bezier curve, and x is a measure of the angle divided by two. Suppose x is less than pi / 2.

Let PM be the midpoint of the segment P0P3 and PH the midpoint of the arc. To bring the arc closer, we want the Bezier curve to start at P0, pass through PH, end at P3 and touch the arc at P0 and P3.

(Click Run Code Snippet to display the drawing. Curses to imgur still does not support SVG.)

 <svg xmlns="http://www.w3.org/2000/svg" viewBox="10 20 80 80"> <style>text{font-size:40%;font-style:italic;text-anchor:middle}tspan{font-size:50%;font-style:normal}</style> <rect x="10" y="20" width="80" height="80" fill="none" stroke="gray"></rect> <path stroke="gray" stroke-dasharray="3,2" fill="none" d="M25,30 62.6,31.62 80,65 22.19,95.13 25,30 80,65 M22.19,95.13 62.6,31.62"></path> <path stroke="black" fill="none" d="M25,30A65.19 65.19 0 0 1 80,65"></path> <circle r="1" fill="red" cx="25" cy="30"></circle> <circle r="1" fill="green" cx="80" cy="65"></circle> <circle r="1" fill="magenta" cx="22.19" cy="95.13"></circle> <circle r="1" fill="darkgreen" cx="52.5" cy="47.5"></circle> <circle r="1" fill="yellow" cx="57.19" cy="40.13"></circle> <circle r="1" fill="maroon" cx="62.6" cy="31.62"></circle> <circle r="1" fill="orange" cx="48.27" cy="31"></circle> <circle r="1" fill="teal" cx="69.24" cy="44.35"></circle> <text x="25" y="28">P<tspan>0</tspan></text> <text x="48.27" y="29">P<tspan>1</tspan></text> <text x="71.24" y="42.35">P<tspan>2</tspan></text> <text x="83" y="63">P<tspan>3</tspan></text> <text x="62.6" y="29.62">P<tspan>E</tspan></text> <text x="59.19" y="47.13">P<tspan>H</tspan></text> <text x="54.5" y="54.5">P<tspan>M</tspan></text> </svg> 

Let PE be the intersection of lines tangent to an arc in P0 and P3. For the curve to touch the arc, P1 must lie on the segment P0PE, and P2 must lie on P3PE. Let k be the ratio of P0P1 / P0PE (also equal to P3P2 / P3PE):

P1 = (1 - k) P0 + k PE

P2 = (1 - k) P3 + k PE

We also have the following (we do some proportions):

PM = (P0 + P3) / 2

PH = PM / cos (x) = PM sec (x) = (P0 + P3) sec (x) / 2

PE = PH / cos (x) = PM sec (x) ^ 2 = (P0 + P3) sec (x) ^ 2/2

To simplify our calculations, I believed that all vector points are based on the center, but in the end, it does not matter.

The general 4-point Bezier curve is given by the formula

C (t) = t ^ 3 P3 + 3 (1 - t) t ^ 2 P2 + 3 (1 - t) ^ 2 t P1 + (1 - t) ^ 3 P0

We must have C (1/2) = PH, therefore

C (1/2) = (P0 + 3 P1 + 3 P2 + P3) / 8

= ((P0 + P3) + 3 (1 - k) P0 + 3 k PE + 3 (1 - k) P3 + 3 k PE) / 8

= ((P0 + P3) + 3 (1 - k) (P0 + P3) + 6 k PE) / 8

= (P0 + P3) (1 + 3 (1 - k) + 3 ks (x) ^ 2) / 8

So this is our equation (multiplied by 8) to find k:

8 C (1/2) = 8 PH

=> (P0 + P3) (4 - 3 k + 3 k sec (x) ^ 2) = 4 (P0 + P3) sec (x)

. Get rid of the vectors (P0 + P3) and get:

4 - 3 k + 3 k sec (x) ^ 2 = 4 s (x)

=> 3 k (sec (x) ^ 2 - 1) = 4 (sec (x) - 1)

=> k = 4/3 (sec (x) + 1)

Now you know where to place the control points. Hurrah!

If you have x = pi / 4, you get k = 0.552 ... You may have seen this value.

When working with elliptical arcs, all you have to do is scale the coordinates of the points accordingly.

If you have to deal with large angles, I suggest breaking them down into more curves. This is actually what some programs do when drawing arcs, since calculating a Bezier curve is sometimes faster than using sines and cosines.

+1
30 Oct '16 at 10:11
source share

I recently came across this problem. I compiled the solution from the articles mentioned here in the form of a module.

It takes the start angle, end angle, center and radius as input.

It approximates rather well small arcs (<= PI / 2). If you need to approximate something arcs from PI / 2 to 2 * PI, you can always break them into <PI / 2 parts, calculate the corresponding curves and join them later.

This solution is an agnostic of the initial and final angles - it always chooses a secondary arc.

As a result, you get all four points that you need to determine the cubic bezier curve in absolute coordinates.

I think this is best explained in the code and comments:

 'use strict'; module.exports = function (angleStart, angleEnd, center, radius) { // assuming angleStart and angleEnd are in degrees const angleStartRadians = angleStart * Math.PI / 180; const angleEndRadians = angleEnd * Math.PI / 180; // Finding the coordinates of the control points in a simplified case where the center of the circle is at [0,0] const relControlPoints = getRelativeControlPoints(angleStartRadians, angleEndRadians, radius); return { pointStart: getPointAtAngle(angleStartRadians, center, radius), pointEnd: getPointAtAngle(angleEndRadians, center, radius), // To get the absolute control point coordinates we just translate by the center coordinates controlPoint1: { x: center.x + relControlPoints[0].x, y: center.y + relControlPoints[0].y }, controlPoint2: { x: center.x + relControlPoints[1].x, y: center.y + relControlPoints[1].y } }; }; function getRelativeControlPoints(angleStart, angleEnd, radius) { // factor is the commonly reffered parameter K in the articles about arc to cubic bezier approximation const factor = getApproximationFactor(angleStart, angleEnd); // Distance from [0, 0] to each of the control points. Basically this is the hypotenuse of the triangle [0,0], a control point and the projection of the point on Ox const distToCtrPoint = Math.sqrt(radius * radius * (1 + factor * factor)); // Angle between the hypotenuse and Ox for control point 1. const angle1 = angleStart + Math.atan(factor); // Angle between the hypotenuse and Ox for control point 2. const angle2 = angleEnd - Math.atan(factor); return [ { x: Math.cos(angle1) * distToCtrPoint, y: Math.sin(angle1) * distToCtrPoint }, { x: Math.cos(angle2) * distToCtrPoint, y: Math.sin(angle2) * distToCtrPoint } ]; } function getPointAtAngle(angle, center, radius) { return { x: center.x + radius * Math.cos(angle), y: center.y + radius * Math.sin(angle) }; } // Calculating K as done in https://pomax.imtqy.com/bezierinfo/#circles_cubic function getApproximationFactor(angleStart, angleEnd) { let arc = angleEnd - angleStart; // Always choose the smaller arc if (Math.abs(arc) > Math.PI) { arc -= Math.PI * 2; arc %= Math.PI * 2; } return (4 / 3) * Math.tan(arc / 4); } 
0
Apr 11 '17 at 15:52
source share



All Articles