2010-11-03 65 views
15

为了与通常的四个点A,B,c和d的三次贝塞尔曲线上的点的切线,查找三次Bezier曲线

对于给定值t,

如何最优雅在那一点找到正切

回答

29

曲线的正切就是它的导数。参数方程米哈尔用途:

P(t) = (1 - t)^3 * P0 + 3t(1-t)^2 * P1 + 3t^2 (1-t) * P2 + t^3 * P3 

应该有

dP(t)/dt = -3(1-t)^2 * P0 + 3(1-t)^2 * P1 - 6t(1-t) * P1 - 3t^2 * P2 + 6t(1-t) * P2 + 3t^2 * P3 

衍生物其中,顺便说一句,似乎是错在你前面的问题。我相信你在那里使用斜率为二次贝塞尔曲线,而不是立方。

从那里开始,执行计算的C函数应该是微不足道的,就像Michal已经为曲线本身提供的一样。

7

这里经过全面测试的代码复制并粘贴:

它借鉴approxidistant点沿曲线,它绘制的切线。

bezierInterpolation找到点

bezierTangent找到切线

两个版本的下方供给的bezierInterpolation

bezierInterpolation作品完美

altBezierInterpolation是完全一样的,但它是以扩大的,非常清楚的,解释性的方式写成的。它使得算术更容易理解。

使用这两个例程之一:结果是相同的。

在这两种情况下,请使用bezierTangent来查找切线。 (注意:Michal的神话般的代码库here。)

一个完整的例子,如何使用drawRect:也包括在内。

// MBBezierView.m original BY MICHAL stackoverflow #4058979 

#import "MBBezierView.h" 



CGFloat bezierInterpolation(
    CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) { 
// see also below for another way to do this, that follows the 'coefficients' 
// idea, and is a little clearer 
    CGFloat t2 = t * t; 
    CGFloat t3 = t2 * t; 
    return a + (-a * 3 + t * (3 * a - a * t)) * t 
    + (3 * b + t * (-6 * b + b * 3 * t)) * t 
    + (c * 3 - c * 3 * t) * t2 
    + d * t3; 
} 

CGFloat altBezierInterpolation(
    CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) 
    { 
// here's an alternative to Michal's bezierInterpolation above. 
// the result is absolutely identical. 
// of course, you could calculate the four 'coefficients' only once for 
// both this and the slope calculation, if desired. 
    CGFloat C1 = (d - (3.0 * c) + (3.0 * b) - a); 
    CGFloat C2 = ((3.0 * c) - (6.0 * b) + (3.0 * a)); 
    CGFloat C3 = ((3.0 * b) - (3.0 * a)); 
    CGFloat C4 = (a); 

    // it's now easy to calculate the point, using those coefficients: 
    return (C1*t*t*t + C2*t*t + C3*t + C4 ); 
    } 







CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) 
{ 
    // note that abcd are aka x0 x1 x2 x3 

/* the four coefficients .. 
    A = x3 - 3 * x2 + 3 * x1 - x0 
    B = 3 * x2 - 6 * x1 + 3 * x0 
    C = 3 * x1 - 3 * x0 
    D = x0 

    and then... 
    Vx = 3At2 + 2Bt + C   */ 

    // first calcuate what are usually know as the coeffients, 
    // they are trivial based on the four control points: 

    CGFloat C1 = (d - (3.0 * c) + (3.0 * b) - a); 
    CGFloat C2 = ((3.0 * c) - (6.0 * b) + (3.0 * a)); 
    CGFloat C3 = ((3.0 * b) - (3.0 * a)); 
    CGFloat C4 = (a); // (not needed for this calculation) 

    // finally it is easy to calculate the slope element, 
    // using those coefficients: 

    return ((3.0 * C1 * t* t) + (2.0 * C2 * t) + C3); 

    // note that this routine works for both the x and y side; 
    // simply run this routine twice, once for x once for y 
    // note that there are sometimes said to be 8 (not 4) coefficients, 
    // these are simply the four for x and four for y, 
    // calculated as above in each case. 
} 







@implementation MBBezierView 

- (void)drawRect:(CGRect)rect { 
    CGPoint p1, p2, p3, p4; 

    p1 = CGPointMake(30, rect.size.height * 0.33); 
    p2 = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)); 
    p3 = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)); 
    p4 = CGPointMake(-30 + CGRectGetMaxX(rect), rect.size.height * 0.66); 

    [[UIColor blackColor] set]; 
    [[UIBezierPath bezierPathWithRect:rect] fill]; 
    [[UIColor redColor] setStroke]; 
    UIBezierPath *bezierPath = [[[UIBezierPath alloc] init] autorelease]; 
    [bezierPath moveToPoint:p1]; 
    [bezierPath addCurveToPoint:p4 controlPoint1:p2 controlPoint2:p3]; 
    [bezierPath stroke]; 

    [[UIColor brownColor] setStroke]; 

