如何在C++中实现Bézier曲线?

25

我想实现一个贝塞尔曲线。我以前在C#中做过这个,但是我完全不熟悉C++库。我该怎么创建二次曲线?

void printQuadCurve(float delta, Vector2f p0, Vector2f p1, Vector2f p2);

显然,我们需要使用线性插值,但标准数学库中是否存在这种功能?如果不存在,我应该去哪里找到它?

我正在使用Linux。

8个回答

127

最近我遇到了同样的问题,想自己实现它。这张来自维基百科的图片对我很有帮助:

http://upload.wikimedia.org/wikipedia/commons/3/35/Bezier_quadratic_anim.gif

以下代码是用C++编写的,演示如何计算二次贝塞尔曲线。

int getPt( int n1 , int n2 , float perc )
{
    int diff = n2 - n1;

    return n1 + ( diff * perc );
}    

for( float i = 0 ; i < 1 ; i += 0.01 )
{
    // The Green Line
    xa = getPt( x1 , x2 , i );
    ya = getPt( y1 , y2 , i );
    xb = getPt( x2 , x3 , i );
    yb = getPt( y2 , y3 , i );

    // The Black Dot
    x = getPt( xa , xb , i );
    y = getPt( ya , yb , i );

    drawPixel( x , y , COLOR_RED );
}

在图像中,(x1|y1)、(x2|y2)和(x3|y3)分别表示P0、P1和P2,仅用于展示基本思想...

对于那些要求三次贝塞尔曲线的人,它与二次贝塞尔曲线类似(也来自维基百科):

http://upload.wikimedia.org/wikipedia/commons/a/a3/Bezier_cubic_anim.gif

这个答案提供了相关代码。


2
回复很好,但这实际上是DeCasteljau表示法。虽然更易于理解,但通常不太优化。 - Ender Doe
2
谢谢!你说的完全正确。当时我需要的是对贝塞尔曲线的生动理解(人们有类似欲望的原因可能是这个回答被投票了很多次)。大多数其他算法都是DeCasteljau的(通常必要)优化,所以它似乎是一个非常好的基础,知识可以很容易地建立在其上。 - Jakob Riedle

16
这是一个通用的曲线实现,适用于任意数量的点。
vec2 getBezierPoint( vec2* points, int numPoints, float t ) {
    vec2* tmp = new vec2[numPoints];
    memcpy(tmp, points, numPoints * sizeof(vec2));
    int i = numPoints - 1;
    while (i > 0) {
        for (int k = 0; k < i; k++)
            tmp[k] = tmp[k] + t * ( tmp[k+1] - tmp[k] );
        i--;
    }
    vec2 answer = tmp[0];
    delete[] tmp;
    return answer;
}

请注意,它使用堆内存来存储临时数组,这并不是非常高效的。如果您只需要处理固定数量的点,则可以硬编码numPoints值并改用栈内存。
当然,上述假设您有一个vec2结构和像这样的运算符:
struct vec2 {
    float x, y;
    vec2(float x, float y) : x(x), y(y) {}
};

vec2 operator + (vec2 a, vec2 b) {
    return vec2(a.x + b.x, a.y + b.y);
}

vec2 operator - (vec2 a, vec2 b) {
    return vec2(a.x - b.x, a.y - b.y);
}

vec2 operator * (float s, vec2 a) {
    return vec2(s * a.x, s * a.y);
}

1
@ifroce2d 这是哪种类型的曲线? - mmostajab
4
根据问题和函数名称“getBezierPoint”,这是贝塞尔曲线。 - iforce2d
@ifroce2d 基本思路对于 Vector3 应该是相同的吧? - Coldsteel48

13
你可以选择使用de Casteljau方法,这个方法是通过递归分割控制路径,最终使用线性插值到达点的方法,就像上面解释的那样。另外一种方法是Bezier方法,它是通过混合控制点来实现的。
 p = (1-t)^3 *P0 + 3*t*(1-t)^2*P1 + 3*t^2*(1-t)*P2 + t^3*P3 

对于三次方程和

 p = (1-t)^2 *P0 + 2*(1-t)*t*P1 + t*t*P2

关于二次曲线。

t通常在0-1之间,但这并不是必要的——事实上,曲线可以延伸到无限远。P0、P1等是控制点。曲线通过两个端点,但通常不通过其他点。


8

你之前使用过C#库吗?

