问题在于 Bezier 控制点不如插值三次方直观。所以我们可以使用这些来代替,然后将它们的控制点转换为贝塞尔曲线,以使事情变得更容易。
-
只需创建路径上的点列表
所有这些都直接在路径上,曲线的连续性由插值三次方程本身保证,因此不需要调整......
确保你有足够的分数......例如,对于完整的圆圈,至少需要 8 个点,螺母 16 更好......
-
将路径点转换为贝塞尔三次控制点
所以只需在路径上选择 4 个后续点并使用以下方法将它们转换为贝塞尔控制点:
为了确保连续性,应该从下一个点开始下一个贝塞尔曲线...所以如果我们有点 p0,p1,p2,p3,p4,p5... 那么我们从 (p0,p1,p2,p3) , (p1,p2,p3,p4) 创建贝塞尔曲线, ... 等等。第一个点p0 确定起始方向,最后一个点确定结束方向。如果您希望您的路径开始/结束那些只需复制它们...
这里有一个 C++ 中未优化且粗略的小例子:
//---------------------------------------------------------------------------
List<double> it4; // interpolation cubic control points
List<double> bz4; // bezier cubic control points
//---------------------------------------------------------------------------
void generate()
{
int i,j,n;
double x,y,z,a,a0,a1,z0,z1,da,dz,r;
const double deg=M_PI/180.0;
const double rad=180.0/M_PI;
// generate some helix path points
n=32; // number of points along path
r=0.75; // radius
z0=0.0; z1=0.5; // height range
a0=-25.0*deg; a1=+720.0*deg; // angle range
da=(a1-a0)/double(n);
dz=(z1-z0)/double(n);
it4.num=0; // clear list of points
for (z=z0,a=a0,i=0;i<n;i++,a+=da,z+=dz)
{
// 3D point on helix
x=r*cos(a);
y=r*sin(a);
// add it to the list
it4.add(x);
it4.add(y);
it4.add(z);
}
// convert it4 into bz4 control points
bz4.num=0; // clear list of points
for (i=0;i<=it4.num-12;i+=3)
{
const double m=1.0/6.0;
double x0,y0,z0,x1,y1,z1,x2,y2,z2,x3,y3,z3;
double X0,Y0,Z0,X1,Y1,Z1,X2,Y2,Z2,X3,Y3,Z3;
j=i;
X0=it4[j]; j++; Y0=it4[j]; j++; Z0=it4[j]; j++;
X1=it4[j]; j++; Y1=it4[j]; j++; Z1=it4[j]; j++;
X2=it4[j]; j++; Y2=it4[j]; j++; Z2=it4[j]; j++;
X3=it4[j]; j++; Y3=it4[j]; j++; Z3=it4[j]; j++;
x0 = X1; y0 = Y1; z0 = Z1;
x1 = X1-(X0-X2)*m; y1 = Y1-(Y0-Y2)*m; z1 = Z1-(Z0-Z2)*m;
x2 = X2+(X1-X3)*m; y2 = Y2+(Y1-Y3)*m; z2 = Z2+(Z1-Z3)*m;
x3 = X2; y3 = Y2; z3 = Z2;
bz4.add(x0); bz4.add(y0); bz4.add(z0);
bz4.add(x1); bz4.add(y1); bz4.add(z1);
bz4.add(x2); bz4.add(y2); bz4.add(z2);
bz4.add(x3); bz4.add(y3); bz4.add(z3);
}
}
//---------------------------------------------------------------------------
在 VCL/GL/C++ 中进行简单渲染
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
float aspect=float(xs)/float(ys);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0/aspect,aspect,0.1,100.0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-2.5);
glRotatef(-70.0,1.0,0.0,0.0);
glRotatef(-130.0,0.0,0.0,1.0);
glEnable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
int i,j;
// render axises
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3d(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0);
glColor3f(0.0,1.0,0.0); glVertex3d(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0);
glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0);
glEnd();
// render it4 control points (aqua)
glColor3f(0.0,1.0,1.0);
glPointSize(8);
glBegin(GL_POINTS);
for (i=0;i<it4.num;i+=3) glVertex3dv(it4.dat+i);
glEnd();
glPointSize(1);
// render bz4 control points (magenta)
glColor3f(1.0,0.0,1.0);
glPointSize(4);
glBegin(GL_POINTS);
for (i=0;i<bz4.num;i+=3) glVertex3dv(bz4.dat+i);
glEnd();
glPointSize(1);
// render bz4 path (yellow)
double t,tt,ttt,cx[4],cy[4],cz[4],x,y,z;
double x0,y0,z0,x1,y1,z1,x2,y2,z2,x3,y3,z3;
glColor3f(1.0,1.0,0.0);
glLineWidth(2);
for (i=0;i<=bz4.num-12;i+=12)
{
j=i;
x0=bz4[j]; j++; y0=bz4[j]; j++; z0=bz4[j]; j++;
x1=bz4[j]; j++; y1=bz4[j]; j++; z1=bz4[j]; j++;
x2=bz4[j]; j++; y2=bz4[j]; j++; z2=bz4[j]; j++;
x3=bz4[j]; j++; y3=bz4[j]; j++; z3=bz4[j]; j++;
cx[0]= ( x0);
cx[1]= (3.0*x1)-(3.0*x0);
cx[2]= (3.0*x2)-(6.0*x1)+(3.0*x0);
cx[3]= ( x3)-(3.0*x2)+(3.0*x1)-( x0);
cy[0]= ( y0);
cy[1]= (3.0*y1)-(3.0*y0);
cy[2]= (3.0*y2)-(6.0*y1)+(3.0*y0);
cy[3]= ( y3)-(3.0*y2)+(3.0*y1)-( y0);
cz[0]= ( z0);
cz[1]= (3.0*z1)-(3.0*z0);
cz[2]= (3.0*z2)-(6.0*z1)+(3.0*z0);
cz[3]= ( z3)-(3.0*z2)+(3.0*z1)-( z0);
glBegin(GL_LINE_STRIP);
for (t=0.0,j=0;j<20;j++,t+=0.05)
{
tt=t*t; ttt=tt*t;
x=cx[0]+cx[1]*t+cx[2]*tt+cx[3]*ttt;
y=cy[0]+cy[1]*t+cy[2]*tt+cy[3]*ttt;
z=cz[0]+cz[1]*t+cz[2]*tt+cz[3]*ttt;
glVertex3d(x,y,z);
}
glEnd();
}
glLineWidth(1);
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
我也使用了我的动态列表模板,所以:
List<double> xxx; 与double xxx[]; 相同
xxx.add(5); 将5 添加到列表末尾
xxx[7]访问数组元素(安全)
xxx.dat[7]访问数组元素(不安全但快速直接访问)
xxx.num是数组实际使用的大小
xxx.reset() 清除数组并设置xxx.num=0
xxx.allocate(100) 为 100 项目预分配空间
只是为了确保代码是可理解的。
和预览:
当您想要编辑路径时,最好控制插值三次控制点而不是贝塞尔曲线,因为您通过艰难的方式了解到这些控制点并不那么直观且易于操作以实现所需的输出。
[Edit1] 输入点更符合您的形状
当您最终提供所需形状的图像时……您只需沿路径采样一些点并将其转换为贝塞尔曲线。所以唯一改变的是输入点:
void generate()
{
int i,j,n;
double x,y,z,a,a0,a1,b,b0,b1,z0,dz,r,t;
const double deg=M_PI/180.0;
const double rad=180.0/M_PI;
// generate some helix path points
n=32; // number of points along path
r=0.75; // curve radius
z0=0.0; // mid height
dz=0.1; // height amplitude
a0=180.0*deg; a1= 0.0*deg; // angle range
b0= 30.0*deg; b1=+330.0*deg; // angle range
it4.num=0; // clear list of points
for (i=0;i<n;i++)
{
// parameters
t=double(i)/double(n-1);
a=a0+(a1-a0)*t;
b=b0+(b1-b0)*t;
// curve
x=r*cos(a);
y=r*sin(a);
// height
z=z0+dz*sin(b);
// add it to the list
it4.add(x);
it4.add(y);
it4.add(z);
}
// convert it4 into bz4 control points
bz4.num=0; // clear list of points
for (i=0;i<=it4.num-12;i+=3)
{
const double m=1.0/6.0;
double x0,y0,z0,x1,y1,z1,x2,y2,z2,x3,y3,z3;
double X0,Y0,Z0,X1,Y1,Z1,X2,Y2,Z2,X3,Y3,Z3;
j=i;
X0=it4[j]; j++; Y0=it4[j]; j++; Z0=it4[j]; j++;
X1=it4[j]; j++; Y1=it4[j]; j++; Z1=it4[j]; j++;
X2=it4[j]; j++; Y2=it4[j]; j++; Z2=it4[j]; j++;
X3=it4[j]; j++; Y3=it4[j]; j++; Z3=it4[j]; j++;
x0 = X1; y0 = Y1; z0 = Z1;
x1 = X1-(X0-X2)*m; y1 = Y1-(Y0-Y2)*m; z1 = Z1-(Z0-Z2)*m;
x2 = X2+(X1-X3)*m; y2 = Y2+(Y1-Y3)*m; z2 = Z2+(Z1-Z3)*m;
x3 = X2; y3 = Y2; z3 = Z2;
bz4.add(x0); bz4.add(y0); bz4.add(z0);
bz4.add(x1); bz4.add(y1); bz4.add(z1);
bz4.add(x2); bz4.add(y2); bz4.add(z2);
bz4.add(x3); bz4.add(y3); bz4.add(z3);
}
}
这里预览:
并以 N=8 点预览:
我只是用参数a 将曲线和高度分成圆形路径,用参数b 将正弦曲线分离。如您所见,无论输入点如何变化,转换代码都是相同的......