How to best approximate a geometrical arc with a B

2019-01-13 15:45发布

When drawing an Arc in 2D, using a Bezier Curve approximation, how does one calculate the two control points given that you have a center point of a circle, a start and end angle and a radius?

8条回答
放我归山
2楼-- · 2019-01-13 16:14

Raphael 2.1.0 has support for Arc->Cubic (path2curve-function), and after fixing a bug in S and T path normalization, it seems to work now. I updated *the Random Path Generator* so that it generates only arcs, so it's easy test all possible path combinations:

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

Test and if some path fails, I'd be happy to get report.

EDIT: Just realized that this is 3 years old thread...

查看更多
戒情不戒烟
3楼-- · 2019-01-13 16:18

There's Mathematica code at Wolfram MathWorld: Bézier Curve Approximation of an Arc, which should get you started.

See also:

查看更多
仙女界的扛把子
4楼-- · 2019-01-13 16:19

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

It accepts start angle, end angle, center and radius as input.

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

This solution is start and end angle order agnostic - it always picks the minor arc.

As a result you get all four points you need to define a cubic bezier curve in absolute coordinates.

I think this is best explained in 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.github.io/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);
}
查看更多
爱情/是我丢掉的垃圾
5楼-- · 2019-01-13 16:22

A nice explanation is provided in "Approximation of a "Cubic Bezier Curve by Circular Arcs"

Long story short: using Bezier curves you can achieve a minimum error of 1.96×10^-4, which is pretty ok 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 a so-called "magic number", which is an non-rational number. It can be approximated as follows:

K = 0.5522847498
查看更多
劳资没心,怎么记你
6楼-- · 2019-01-13 16:28

This isn't easily explained in a StackOverflow post, particularly since proving it to you will involve a number of detailed steps. However, what you're describing is a common question and there's a number of thorough explanations. See here and here; I like #2 very much and have used it before.

查看更多
迷人小祖宗
7楼-- · 2019-01-13 16:28

This is an 8-year-old question, but one that I recently struggled with, so I thought I'd share what I came up with. I spent a lot of time trying to use solution (9) from this text and couldn't get any sensible numbers out of it until I did some Googling and learned that, apparently, there were some typos in the equations. Per the corrections listed in this blog post, given the start and end points of the arc ([x1, y1] and [x4, y4], respectively) and the the center of the circle ([xc, yc]), one can derive the control points for a 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!

查看更多
登录 后发表回答