代码之家  ›  专栏  ›  技术社区  ›  Andrew

如何防止三次Bezier曲线中的循环

  •  0
  • Andrew  · 技术社区  · 9 年前

    我有一个计算,可以生成一条三维二维贝塞尔曲线。在这种情况下,端点是固定的,我的程序计算内部控制点。大多数时候,这些曲线在两个相邻形状之间产生简单的混合。但有时几何图形会导致绘制曲线会产生一个循环,这在这个应用程序中看起来永远不会很好。在这种情况下,我愿意修改控制点以防止循环。但要做到这一点,我需要检测给定的三次贝塞尔曲线在绘制时是否会循环。

    当然,我可以简单地在多个点对曲线进行采样并寻找循环,但我更希望基于8个变量(4个点中每个点的x和y值)找到代数解。理想情况下,该解决方案不仅会告诉我是否存在循环,而且会告诉我在某种意义上该循环有多“大”。但即使有一个二进制的是/否答案也是一个很大的帮助。

    有没有人知道一种算法,可以确定给定的二维贝塞尔曲线在绘制时是否会产生循环?

    2 回复  |  直到 9 年前
        1
  •  2
  •   Mike 'Pomax' Kamermans    9 年前

    当然:你可以看看它 canonical form 看看这四个点是否会导致曲线在一定存在循环的区域“结束”。

    1989年的论文概述了这方面的原始理论 "A Geometric Characterization of Parametric Cubic curves"

        2
  •  0
  •   Biły    8 年前

    首先是代码,然后是一些解释。这个过程有些耗时,所以代码做了轻微的优化。现在,这就像是一场激烈的选拔,很少有候选人能通过所有的考试。

    type
      myType=Integer; {remember to change here to your own type, 
        an integers are good for an educational purpose}
      point_2d=record    xx,yy :myType  end;
      vector_2d=record    vx,vy :myType  end;    
    
    function bezier_has_a_loop(p0,p1,p2,p3:point_2d):boolean;
                  {-------------------------}
                  function f_vec_from_2_pts(pa,pb:point_2d):vector_2d;
                  begin
                    with Result do begin
                      vx:=pb.xx - pa.xx;
                      vy:=pb.yy - pa.yy     end  end;
                  {-------------------------}
                  function f_cross_prod(va,vb : vector_2d):myType;
                  begin
                     Result := va.vx*vb.vy - va.vy*vb.vx      end;
                  {-------------------------}
                  function f_mult(m:myType; v:vector_2d):vector_2d;
                  begin
                    with Result do begin
                      vx := m*v.vx;
                      vy := m*v.vy   end   end;
                  {-------------------------}
                  function f_sum(va,vb : vector_2d):vector_2d;
                  begin
                    with Result do begin
                      vx := va.vx+vb.vx;
                      vy := va.vy+vb.vy    end   end;
                  {-------------------------}
                  function f_rotate(v, rotor : vector_2d):vector_2d;
                  var
                    m_sin,m_cos:myType; {only for a readability purpose}
                  begin
                    m_cos:=rotor.vx {
                    /sqrt(sqr(rotor.xx)+sqr(rotor.yy))  - unnecessary };
                    m_sin:=rotor.vy {
                    /sqrt(sqr(rotor.xx)+sqr(rotor.yy))  - unnecessary };
                    with Result do begin
                      vx := -v.vx * m_cos - v.vy *m_sin;
                      vy :=  v.vx * m_sin - v.vy *m_cos   end   end;
                  {-------------------------}
    var
      a,b,c, c1,c2,c3  :vector_2d;
      ab,ac,bc:myType;
      bb,s1,s2,delta,shift,t_1_2 : Double;
    begin
    
      a:=f_vec_from_2_pts(p0,p1);
      b:=f_vec_from_2_pts(p1,p2);
      c:=f_vec_from_2_pts(p2,p3);
    
      ab:=f_cross_prod(a,b); {on the planar, 
        myType for a cross product is just fine}}
      ac:=f_cross_prod(a,c);
      bc:=f_cross_prod(b,c);
    
      {==1== No inflection point(s) or cusp allowed}
      if ac*ac >= 4*ab*bc  then begin    Result:=False; exit end;
    
      c3:= f_sum( f_sum(a,c)  ,  f_mult( -2,b ) );
      if c3.vy<>0 then begin   {Is the bag's handle horizontal?}
        a := f_rotate(a,c3);      
        b := f_rotate(b,c3);
        c := f_rotate(c,c3);
        c3:= f_sum  ( f_sum(a,c) , f_mult(-2,b) ); 
        {Now the handle is forced to be horizontal.}
      end;
    
      c1:= f_mult ( 3,a );
      c2:= f_sum  ( f_mult(3,b) , f_mult(-3,a) );
    
      { Following 4 restrictions comes from a single caveats for a roots:
         0<= t1<t2<=1}
      bb:= -c1.vy / c2.vy;  { always  c2.vy<>0 }
    
      {==2== A central point (t1+t2)/2 outside the limits}
      if  (bb<0) or  (bb>2) then begin Result:=False;  exit  end;
    
      s1:= c1.vx/c3.vx;    { always  c3.vx<>0 }
      s2:= c2.vx/c3.vx;
      delta := -bb*(4*s2+3*bb)-4*s1;
    
      {==3==  delta is to big}
      if delta>1 then begin Result:=False;  exit  end;
    
      shift:=sqrt(delta);   { always  delta>0 }
      t_1_2:=bb-shift;    {for readability purpose only, 
        one can omit this and write below: if shift>bb }
    
      {==4==  t1 is to low }
      if t_1_2<0 then begin Result:=False; exit end;
      t_1_2:=bb+shift;      { no /2 here,beacause of *2 below}
    
      {==5==  t2 is to high}
      if t_1_2>2 then  Result:=False
                  else  Result:=True  { TA DA! Thank you for your patience. }
    end;
    

    现在是一些理论。

    1. 考虑4个点P0、P1、P2、P3。这些点定义(几乎总是)三次贝塞尔曲线。
    2. 假设点H1,例如矢量P1_H1是矢量P0_P1的一半。
    3. 假设点H2,例如向量P2_H2是向量P3_P2的一半。
    4. 最后,让我们创建否决权 小时 =H1_H2。(我个人把这个向量称为“袋子的把手”,猜猜为什么。)

    Bezier curve and its handle

    • 当你开始各向同性缩放或P0、P1、P2、P3的旋转,然后向量 小时 将相应地变换。
    • 也许对某人来说,这将是一个惊喜,矢量 小时 =[_h_x,_h_y]与向量[c3x,c3y]成比例,该向量由三次贝塞尔曲线的代数形式的最高系数创建。比例系数为(-1/2)。

    (事实上,当点H1=H2重合时 小时 向量消失,c3x=c3y=0,因此三次B zier曲线至少减少为从P0、H1、P3点创建的二次B zier。)

    现在线索是:向右旋转总是可以旋转矢量 小时 水平(或垂直)旋转,这种旋转将使c3y(或c3x)变为零,但保留循环。反过来,我们可以将所述问题简化为二次方程的平凡寻根。(我想这是找到解决方案的决定性提示。)

    为了测量循环,我建议考虑变量 希腊字母表的第4个字母 根据上面的代码。

       0 < delta <= 1
    When delta -> 0, the loop vanishes. 
    When delta -> 1, the loop becomes really pompous.
    

    我对这个提议不太满意,但总比没有好。