RU: Bezier Curve

Геометрические примитивы

Кривая Безье

Кривая Безье аппросксимирует (сглаживает) набор опорных точек. Чтобы дать наглядное представление об алгоритме постороения кривой, рассмотрим вначале подробно частные случаи двух, трех и четырех точек.

В случае двух точек $\mathbf{r}_0$, $\mathbf{r}_1$ построение кривой тривиально: можно только соединить их отрезком и вычислять промежуточное значение по формуле линейной интерполяции. Введем параметр $t$, изменяющийся в диапазоне от нуля до единицы, и запишем $\mathbf{b}^{(1)}(t) = (1-t) \mathbf{r}_0 + t \mathbf{r}_1$. Здесь $\mathbf{b}^{(1)}(t)$ — уравнение искомой кривой, кривой Безье первой степени. Коэффициенты ее обозначим через $B_0^{(1)}(t) = (1-t)$ и $B_1^{(1)}(t) = t$. Отметим выполнение условия $B_0^{(1)}(t) + B_1^{(1)}(t) = 1$.

Рассмотрим теперь случай трех опорных точек $\mathbf{r}_0$, $\mathbf{r}_1$, $\mathbf{r}_2$.

Как и выше, введем параметр $t$, изменяющийся в диапазоне от нуля до единицы, и по точкам $\mathbf{r}_0$, $\mathbf{r}_1$ построим точку $\mathbf{b}_0^{(1)}(t) = (1-t)\mathbf{r}_0+t\mathbf{r}_1$ (смотри рисунок). Аналогичную точку построим на отрезке $(\mathbf{r}_1, \mathbf{r}_2)$: $\mathbf{b}_1^{(1)}(t) = (1-t)\mathbf{r}_1+t\mathbf{r}_2$. Теперь проинтерполируем полученные точки: $\mathbf{b}_0^{(2)}(t) = (1-t)\mathbf{b}_0^{(1)} (t)+t\mathbf{b}_1^{(1)}(t)$. В результате мы получили параболу вида

(1)
\begin{align} \mathbf{b}^{(2)}(t) = (1-t)^2 \mathbf{r}_0 +2t(1-t) \mathbf{r}_1 + t^2 \mathbf{r}_2 \end{align}

кривую Безье второй степени. Введем следующие обозначения для коэффициентов этой параболы $B_0^{(2)}(t) = (1-t)^2$, $B_1^{(2)}(t) = 2t(1-t)$, $B_2^{(2)}(t) = t^2$.

Bezier01.JPG

Многочлены $B_i^{(2)}(t)$ возникают при разложении квадрата единицы $1^2 = ((1-t)+t)^2=B_0^{(2)}(t)+B_1^{(2)}(t)+B_2^{(2)}(t)$ и называются многочленами Бернштейна степени 2.

Найдем производную построенной кривой. Дифференцируя, получим

(2)
\begin{align} \frac{d\mathbf{b}^{(2)}}{dt}(t) = 2\left[(1-t)(\mathbf{r}_1-\mathbf{r}_0)+t(\mathbf{r}_2 - \mathbf{r}_1)\right] \end{align}

так что при $t=0$ производная направлена по вектору $\mathbf{r}_1-\mathbf{r}_0$, а при $t=1$ производная имеет направление вектора $\mathbf{r}_2-\mathbf{r}_1$.

Суммируем описанные выше свойства:

  • коэффициенты параболы (функции влияния опорных точек) при $0 \leq t \leq 1$ неотрицательны: $B_i^{(2)}(t)\geq 0$. Вследствие этого кривая $\mathbf{b}^{(2)}(t)$, целиком лежит внутри треугольника, образованного точками $\mathbf{r}_0$, $\mathbf{r}_1$, $\mathbf{r}_2$;
  • сумма коэффициентов равна единице: $B_0^{(2)}(t)+B_1^{(2)}(t)+B_2^{(2)}(t) = 1$;
  • кривая проходит через конечные точки $\mathbf{r}_0$ и $\mathbf{r}_2$: $\mathbf{b}^{(2)}(0)=\mathbf{r}_0$ и $\mathbf{b}^{(2)}(1)=\mathbf{r}_2$;
  • в конечных точках кривая касается опорной ломаной: ${\mathbf{b}'}^{(2)}(0)= 2(\mathbf{r}_1-\mathbf{r}_0)$ и ${\mathbf{b}'}^{(2)}(1)=2(\mathbf{r}_2-\mathbf{r}_1)$.