在C++中,尚未提供Bezier曲线的标准库函数。当然,你可以自己编写代码(CodeProject 示例)或寻找数学库。

这篇博客文章很好地解释了这个想法,但是用的是Actionscript。翻译应该不会有太大问题。


1
只是提醒一下。博客文章的链接显示为空白页面。 - R Sahu
1
@RSahu 感谢您的提醒;我在 Wayback Machine 中找到了一个存档副本并编辑了答案。 - Robert Penner

4
为了在给定的旅行百分比(t)和给定控制点(x1,y1),(x2,y2),(x3,y3)和(x4,y4)下获得沿立方曲线的个别点(x,y),我扩展了De Casteljau算法并重新排列了方程以最小化指数:
```html

为了在给定的旅行百分比 (t) 和给定控制点 (x1, y1)(x2, y2)(x3, y3)(x4, y4) 下获取沿着三次曲线的某个点 (x, y),我扩展了 De Casteljau 算法并重新排列了方程式以达到最小次数:

```
//Generalized code, not C++
variables passed to function:   t,  x1, y1, x2, y2, x3, y3, x4, y4
variables declared in function: t2, t3, x,  y
t2 = t * t
t3 = t * t * t
x = t3*x4 + (3*t2 - 3*t3)*x3 + (3*t3 - 6*t2 + 3*t)*x2 + (3*t2 - t3 - 3*t + 1)*x1
y = t3*y4 + (3*t2 - 3*t3)*y3 + (3*t3 - 6*t2 + 3*t)*y2 + (3*t2 - t3 - 3*t + 1)*y1

(t)是一个0到1之间的十进制数(0 <= t <= 1),代表曲线上行进的百分比。

公式对于x和y都是一样的,你可以编写一个函数,该函数接受一组通用的4个控制点或将系数分组:

t2 = t * t
t3 = t * t * t

A = (3*t2 - 3*t3)
B = (3*t3 - 6*t2 + 3*t)
C = (3*t2 - t3 - 3*t + 1)

x = t3*x4 + A*x3 + B*x2 + C*x1
y = t3*y4 + A*y3 + B*y2 + C*y1

对于二次函数,类似的方法如下:

t2 = t * t

A = (2*t - 2*t2)
B = (t2 - 2*t + 1)

x = t2*x3 + A*x2 + B*x1
y = t2*y3 + A*y2 + B*y1

1
  • 如果您只想显示贝塞尔曲线,可以在Windows上使用PolyBezier之类的工具。

  • 如果您想自己实现该例程,可以在互联网上找到线性插值代码

  • 我相信Boost库支持此功能。具体来说是线性插值,而不是特定的贝塞尔曲线。但请不要引用我的话。


太好了!我认为那个代码项目的示例很不错 :) - Nick Bolton
虽然这理论上回答了问题,但最好在此处包含答案的基本部分,并提供参考链接。 - Toby Speight

0

我基于这个例子https://dev59.com/fnRA5IYBdhLWcg3w6SRH#11435243进行了实现,但适用于任意数量的路径点

void bezier(int [] arr, int size, int amount) {
  int a[] = new int[size * 2];    
  for (int i = 0; i < amount; i++) {
    for (int j = 0; j < size * 2; j++) a[j] = arr[j];
    for (int j = (size - 1) * 2 - 1; j > 0; j -= 2) 
      for (int k = 0; k <= j; k++) 
        a[k] = a[k] + ((a[k+2] - a[k]) * i) / amount;
    circle(a[0], a[1], 3);  // draw a circle, in Processing
  }
}

其中arr是点数组{x1,y1,x2,y2,x3,y3... xn,yn},size是点的数量(是数组大小的一半),amount是输出点的数量。

为了进行最优计算,您可以使用2^n数量和位移:

void bezier(int [] arr, int size, int dense) {
  int a[] = new int[size * 2];    
  for (int i = 0; i < (1 << dense); i++) {
    for (int j = 0; j < size * 2; j++) a[j] = arr[j];
    for (int j = (size - 1) * 2 - 1; j > 0; j -= 2) 
      for (int k = 0; k <= j; k++) 
        a[k] = a[k] + (((a[k+2] - a[k]) * i) >> dense);
    circle(a[0], a[1], 3);  // draw a circle, in Processing
  }
}

0

这个在Github上的实现展示了如何计算一个简单的三次贝塞尔曲线,包括在0-1之间的't'值的法线和切线值。 它是维基百科公式的直接转换。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接