如何为具有不同起点和终点切线斜率的弧创建贝塞尔曲线 [英] How to create bezier curves for an arc with different start and end tangent slopes

查看:16
本文介绍了如何为具有不同起点和终点切线斜率的弧创建贝塞尔曲线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经坚持了一个星期,现在我似乎无法解决它.

我有一条弧线,当弧线平坦时,我可以很容易地将其转换为一系列贝塞尔曲线:

但是当弧是螺旋线并且端切线具有不同的斜率时,我正在努力找出如何找到贝塞尔曲线.

这是我目前所了解的:

如您所见,每条贝塞尔曲线都有不在正确平面上的控制点,并且由于我无法工作,因此未考虑完整弧线的起点和终点切线(第二张图像中的红色矢量)知道怎么做.

要从弧中找到贝塞尔切片的平面版本,我有这段代码,它肯定适用于平面弧:

//来自 https://pomax.github.io/bezierinfo/#circles_cubic公共 CubicBezier ConvertArc(Vector3 原点,浮动半径,Vector3 从,Vector3 到,浮动角度){var c = Math.Tan(角度 * Mathf.Deg2Rad/4f) * 4/3f * 半径;var c1 = from + (from - origin).Perp().normalized * c;var c2 = to - (to - origin).Perp().normalized * c;return new CubicBezier(from, c1, c2, to);}

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

//将圆弧切割成最大 90 度的贝塞尔曲线浮动切割 = _arc.totalAngle/90f;for (int i = 0; i <切割; i++){浮动 t = i/削减;浮动 t2 = (i + 1)/削减;弧切片 = 新弧(_arc,_arc.Point(t),_arc.Point(t2));//下面这个函数是问题所在,它需要切片的开始和结束切线,//但我也不知道如何找到整个弧的每个切片的切线//关联整个圆弧的起点和终点//见上面的功能代码片段var cb = ConvertArc(slice.origin, slice.radius, slice.a, slice.b, slice.totalAngle);cb.DebugDraw(Color.yellow);}

希望有人可以帮助解释解决如何正确找到控制点以匹配切线的逻辑,已经浪费了一周的时间.

这是用 C# 编写的,但我认为语言无关紧要,无论语言如何,数学都是数学.

我希望结果如何尊重端切线斜率的视觉效果(虽然画得不好):

解决方案

问题是贝塞尔控制点不如插值三次方直观.所以我们可以使用这些来代替,然后将它们的控制点转换为贝塞尔曲线,以使事情变得更容易.

  1. 沿路径创建点列表

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

    确保你有足够的积分...例如,对于完整的圆,至少需要 8 个积分,螺母 16 更好...

  2. 将路径点转换为贝塞尔三次控制点

    所以只需在路径上选择 4 个后续点并使用以下方法将它们转换为贝塞尔控制点:

    • 当您想要编辑路径时,最好控制插值三次控制点而不是贝塞尔曲线,因为您通过艰难的方式了解到这些控制点并不那么直观且易于操作以实现所需的输出.

      [Edit1] 输入点更符合您的形状

      当您最终提供所需形状的图像时……您只需沿路径采样一些点并将其转换为贝塞尔曲线.所以唯一改变的是输入点:

      void generate(){整数 i,j,n;双 x,y,z,a,a0,a1,b,b0,b1,z0,dz,r,t;常量双度=M_PI/180.0;常量双弧度=180.0/M_PI;//生成一些螺旋路径点n=32;//沿路径的点数r=0.75;//曲线半径z0=0.0;//中间高度dz=0.1;//高度幅值a0=180.0*度;a1= 0.0*度;//角度范围b0= 30.0*度;b1=+330.0*度;//角度范围it4.num=0;//清除点列表对于 (i=0;i

      这里预览:

      并以 N=8 点预览:

      我只是用参数a将曲线和高度分成圆形路径和参数b的正弦曲线.如您所见,无论输入点如何变化,转换代码都是相同的......

      I've been stuck on this for a week now i can't seem to solve it.

      I have an arc which i can convert to a series of bezier curves quite easily when the arc is flat:

      But i am struggling to work out how to find the bezier curves when the arc is a helix and the end tangents have different slopes.

      This is as far as i have gotten so far:

      As you can see each bezier curve has control points that are not on the right plane, and the start and end tangent (the red vectors in the second image) of the full arc is not factored in as i couldn't work out how to do it.

      To find the flat version of the bezier slices from arcs i have this piece of code which certainly works fine for a flat arc:

          // 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);
          }
      

      This is my current code to create each bezier cut:

              //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);
              }
      

      Hope some one can help explain the logic to solve how to find the control points correctly to match the tangents, wasted a week already with little progress.

      This is written in C# but i don't think the language matters, math is math no matter the language.

      A visual (albeit poor drawing) of how i want the result to respect the end tangent slopes:

      解决方案

      The problem is that Bezier control points are not as intuitive as interpolation cubics. So we can use those instead and convert their control points into bezier later to make thing easier.

      1. Simply create list of points along your path

        all of these are directly on the path and the continuity of the curve is guaranteed by the interpolation cubic equation itself so no tweaking needed...

        be sure you have enough points ... for example for full circle at least 8 points are needed nut 16 are better ...

      2. Convert path points into Bezier cubic control points

        so simply pick 4 consequent points on path and convert them into bezier control points using this:

        to ensure continuity the next bezier should be done from next point ... So if we have points p0,p1,p2,p3,p4,p5... then we create beziers from (p0,p1,p2,p3) , (p1,p2,p3,p4) , ... and so on. The first point p0 determines starting direction and the last the ending one. If you want your path to start / end on those simply duplicate them ...

      Here a small unoptimized and crude example of this in 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);
              }
          }
      //---------------------------------------------------------------------------
      

      And simple render in 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);
          }
      //---------------------------------------------------------------------------
      

      I also used mine dynamic list template so:


      List<double> xxx; is the same as double xxx[];
      xxx.add(5); adds 5 to end of the list
      xxx[7] access array element (safe)
      xxx.dat[7] access array element (unsafe but fast direct access)
      xxx.num is the actual used size of the array
      xxx.reset() clears the array and set xxx.num=0
      xxx.allocate(100) preallocate space for 100 items

      just to be sure the code is comprehensable.

      And preview:

      When you want to edit your path its better to control the interpolation cubic control points instead of the bezier as you learned the hard way those are not as intuitive and easy to manipulate to achieve wanted output.

      [Edit1] input points better matching your shape

      As you finally provided image of shape you want ... you simply sample some points along the path and convert that into bezier. So the only stuff that changes are the input points:

      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);
              }
          }
      

      Here preview:

      And preview with N=8 points:

      I simply separated curve and height into circular path with parameter a and sinusoid with parameter b. As you can see the conversion code is the same no matter the change of input points ...

      这篇关于如何为具有不同起点和终点切线斜率的弧创建贝塞尔曲线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