如何创建起止切线斜率不同的弧线的贝塞尔曲线。

问题描述 投票:1回答:1

我被这个问题困扰了一个星期,我似乎无法解决它。

我有一个弧线,当弧线是平的时候,我可以很容易地转换成一系列的贝塞尔曲线。

enter image description here

但我很难解决如何找到贝塞尔曲线 当弧线是螺旋线且末端切线有不同的斜率时.

这是我目前得到的最多的结果。

enter image description here

如你所见,每条贝塞尔曲线的控制点都不在正确的平面上,而且整个弧线的起点和终点切线(第二张图中的红色向量)没有被计算在内,因为我无法计算出如何计算。

为了从圆弧中找到平面版的bezier切片,我有这段代码,对于平面圆弧来说当然很好用。

    // from https://pomax.github.io/bezierinfo/#circles_cubic
    public CubicBezier ConvertArc(Vector3 origin, float radius, Vector3 from, Vector3 to, float angle)
    {
        var c = Math.Tan(angle * Mathf.Deg2Rad / 4f) * 4 / 3f * radius;

        var c1 = from + (from - origin).Perp().normalized * c;
        var c2 = to - (to - origin).Perp().normalized * c;
        return new CubicBezier(from, c1, c2, to);
    }

这是我目前创建每个贝塞尔切面的代码:

        //cut the arc in to bezier curves up to 90 degrees max
        float cuts = _arc.totalAngle / 90f;
        for (int i = 0; i < cuts; i++)
        {
            float t = i / cuts;
            float t2 = (i + 1) / cuts;

            Arc slice = new Arc(_arc,_arc.Point(t),_arc.Point(t2));

            //this function below is the issue, it needs start and end tangent for the slice, 
            //but i also don't know how to find the tangents at each slice for the whole arc
            //relating the start and end tangents of the entire arc
            //see above snippet for function code
            var cb = ConvertArc(slice.origin, slice.radius, slice.a, slice.b, slice.totalAngle);
            cb.DebugDraw(Color.yellow);
        }

希望有人能帮我解释一下如何正确地找到控制点以匹配切线的逻辑,我已经浪费了一个星期的时间,却没有什么进展。

这个是用C#写的,但我觉得语言不重要,不管是什么语言,数学就是数学。

我希望结果能尊重最终的切线斜率,这是一个直观的(虽然画得不好)。enter image description here

c# math geometry bezier curve
1个回答
0
投票

问题是贝塞尔控制点没有插值立方体那么直观。 因此,我们可以使用这些代替,并将其控制点转换成贝塞尔曲线后,使事情更容易。

  1. 简单地创建沿着你的路径的点的列表

    所有这些都是直接在路径上的,而且曲线的连续性是由插值三次方程本身保证的,所以不需要调整......。

    确保你有足够的点......例如全圆至少需要8个点,螺母16个更好......。

  2. 将路径点转换为贝塞尔立方体控制点

    所以只需在路径上选取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);
        }
    }
//---------------------------------------------------------------------------

还有一个简单的VCLGLC++的渲染

//---------------------------------------------------------------------------
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=0xxx.allocate(100) 预留空间 100 物品

只是为了确保代码的可理解性。

并进行预览。

preview

当你想编辑你的路径时 最好控制插值的立方体控制点 而不是贝兹尔,因为你学到的是硬道理 那些点没有那么直观,也不容易操作来实现想要的输出。

编辑1]输入点更好地匹配你的形状。

由于你最终提供了你想要的形状图像......你只需沿路径取样一些点并将其转换为bezier。所以唯一改变的是输入点。

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
投票

你有一段已知端点有切线的三维曲线,想建立贝塞尔曲线的近似。

Bezier曲线的内部控制点将位于与切线向量相邻的向量上。但你需要知道它们的长度。

圆弧的逼近方法选择这些向量的长度来提供与圆弧中间点重合的中间贝塞尔点。你可以在这里应用同样的方法。编写

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

将t=12,P=曲线中间的Bezier方程代入,然后找到未知的L。对所有三个分量都这样做,并得到一些平均值,提供相当好的误差(也许可以进行一些优化)。

如果曲线是高度不对称的--有人可能会尝试对两条切线使用不同的长度。

© www.soinside.com 2019 - 2024. All rights reserved.