В случае задания четырех опорных точек $\mathbf{r}_0$, $\mathbf{r}_1$, $\mathbf{r}_2$, $\mathbf{r}_3$ поступим аналогично. Последовательно строим многочлены первой $\mathbf{b}_i^{(1)}(t)$, второй $\mathbf{b}_i^{(2)}(t)$ и третьей $\mathbf{b}_i^{(3)}(t)$ степени:

(3)
\begin{align} \mathbf{b}_0^{(1)}(t) &= (1-t)\mathbf{r}_0+t\mathbf{r}_1, & \mathbf{b}_0^{(2)}(t) &= (1-t)\mathbf{b}_0^{(1)}(t)+t\mathbf{b}_1^{(1)}(t), & \mathbf{b}_0^{(3)}(t) &= (1-t)\mathbf{b}_0^{(2)}(t)+t\mathbf{b}_1^{(2)}(t); \\ \mathbf{b}_1^{(1)}(t) &= (1-t)\mathbf{r}_1+t\mathbf{r}_2, & \mathbf{b}_1^{(2)}(t) &= (1-t)\mathbf{b}_1^{(1)}(t)+t\mathbf{b}_2^{(1)}(t); \\ \mathbf{b}_2^{(1)}(t) &= (1-t)\mathbf{r}_2+t\mathbf{r}_3. \end{align}

Кривая $\mathbf{b}_0^{(3)}(t)$ и есть искомый сглаживающий многочлен (кривая Безье) третьей степени: $\mathbf{b}^{(3)}(t) = \sum_{i=0}^3B_i^{(3)}(t)\mathbf{r}_i$ где

(4)
\begin{align} B_0^{(3)}(t)&=(1-t)B_0^{(2)}(t)=(1-t)^3, & B_1^{(3)}(t)&=(1-t)B_1^{(2)}(t)+tB_0^{(2)}(t)=3t(1-t)^2, \\ B_2^{(3)}(t)&=(1-t)B_2^{(2)}(t)+tB_1^{(2)}(t)=3t(1-t)^2, & B_3^{(3)}(t)&=tB_2^{(2)}(t)=t^3. \end{align}

— многочлены Бернштейна третьей степени. Можно показать, что при $t=0$ производная кривой Безье третьей степени направлена по вектору $\mathbf{r}_1-\mathbf{r}_0$, а при $t=1$ производная имеет направление вектора $\mathbf{r}_3-\mathbf{r}_2$.

Выписанные выше выражения естественно обобщаются на случай $n+1$ опорных точек. При этом выражение для кривой имеет вид: $\mathbf{b}^{(n)}(t) = \sum_{i=0}^n B_i^{(n)}(t)\mathbf{r}_i$, $0 \le t \le 1$, где коэффициенты, многочлены Бернштейна, неотрицательны и в сумме равны единице в каждой точке интервала. Кривая проходит через конечные точки $\mathbf{r}_0$ и $\mathbf{r}_n$: $\mathbf{b}^{(n)}(0)=\mathbf{r}_0$ и $\mathbf{b}^{(n)}(1)=\mathbf{r}_n$. В конечных точках кривая касается опорной ломаной.

Кроме описанной выше обычной кривой Безье иногда использют рациональную кривую, которая определяется следующим образом. Пусть заданы $n+1$ опорных точек $\mathbf{r}_0$, $\mathbf{r}_1, \ldots$, $\mathbf{r}_n$ и столько же положительных чисел $w_i$весовых коэффициентов. Рациональной кривой Безье $n$-ой степени называется кривая вида

(5)
\begin{align} {\bf b}^{(n)}(t)=\frac{\sum_{i=0}^{n}w_{i}B_i^{(n)}(t){\bf r}_i} {\sum_{i=0}^{n}w_{i}B_i^{(n)}(t)} \end{align}

Рациональная кривая есть отношение двух обычных кривых Безье: числитель строится по опорным точкам $w_i\mathbf{r}_i$, а знаменатель по $w_i$. В случае одинаковых весовых коэффициентов рациональная кривая Безье превращается в обычную. Весовые коэффициенты дают дополнительные степени свободы для изменения поведения кривой: увеличивая вес $w_i$ вы приближаете кривую к опорной точке $\mathbf{r}_i$.

Класс Geom_BezierCurve

Класс предназначен для описания как обычной, так и рациональной кривой Безье. Кривая Безье определяется набором опорных точек (poles, control points) и набором весов (weight) в случае рациональной кривой.

Мы начнем с примеров построения кривой, а описание процедур класса приведем ниже. Построим кривую и проверим ее параметры.

