C-5 B样条曲线
C-5 B样条曲线
N i , 0 ( u ) = { 1 , u i ≤ u < u i + 1 0 , o t h e r s N_{i,0}(u)=\left\{\begin{matrix} 1 , \quad u_i\le u <u_{i+1} \\0 ,\quad others \qquad \quad\end{matrix}\right. Ni,0(u)={1,ui≤u<ui+10,others
N i , p ( u ) = u − u i u i + p − u i ⋅ N i , p − 1 ( u ) + u i + p + 1 − u u i + p + 1 − u i + 1 ⋅ N i + 1 , p − 1 ( u ) N_{i,p}(u)=\frac{u -u_i}{u_{i+p}-u_i} \cdot N_{i,p-1}(u) +\frac{u_{i+p+1} -u}{u_{i+p+1}-u_{i+1}} \cdot N_{i+1,p-1}(u) Ni,p(u)=ui+p−uiu−ui⋅Ni,p−1(u)+ui+p+1−ui+1ui+p+1−u⋅Ni+1,p−1(u)
C ( u ) = ∑ i = 1 n N i , p ( u ) ⋅ C o n t r o l P o i n t ( i ) C(u) = \sum ^n_{i=1} N_{i,p}(u)\cdot ControlPoint_{(i)} C(u)=i=1∑nNi,p(u)⋅ControlPoint(i)
-
C就是我们要求的曲线
-
只需要给定控制点数组和节点向量数组,我们就能根据以上公式得到一条B样条曲线。
-
n是给定控制点数组的长度,p是B样条的阶数,一般就直接设置为3了, u i u_i ui就算节点向量数组,可以从公式中发现,节点向量的长度等于 控制点数组长度+阶数+1。(从递归公式中的 u i + p + 1 u_{i+p+1} ui+p+1可以看出来)
-
前面的参数 N i , p N_{i,p} Ni,p,只需要给定节点向量和阶数就能直接提前求出来,可以写成矩阵的形式。方便后面移动控制点求解。
-
在Step标准中,B样条是如下定义的:
-
#21=B_SPLINE_CURVE_WITH_KNOTS('',3,(#118,#119,#120,#121),.UNSPECIFIED., .F.,.F.,(4,4),(0.,1.),.UNSPECIFIED.); //相当于输入控制点 (#118,#119,#120,#121),和节点向量(0,0,0,0,1,1,1,1) //有一个重复的节点,那里的连续性就会减一,具体可以见Games102,刘利刚老师的课 //3指3阶B样条曲线,第一个F指这个曲线没有闭合,(4,4)是修饰后面的节点向量,这条曲线比较简单,一般是下面#31这样的 //1阶B样条曲线的连续性是C1,也就是连连上,导数不连续。而3阶B样条曲线的连续性是C3 #31=B_SPLINE_CURVE_WITH_KNOTS('',3,(#88,#89,#90,#91,#92,#93,#94), .UNSPECIFIED.,.T.,.F.,(4,1,1,1,4),(0.,0.190110440975709,0.596109450554148, 0.871779783977488,1.),.UNSPECIFIED.);
-
局部性
- 局部性:我们操控第i个控制点,只能控制有限的一段曲线而不是影响到这条曲线
- 我们发现,控制点i前面的参数是 N i , p N_{i,p} Ni,p.这个参数一递归,最多最多只能影响到 u i u_i ui到 u i + p + 1 u_{i+p+1} ui+p+1这P+2个节点范围内的曲线,因为递归公式中参数的范围就是这个,然后在用阶梯函数吧,一点点线性插值,构成这一段B样条曲线
代码
- 对于上述的#21的样条曲线,可以用如下代码求解,并将其离散化成32个点
- 代码和详细注释如下,代码中的t相当于公式中的u,一般是从0到1的参变量
#include <iostream>
#include <vector>
#include <array>
class Point {
public:
double x=0;
double y=0;
};
class NubrsEdge {
public:
bool ISClOSED=0;
//B样条曲线的介数,一般为3
int P;
//控制点
std::vector<Point> ControlPoints;
//节点向量
std::vector<double> u;
//利用节点向量计算得到的参数,我们把B样条曲线统一分成32份
std::vector<std::array<double, 32>> N_p;
//递归计算参数N
double N(int i,int p,double t) {
if (p == 0) {
if (u[i] <= t && t < u[i + 1]) return 1;
else return 0;
}
else{
double x1 = 0;
//对重复节点的处理!!!!!!!!!!
//不然就出现除数等于0的情况了
if (u[i + p] != u[i])
x1 = (t - u[i]) / (u[i + p] - u[i]);
double x2 = 0;
//对重复节点的处理
if(u[i + p + 1] != u[i + 1])
x2 = (u[i + p + 1] - t) / (u[i + p + 1] - u[i + 1]);
return x1 * N(i, p - 1, t) + x2 * N(i + 1, p - 1, t);
}
}
//计算参数,$N_{i,p}(u)$
void calculate_N_p() {
N_p.resize(ControlPoints.size());
for (int i = 0; i < ControlPoints.size(); i++) {
for (int t = 0; t < 32; t++) {
N_p[i][t] = N(i, P, double(t)/31.00001);
}
}
}
//相当于在做矩阵的乘法
std::vector<Point> Discretization() {
calculate_N_p();
std::vector<Point> result(32);
for (int t = 0; t < 32; t++) {
for(int i=0;i < ControlPoints.size(); i++)
{
result[t].x += ControlPoints[i].x * N_p[i][t];
result[t].y += ControlPoints[i].y * N_p[i][t];
}
}
return result;
}
};
int main()
{
NubrsEdge e;
// u.size() = 8 = ControlPoints.size + P +1 =4 + 3 + 1
e.ControlPoints= {
{-410.738133667533,-34.3141639975569},
{-360.696916167804,124.126883382823},
{-317.136530803152,125.95962808178},
{-280.056977573576,-28.8159299006879}
};
e.u = { 0, 0, 0, 0, 1,1,1, 1 };
e.P = 3;
// 离散化曲线
std::vector<Point> curvePoints = e.Discretization();
// 打印结果
for (int i = 0; i < curvePoints.size(); i++) {
std::cout << "Point " << i << ": (" << curvePoints[i].x << ", " << curvePoints[i].y << ")" << std::endl;
}
return 0;
}
- 可以发现起始点和结束点与第一个和最后一个控制点重合