首先是代码,然后是一些解释。这个过程有些耗时,所以代码做了轻微的优化。现在,这就像是一场激烈的选拔,很少有候选人能通过所有的考试。
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;
现在是一些理论。
-
考虑4个点P0、P1、P2、P3。这些点定义(几乎总是)三次贝塞尔曲线。
-
假设点H1,例如矢量P1_H1是矢量P0_P1的一半。
-
假设点H2,例如向量P2_H2是向量P3_P2的一半。
-
最后,让我们创建否决权
小时
=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.
我对这个提议不太满意,但总比没有好。