我有一组控制点,我正试图基于这些控制点绘制一条三次B样条(阶3(。我遇到的问题是,我的曲线没有连接到最终控制点,而是将曲线绘制到不同区域的其他点。曲线点在一定时间后接近CCD_ 1。
仅控制点的图像
曲线和控制点的图像。请注意,曲线正确地从第一个控制点开始,但不会在最后一个控制点结束。
我正在使用的代码:
float Stroke::calculate_N(float t, int i, int j, vector<float> knots){
float t_1 = knots[i];
float t_2 = knots[(i + j)];
float t_3 = knots[(i + 1)];
float t_4 = knots[(i + j + 1)];
// Base case of basis function
if (j == 0){
if (t_1 <= t && t < t_3) return 1;
else return 0;
}
float temp1 = (t_2 - t_1 == 0) ? 0 : ((t - t_1) / (t_2 - t_1)) * calculate_N(t, i, j-1, knots);
float temp2 = (t_4 - t_3 == 0) ? 0 : ((t_4 - t) / (t_4 - t_3)) * calculate_N(t, i+1, j-1, knots);
return temp1 + temp2;
}
vector<float> make_knot_vector(int m, int p, int n){
vector<float> knots;
for (int i = 0; i <= p; i++){
knots.push_back(0.0);
}
for (int i = 1; i <= n - p; i++){
knots.push_back((float)i/(float)(n-p+1));
}
for (int i = 0; i <= p; i++){
knots.push_back(1.0);
}
return knots;
}
int main(){
// Init control points
s = Spline();
s.add_control_point(100,100);
s.add_control_point(232,71);
s.add_control_point(148,294);
s.add_control_point(310,115);
s.add_control_point(375,280);
// Get the number of knots based on the number of control points and degree
int num_ctrl_pts = s.get_control_points().size();
float NUM_KNOTS = (float)(num_ctrl_pts + 3 + 1);
// Draw each control point in red
for (auto pt : s.get_control_points()){
int x = pt->get_x();
int y = pt->get_y();
int r = s.get_radius();
vector<vector<float>> circle_points = calc_circ(y, x, r);
int si = circle_points.size();
for (auto circ_point : circle_points){
c->setColor(circ_point[0], circ_point[1], Color(1.0, 0.0, 0.0));
}
}
// Draw the curve
vector<float> knots = make_knot_vector(NUM_KNOTS, 3, num_ctrl_pts);
for (float t = 0.0; t < 1.0; t+= 1.0/1000.0){
Vector sum = Vector(0.0, 0.0);
for (int i = 0;i < num_ctrl_pts; i++){
Vector next = *(s.get_control_points()[i]);
float n = s.calculate_N(t, i, 3, knots);
next = next * n;
sum = sum + next;
}
cout<<"("<<(int)sum.get_x()<<", "<<(int)sum.get_y()<<")"<<endl;
// Draw the curve point in green
vector<vector<float>> circle_points = calc_circ((int)sum.get_y(), (int)sum.get_x(), s.get_radius());
for (auto circ_point : circle_points){
c->setColor(circ_point[0], circ_point[1], Color(0.0, 1.0, 0.0));
}
}
c->writeImage(path + "spline.ppm");
// delete canvas;
return 0;
}
三次B样条由一个起点(节点(、两个控制点和一个终点(节点(组成。曲线不通过其控制点,只通过其结。
当将多条三次B样条组合成一个形状时,为了避免间隙,一条样条的终点通常是下一条的起点。为了使曲线看起来平滑,这个结和相邻的控制点需要是共线的(三个控制点都在同一条线上(。
您应该检查Spline
类是否区分控制点和节点。如果没有,很可能它只是希望每三分(从第一分开始,然后跳过两分(都是一个结。在这种情况下,确保至少增加4点,或7点,或10点,等等
如果Spline
只接受结(在这种情况下,您应该将add_control_point()
成员函数重命名为add_knot()
(,它可以自动计算其控制点。一种常见的方法是构造Catmull-Rom样条曲线。样条曲线将穿过点2、3、4。。。n-1.要将第一个(从1到2(和最后一个线段(从n-1到n(相加,通常要将第一点和最后一点相加两次。
// Pseudo code:
auto spline = CatmullRom( { p1, p1, p2, p3, p4, p5, p5 } );
更好的解决方案是在第一点周围反映第二点:
auto p0 = 2 * p1 - p2;
auto p6 = 2 * p5 - p4;
auto spline = CatmullRom( { p0, p1, p2, p3, p4, p5, p6 } );