我想实现一个Bézier curve。我以前在 C# 中做过这个,但我对 C++ 库完全不熟悉。我应该如何创建二次曲线?
void printQuadCurve(float delta, Vector2f p0, Vector2f p1, Vector2f p2);
显然我们需要使用线性插值,但这是否存在于标准数学库中?如果没有,我在哪里可以找到它?
我正在使用 Linux。
我想实现一个Bézier curve。我以前在 C# 中做过这个,但我对 C++ 库完全不熟悉。我应该如何创建二次曲线?
void printQuadCurve(float delta, Vector2f p0, Vector2f p1, Vector2f p2);
显然我们需要使用线性插值,但这是否存在于标准数学库中?如果没有,我在哪里可以找到它?
我正在使用 Linux。
最近我遇到了同样的问题,想自己实现它。这张来自维基百科的图片帮助了我:
以下代码是用 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。只是为了展示基本思想......
对于那些要求三次贝塞尔曲线的人,它只是模拟(也来自维基百科):
这个答案为它提供了代码。
这是具有任意数量点的曲线的一般实现。
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);
}
您可以选择 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 等是控制点。曲线经过两个端点,但通常不经过其他点。
(x, y)
为了在给定的行程百分比下沿三次曲线获得一个单独的点(t)
,给定的控制点(x1, y1)
, (x2, y2)
, (x3, y3)
, 和(x4, y4)
我扩展了 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
如果您只想显示贝塞尔曲线,则可以使用PolyBezier for Windows 之类的东西。
如果您想自己实现该例程,您可以在 Intarnetz 各处找到线性插值代码。
我相信Boost 库对此有支持。线性插值,而不是贝塞尔曲线。但是,不要引用我的话。
我基于此示例https://stackoverflow.com/a/11435243/15484522进行了实现,但对于任意数量的路径点
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
}
}
这个在 github 上的实现展示了如何计算一个简单的三次贝塞尔曲线,“t”的值从 0->1 的正常值和正切值。它是维基百科上公式的直接换位。