Проблема в том, что контрольные точки Безье не так интуитивно понятны, как кубики интерполяции. Так что мы можем использовать их вместо этого и позже преобразовать их контрольные точки в кривые Безье, чтобы упростить задачу.
Просто создайте список точек вдоль вашего пути
все они находятся прямо на пути, и непрерывность кривой гарантируется кубом интерполяции c уравнением, поэтому никаких настроек не требуется ...
убедитесь, что у вас достаточно точек ... например для полного круга необходимо не менее 8 точек, гайка 16 лучше ...
Конвертировать точки пути в куби Безье c контрольные точки
просто выберите 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);
}
//---------------------------------------------------------------------------
Я также использовал свой шаблон списка Dynami c, поэтому:
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
элементов
просто чтобы убедиться, что код понятен.
И предварительный просмотр:
Если вы хотите отредактировать свой путь, лучше управляйте кубом интерполяции c контрольными точками вместо кривой Безье, поскольку вы на собственном опыте узнали, что они не так интуитивно понятны и просты в использовании для достижения желаемого результата.
[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
. Как видите, код преобразования остается неизменным независимо от изменения точек ввода ...