// now mark in points along the bezier! 

    for (CGFloat t = 0.0; t <= 1.00001; t += 0.05) { 
    [[UIColor brownColor] setStroke]; 

     CGPoint point = CGPointMake(
      bezierInterpolation(t, p1.x, p2.x, p3.x, p4.x), 
      bezierInterpolation(t, p1.y, p2.y, p3.y, p4.y)); 

      // there, use either bezierInterpolation or altBezierInterpolation, 
      // identical results for the position 

     // just draw that point to indicate it... 
     UIBezierPath *pointPath = 
      [UIBezierPath bezierPathWithArcCenter:point 
      radius:5 startAngle:0 endAngle:2*M_PI clockwise:YES]; 
     [pointPath stroke]; 

     // now find the tangent if someone on stackoverflow knows how 
     CGPoint vel = CGPointMake(
      bezierTangent(t, p1.x, p2.x, p3.x, p4.x), 
      bezierTangent(t, p1.y, p2.y, p3.y, p4.y)); 

     // the following code simply draws an indication of the tangent 
     CGPoint demo = CGPointMake(point.x + (vel.x*0.3), 
             point.y + (vel.y*0.33)); 
     // (the only reason for the .3 is to make the pointers shorter) 
     [[UIColor whiteColor] setStroke]; 
     UIBezierPath *vp = [UIBezierPath bezierPath]; 
     [vp moveToPoint:point]; 
     [vp addLineToPoint:demo]; 
     [vp stroke]; 
    } 
} 

@end 

to draw that class... 
MBBezierView *mm = [[MBBezierView alloc] 
        initWithFrame:CGRectMake(400,20, 600,700)]; 
[mm setNeedsDisplay]; 
[self addSubview:mm]; 

这里有两个例程以计算大约等距点,和那些的切线,沿一条Bezier三次。

为了清晰和可靠,这些例程以最简单,最具说明性的方式编写。

CGFloat bezierPoint(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) 
    { 
    CGFloat C1 = (d - (3.0 * c) + (3.0 * b) - a); 
    CGFloat C2 = ((3.0 * c) - (6.0 * b) + (3.0 * a)); 
    CGFloat C3 = ((3.0 * b) - (3.0 * a)); 
    CGFloat C4 = (a); 

    return (C1*t*t*t + C2*t*t + C3*t + C4 ); 
    } 

CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) 
    { 
    CGFloat C1 = (d - (3.0 * c) + (3.0 * b) - a); 
    CGFloat C2 = ((3.0 * c) - (6.0 * b) + (3.0 * a)); 
    CGFloat C3 = ((3.0 * b) - (3.0 * a)); 
    CGFloat C4 = (a); 

    return ((3.0 * C1 * t* t) + (2.0 * C2 * t) + C3); 
    } 

四个预先计算的值,C1 C2 C3 C4,有时称为系数贝塞尔的。(回想一下bc d通常被称为四个控制点。)

当然,t从0到1,例如每0.05。

只需一次单独调用这些例程一次X,然后Y.

希望它可以帮助别人!


重要的事实:

(1)这是一个绝对的事实是:不幸的是,有,当然,没有一种方法,由苹果公司提供,从UIBezierPath提取点。 (2)不要忘记,像沿着沿着UIBezierPath路径创建的动画一样简单。 Google many examples

(3)很多人问,“Can not CGPathApply用于从UIBezierPath中提取点?”没有,CGPathApply是风马牛不相及的:它只是让你在做任何路径的指令列表(因此,“从这里开始”,“画一条直线到这个地步”,等等等等)

1

我发现它使用提供的方程太容易出错。太容易错过微妙的t或错位的支架。

相比之下,维基百科提供了一个更清晰,更清洁,衍生恕我直言:

enter image description here

...这在代码很容易实现的:

3f * oneMinusT * oneMinusT * (p1 - p0) 
+ 6f * t * oneMinusT * (p2 - p1) 
+ 3f * t * t * (p3 - p2) 

(假设你有病媒减去你选择的语言配置;问题没有具体标记为ObjC,而iOS现在有几个langs可用)

+0

非常优雅的输入广告我,谢谢。 (纯粹作为一种好奇心,在目前的Swift-iOS中,它们有一个f * -up,其中的代码行太长......不起作用,因此,可以将其分解为几个部分!) – Fattie 2018-01-15 01:26:59