// Задаем опорные точки
    TColgp_Array1OfPnt points(1,4);
    points.SetValue(1, gp_Pnt( 0.5, 0.0, 0.0 ) );
    points.SetValue(2, gp_Pnt( 0.0, 1.0, 0.0 ) );
    points.SetValue(3, gp_Pnt( 1.0, 1.0, 0.0 ) );
    points.SetValue(4, gp_Pnt( 1.0, 0.0, 0.0 ) );
// Строим кривую
    Geom_BezierCurve bezier(points);
// Проверим, что значение параметра вдоль 
// кривой изменяется от 0 до 1
    Standard_Real t;
    t = bezier.FirstParameter();    // t = 0.0
    t = bezier.LastParameter();     // t = 1.0
// Проверим, что начальная и конечная точки 
// кривой совпадают с опорными
    gp_Pnt pnt;
    pnt = bezier.StartPoint();  //  ( 0.5, 0.0, 0.0 )
    pnt = bezier.EndPoint();    //  ( 1.0, 0.0, 0.0 )
// То же самое можно сделать, указав параметр
    bezier.D0(0.,pnt);   //  pnt = ( 0.5, 0.0, 0.0 )
    bezier.D0(1.,pnt);   //  pnt = ( 1.0, 0.0, 0.0 )
OCC_Bezier01.jpg
//
// Вычислим теперь касательные в начальной и конечной точках.
// Проверим, что они направлены по отрезкам опорной ломаной
    gp_Vec tang;
    bezier.D1(0.0,pnt,tang); // tang = (-1.5, 3.0, 0.0)
    bezier.D1(1.0,pnt,tang); // tang = ( 0.0,-3.0, 0.0)

Можно показать, что рациональная кривая Безье второй степени есть кривая второго порядка, лежащая в плоскости, проходящей через опорные точки. В частности, уравнение окружности можно предстасить в виде отношения многочленов

(6)
\begin{align} x(t) = R \frac{1-t^2}{1+t^2}, \qquad y(t) = R \frac{2t}{1+t^2} \end{align}

Построим с помощью кривой Безье четверть единичной окружности

    TColgp_Array1OfPnt points(1,3);
    TColStd_Array1OfReal weights(1,3);
// Участок окружности. Опорные точки и веса
    points.SetValue(1, gp_Pnt(1.,0.,0.));
    points.SetValue(2, gp_Pnt(1.,1.,0.));
    points.SetValue(3, gp_Pnt(0.,1.,0.));
    weights.SetValue(1,1.);
    weights.SetValue(2,1.);
    weights.SetValue(3,2.);
//
    Geom_BezierCurve  circle(points,weights);
// Проверим расстояние точки на кривой до начала координат
    gp_Pnt p;
    circle.D0(0.5,p);
    double dist = p.Distance(gp::Origin());     // dist = 1.

Увеличим степень построенной кривой — участка окружности. Обратим внимание на то, что что форма кривой при этом не изменяется (в дааном случае кривая остается окружностью), но опорные точки и веса пересчитываются.
// ..... продолжение
    circle.Increase(3);    // Кривая третьей степени
// Опросим новые опорные точки и веса. Их теперь четыре
    TColgp_Array1OfPnt newPoints(1,4);
    TColStd_Array1OfReal newWeights(1,4);
    int nb = circle.NbPoles();  // nb = 4
    circle.Poles(newPoints);    // (1.0, 0.0), (1.0, 2./3.), (0.5, 1.0) (0.0, 1.0)
    circle.Weights(newWeights); // 1.0, 1.0, 1 1/3, 2.0
    circle.D0(0.5,p);
    dist = p.Distance(gp::Origin());     // dist = 1.

Переходим к описанию процедур класса Geom_BezierCurve.

  • Конструкторы
// Создается обычная (не рациональная) кривая Безье на  основе массива опорных точек CurvePoles.
// Число опорных точек должно быть не меньше двух и не больше, чем MaxDegree + 1 (=26)
    Geom_BezierCurve(const TColgp_Array1OfPnt& CurvePoles)

// Создается  рациональная кривая Безье на  основе массива опорных точек CurvePoles и  массива весов PoleWeights.
// Если значения весов равны, строится обычная кривая.
// Длины массивов должны совпадать, вес не может быть нулевым,
// число опорных точек должно быть не меньше двух и не больше, чем MaxDegree + 1 (=26)
    Geom_BezierCurve( const TColgp_Array1OfPnt& CurvePoles,  const TColStd_Array1OfReal& PoleWeights )

