Как лучше всего приблизить геометрическую дугу с кривой Безье? - PullRequest
26 голосов
/ 09 апреля 2009

При рисовании дуги в 2D с использованием приближения кривой Безье, как рассчитать две контрольные точки, если у вас есть центральная точка окружности, начальный и конечный угол и радиус?

Ответы [ 8 ]

17 голосов
/ 09 апреля 2009

Это непросто объяснить в посте StackOverflow, особенно потому, что для его доказательства потребуется ряд подробных шагов. Однако то, что вы описываете, является распространенным вопросом, и есть ряд подробных объяснений. См. здесь и здесь ; Мне очень нравится # 2, и я использовал его раньше.

13 голосов
/ 29 июня 2017

Это 8-летний вопрос, но с которым я недавно боролся, поэтому я решил поделиться тем, что придумал. Я потратил много времени, пытаясь использовать решение (9) из этого текста , и не смог извлечь из него никаких разумных чисел, пока не попробовал Google и не узнал, что, по-видимому, в опечатках были некоторые опечатки. уравнения. Согласно исправлениям, перечисленным в этого сообщения в блоге , с учетом начальной и конечной точек дуги ([x1, y1] и [x4, y4], соответственно) и центра круга ([xc, yc]), можно получить контрольные точки для кубической кривой Безье ([x2, y2] и [x3, y3]) следующим образом:

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

Надеюсь, это поможет кому-то, кроме меня!

5 голосов
/ 09 апреля 2009
4 голосов
/ 15 июня 2016

Хорошее объяснение приводится в «Приближение» Кубической кривой Безье круговыми дугами"

Короче говоря: используя кривые Безье, вы можете достичь минимальной ошибки 1,96 × 10 ^ -4, что вполне нормально для большинства приложений.

Для положительной дуги квадранта используйте следующие точки:

p0 = [0, radius]

p1 = [radius * K, radius]  

p2 = [radius, radius * K]

p3 = [radius, 0]

где K - это так называемое «магическое число», которое является нерациональным числом. Его можно аппроксимировать следующим образом:

K = 0.5522847498
4 голосов
/ 28 октября 2012

Raphael 2.1.0 имеет поддержку Arc-> Cubic (path2curve-function), и после исправления ошибки в нормализации S и T пути, похоже, теперь работает. Я обновил * Генератор случайных путей *, чтобы он генерировал только дуги, чтобы можно было легко проверить все возможные комбинации путей:

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

Проверьте, и если какой-то путь не пройден, я был бы рад получить отчет.

РЕДАКТИРОВАТЬ: Просто понял, что это 3-летняя тема ...

3 голосов
/ 30 октября 2016

Я отвечаю на этот старый вопрос (который должен принадлежать математике, поэтому написание формул будет ужасно) с некоторыми демонстрациями.

Предположим, P0 и P3 - ваша начальная и конечная точка вашей дуги, P1 и P2 контрольные точки Безье. кривая, а x - это мера угла, деленная на два. Предположим, что x меньше pi / 2.

Пусть PM средняя точка сегмента P0P3 и PH средняя точка дуги. Чтобы приблизить дугу, мы хотим, чтобы кривая Безье начиналась с P0 , проходила через PH , заканчивалась P3 и касалась к дуге в P0 и P3 .

(Нажмите на «Выполнить фрагмент кода», чтобы отобразить рисунок. Проклятия, которые будут игнорировать, пока не поддерживают 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>

Пусть PE пересечение линий, касающихся дуги в P0 и P3 . Чтобы кривая касалась дуги, P1 должен лежать на отрезке P0PE , а P2 должен лежать на P3PE, Пусть k будет отношением P0P1 / P0PE (также равным P3P2 / P3PE ):

P1 = (1 - k ) P0 + k PE

P2 = (1 - k ) P3 + k PE

У нас также есть следующее (сделать некоторые пропорции):

PM = ( P0 + P3 ) / 2

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

PE = PH / cos ( x ) = PM сек ( x ) ^ 2 = ( P0 + P3 ) сек ( x ) ^ 2/2

Чтобы упростить наши вычисления, я считал, что все векторные точки основаны на центре, но в итоге это не имеет значения.

Общая 4-точечная кривая Безье задается формулой

C ( t ) = t ^ 3 P3 + 3 (1 - t ) т ^ 2 П2 + 3 (1 - т ) ^ 2 т П1 + (1 - t ) ^ 3 P0

У нас должно быть C (1/2) = PH , поэтому

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 k сек ( х ) ^ 2) / 8

Итак, это наше уравнение (умноженное на 8), чтобы найти k :

8 C (1/2) = 8 PH

=> ( P0 + P3 ) (4 - 3 k + 3 k сек ( x * 1260) *) ^ 2) = 4 ( P0 + P3 ) сек ( x )

Давайте избавимся от векторов ( P0 + P3 ), и мы получим:

4 - 3 k + 3 k сек ( x ) ^ 2 = 4 сек ( x )

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

=> k = 4/3 (сек ( x ) + 1)

Теперь вы знаете, где разместить контрольные точки. Ура!

Если у вас есть x = pi / 4, вы получите k = 0,552 ... Возможно, вы видели это значение около.

При работе с эллиптическими дугами достаточно лишь соответствующим образом масштабировать координаты точек.

Если вам приходится иметь дело с большими углами, я предлагаю разделить их на несколько кривых. Именно это и делают некоторые программы при рисовании дуг, поскольку вычисление кривой Безье иногда быстрее, чем использование синусов и косинусов.

2 голосов
/ 08 апреля 2011

У меня был успех с этим общим решением для любой эллиптической дуги в виде кубической кривой Безье. Он даже включает начальный и конечный углы в рецептуру, поэтому нет необходимости в дополнительном вращении (что может быть проблемой для некруглого эллипса).

1 голос
/ 11 апреля 2017

Я недавно наткнулся на эту проблему. Я составил решение из статей, упомянутых здесь, в виде модуля.

В качестве входных данных принимаются начальный угол, конечный угол, центр и радиус.

Он очень хорошо аппроксимирует маленькие дуги (

Это решение не зависит от начального и конечного угловых порядков - оно всегда выбирает малую дугу.

В результате вы получаете все четыре точки, необходимые для определения кубической кривой Безье в абсолютных координатах.

Я думаю, это лучше всего объяснить в коде и комментариях:

'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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...