Как создать кривые Безье для ар c с разными начальными и конечными касательными наклонами - PullRequest
1 голос
/ 07 мая 2020

Я застрял на этом уже неделю, и я не могу решить эту проблему.

У меня есть ar c, который я могу довольно легко преобразовать в серию кривых Безье, когда ar c плоский:

enter image description here

Но я изо всех сил пытаюсь понять, как найти кривые Безье, когда ar c представляет собой спираль, а конечные касательные имеют разные уклоны.

Это все, что я получил далеко:

enter image description here

Как вы можете видеть, каждая кривая Безье имеет контрольные точки, которые не находятся в правой плоскости, а также начальную и конечную касательную (красный векторы на втором изображении) полного ar c не учитывается, так как я не мог понять, как это сделать.

Чтобы найти плоскую версию срезов Безье из дуг, у меня есть этот кусок кода, который, безусловно, отлично работает для плоской арки. logi c, чтобы решить, как правильно найти контрольные точки, чтобы они соответствовали касательным, уже потрачено впустую неделю с небольшой прогресс.

Это написано на C#, но я не думаю, что язык имеет значение, математика есть математика, независимо от языка.

Визуальный (хотя и плохой рисунок) того, как я хотите, чтобы результат учитывал конечные касательные наклоны: enter image description here

Ответы [ 2 ]

0 голосов
/ 07 мая 2020

Проблема в том, что контрольные точки Безье не так интуитивно понятны, как кубики интерполяции. Так что мы можем использовать их вместо этого и позже преобразовать их контрольные точки в кривые Безье, чтобы упростить задачу.

  1. Просто создайте список точек вдоль вашего пути

    все они находятся прямо на пути, и непрерывность кривой гарантируется кубом интерполяции c уравнением, поэтому никаких настроек не требуется ...

    убедитесь, что у вас достаточно точек ... например для полного круга необходимо не менее 8 точек, гайка 16 лучше ...

  2. Конвертировать точки пути в куби Безье 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 элементов

просто чтобы убедиться, что код понятен.

И предварительный просмотр:

preview

Если вы хотите отредактировать свой путь, лучше управляйте кубом интерполяции 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);
        }
    }

Здесь предварительный просмотр:

preview N=32

И предварительный просмотр с N = 8 точки:

preview N=8

Я просто разделил кривую и высоту на круговую траекторию с параметром a и синусоиду с параметром b. Как видите, код преобразования остается неизменным независимо от изменения точек ввода ...

0 голосов
/ 07 мая 2020

У вас есть некоторый сегмент трехмерной кривой с известными касательными в конечных точках и вы хотите построить аппроксимацию Безье.

Внутренние контрольные точки кривой Безье будут l ie на векторах, коллинеарных касательным векторам. Но нужно знать их длину.

Метод аппроксимации круга ar c выбирает такую ​​длину этих векторов, чтобы средняя точка Безье совпадала со средней точкой ar c. Здесь вы можете применить тот же метод. Напишите

P1 = P0 + T0 * L
P2 = P3 - T3 * L

замените в уравнении Безье с t = 1/2, P = серединой кривой и найдите неизвестное L. Сделайте это для всех трех компонентов и получите некоторое среднее значение, обеспечивающее довольно хорошую ошибку (возможно, некоторая оптимизация возможно).

Если кривая сильно несимметрична c - кто-то может попробовать использовать разные длины для обеих касательных.

...