// Создается новый объект -- копия данной кривой
    Handle_Geom_Geometry Copy()
  • Опрос значений точек на кривой и производных
// Значение точки, соответствующей параметру U. Значение параметра может лежать вне диапазона (0., 1.)
    void D0(const Standard_Real U,gp_Pnt& P)
// Значение точки и первой производной, соответствующих параметру U
    void D1(const Standard_Real U,gp_Pnt& P, gp_Vec& V1)
// Значение точки, первой и второй производных, соответствующих параметру U
    void D2(const Standard_Real U,gp_Pnt& P,gp_Vec& V1,gp_Vec& V2)
// Значение точки, первой, второй и третьей производных, соответствующих параметру U
    void D3(const Standard_Real U,gp_Pnt& P,gp_Vec& V1,gp_Vec& V2,gp_Vec& V3)
// Значение производной порядка  N, соответствующей параметру U
    gp_Vec DN(const Standard_Real U,const Standard_Integer N)
  • Опрос параметров кривой
// Возвращается значение максмальной степени (числа опорных точек) кривой Безье. Это 25
    Standard_Integer MaxDegree()
// Возвращается минимальное значение параметра кривой Безье. Это 0.0
    Standard_Real FirstParameter()
// Возвращается максимальное значение параметра кривой Безье. Это 1.0
    Standard_Real LastParameter()
// Возвращается точка кривой для минимального значения параметра (0.). Это первая опорная точка
    gp_Pnt StartPoint()
// Возвращается точка кривой для максимального значения параметра (1.). Это последня опорная точка
    gp_Pnt EndPoint()

// Возвращается число опорных точек
    Standard_Integer NbPoles()
// Возвращается степень кривой (она на единицу меньшн числа опорных точек)
    Standard_Integer Degree()
// Возвращается опорная точка номер Index. Обращаем внимание на то, что нумерация ведется с единицы.
    gp_Pnt Pole(const Standard_Integer Index)
// Возвращается массив значений опорных точек. Длина массива должна быть равна их числу, т.е. NbPoles
    void Poles(TColgp_Array1OfPnt& P)
// Взвращается значение веса номер Index (нумерация ведется с единицы).
    Standard_Real Weight(const Standard_Integer Index)
// Возвращается массив значений весов. Длина массива должна быть равна их числу, т.е. NbPoles
    void Weights(TColStd_Array1OfReal& W)

// Возвращается True, если первая и последняя опорные точки совпадают
    Standard_Boolean IsClosed()

// Возвращается false, если значения всех весов совпадают
    Standard_Boolean IsRational()
  • Задание/изменение параметров кривой
// Задание нового значения опорной точки номер index. Если кривая рациональная, вес точки не меняется.
    void SetPole(const Standard_Integer Index,const gp_Pnt& P)
// Задание нового значения веса номер index. 
    void SetWeight(const Standard_Integer Index, const Standard_Real Weight)
// Задание новых значений опорной точки и веса номераIndex.
    void SetPole(const Standard_Integer Index, const gp_Pnt& P,const Standard_Real Weight)

// Увеличение степени кривой Безье. При этом вид кривой не изменяется, а опорные точки и веса пересчитываются.
    void Increase(const Standard_Integer Degree)

// Вставка новой опорной точки после точки с номером Index.
// Если кривая рациональная, вес точки полагается равным единице.
// В результате вставки степень кривой увеличивается на единицу.
   void InsertPoleAfter(const Standard_Integer Index,const gp_Pnt& P)
// Вставка новой опорной точки перед точкой с номером Index.
    void InsertPoleBefore(const Standard_Integer Index,const gp_Pnt& P)

// Вставка новой опорной точки P с весом Weight после точки с номером Index.
    void InsertPoleAfter( const Standard_Integer Index, const gp_Pnt& P, const Standard_Real Weight) ;
// Вставка новой опорной точки P с весом Weight перед точкой с номером Index.
    void InsertPoleBefore( const Standard_Integer Index, const gp_Pnt& P, const Standard_Real Weight) ;

// Уничтожение опорной точки с номером Index. В результате степень кривой уменьшается на единицу.
    void RemovePole(const Standard_Integer Index)

// Изменение параметризации кривой на обратную: Value (NewU) =  Value (1 - OldU)
    void Reverse()
// Возвращение значения параметра при обратной параметризации: возвращается  1-U
    Standard_Real ReversedParameter(const Standard_Real U)

// Применение преобразования T к кривой
    void Transform(const gp_Trsf& T)