美文网首页
贝赛尔曲线知识详解

贝赛尔曲线知识详解

作者: 充满活力的早晨 | 来源:发表于2018-09-18 14:39 被阅读103次

    这些天pop 动画源码,里面涉及到贝塞尔曲线。对贝塞尔曲线以前只是局限在会用的阶段,没仔细详细研究过,今天准备仔细看看,搞明白。

    贝赛尔曲线 概念

    贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋。

    贝塞尔曲线的公式

    贝塞尔曲线通用公式

    术语

    一些关于参数曲线的术语,有


    image.png

    即多项式


    image.png

    又称作n阶的伯恩斯坦基底多项式,定义00 = 1。
    Pi称作贝济埃曲线的控制点。多边形以带有线的贝济埃点连接而成,起始于P0并以Pn终止,称作贝济埃多边形(或控制多边形)。贝济埃多边形的凸包包含有贝济埃曲线。

    二项式定理

    这里我看到


    image.png

    一脸懵逼,虽然以前学过,但是忘记了,因此这里查了半天才知道这么写法是什么意思了。

    image.png

    看懂这个图就明白二项式前面的项了

    注解

    • 开始于P0并结束于Pn的曲线,即所谓的端点插值法属性。
    • 曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝济埃曲线是直线的充分必要条件是控制点共线。
    • 曲线的起始点结束点相切于贝济埃多边形。
    • 一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝济埃曲线。
    • 一些看似简单的曲线(如圆)无法以贝济埃曲线精确的描述,或分段成贝济埃曲线。
    • 位于固定偏移量的曲线(来自给定的贝济埃曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝济埃曲线精确的形成(某些平凡实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值。

    基本公式的低阶公式

    线性公式 (当n=1)时候

    一阶

    给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。

    二次方公式

    image.png

    三次方公式

    image.png

    构建贝塞尔曲线

    线型曲线

    线性贝塞尔曲线函数中的t会经过由P0至P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线。

    线型贝塞尔曲线

    二次曲线

    为建构二次贝塞尔曲线,可以中介点Q0和Q1作为由0至1的t:
    由P0至P1的连续点Q0,描述一条线性贝塞尔曲线。
    由P1至P2的连续点Q1,描述一条线性贝塞尔曲线。
    由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线。


    三阶贝塞尔曲线

    三次曲线,可由线性贝济埃曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构



    高阶贝塞尔曲线

    对于四次曲线,可由线性贝济埃曲线描述的中介点Q0、Q1、Q2、Q3,由二次贝济埃曲线描述的点R0、R1、R2,和由三次贝济埃曲线描述的点S0、S1所建构


    升阶

    n次贝济埃曲线可以转换为一个形状完全相同的n+1次贝济埃曲线。 这在软件只支援特定阶次的贝济埃曲线时很有用。 例如,Cairo只支援三次贝济埃曲线,你就可以用升阶的方法在Cairo画出二次贝济埃曲线。

    我们利用

    这个特性来做升阶。我们把曲线方程中每一项 都乘上 (1 − t) 或 t,让每一项都往上升一阶。以下是将二阶升为三阶的范例

    这里我估计好多很看着很懵,我也懵,仔细看就明白了,将上述公式的省略项补齐就好看了。
    (1-t)2P0 + 2(1-t)tP1 + t2P2 这是二阶的公式
    推导出三阶如下
       (1-t)2P0        +     2(1-t)tP1        +    t2P2
    = (1-t)2P0[(1-t)+t]     +   2(1-t)tP1[(1-t)+t]      +  t2P2[(1-t)+t]
    = (1-t)2P0(1-t) + (1-t)2P0t +  2(1-t)tP1(1-t) + 2(1-t)tP1t  +  t2P2(1-t) + t2P2t
    =(1-t)3P0 + (1-t)2tP0    +  2(1-t)2tP1 + 2(1-t)t2P1    +  (1-t)t2P2 + t3P2
    整理
    =(1-t)3P0  +  [(1-t)2tP0 + 2(1-t)2tP1]  +  [2(1-t)t2P1 + (1-t)t2P2]  +  t3P2
    =(1-t)3P0  +  (1-t)2t(P0+2P1)    + (1-t)t2(2P1+P2)        +  t3P2
    =(1-t)3P0  +  [(1-t)2tP0 + 2(1-t)2tP1]  +  [2(1-t)t2P1 + (1-t)t2P2]  +  t3P2
    =(1-t)3P0  +  3(1-t)2t(P0+2P1)/3    + 3(1-t)t2(2P1+P2)/3     +  t3P2

    通过上面的规律下面的也是一样的计算获取高阶
    对任何的n值,我们都可以使用以下等式




    式中P-1 和Pn+1 可以任意挑选。
    因此,新的控制点为

    程序范例

    三阶贝塞尔曲线

    namespace WebCore {
      struct UnitBezier {
        UnitBezier(double p1x, double p1y, double p2x, double p2y)
        {
        // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
    /// 三阶贝塞尔曲线系数计算,推导在pop动画分析里讲解的
          cx = 3.0 * p1x;
          bx = 3.0 * (p2x - p1x) - cx;
          ax = 1.0 - cx -bx;
          cy = 3.0 * p1y;
          by = 3.0 * (p2y - p1y) - cy;
          ay = 1.0 - cy - by;
        }
        
        /// 时间对应的点x
        double sampleCurveX(double t)
        {
          // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
          return ((ax * t + bx) * t + cx) * t;
        }
          ///时间对应的点y
        double sampleCurveY(double t)
        {
          return ((ay * t + by) * t + cy) * t;
        }
        
          ///x的倒数
        double sampleCurveDerivativeX(double t)
        {
          return (3.0 * ax * t + 2.0 * bx) * t + cx;
        }
        
        // Given an x value, find a parametric value it came from.
        double solveCurveX(double x, double epsilon)
        {
          double t0;
          double t1;
          double t2;
          double x2;
          double d2;
          int i;
          
          // First try a few iterations of Newton's method -- normally very fast.
            ///
          for (t2 = x, i = 0; i < 8; i++) {
              ///获取t2 时间x 周的坐标
            x2 = sampleCurveX(t2) - x;
              NSLog(@"%lf %lf  %lf",t2 , x ,x2);
            if (fabs (x2) < epsilon)
              return t2;
              NSLog(@"====%lf",x2);
              ///计算斜率
            d2 = sampleCurveDerivativeX(t2);
              NSLog(@"d2====%lf",d2);
    
            if (fabs(d2) < 1e-6)
              break;
              ///
            t2 = t2 - x2 / d2;
          }
           NSLog(@"error");
          // Fall back to the bisection method for reliability.
          t0 = 0.0;
          t1 = 1.0;
          t2 = x;
          
          if (t2 < t0)
            return t0;
          if (t2 > t1)
            return t1;
          
          while (t0 < t1) {
            x2 = sampleCurveX(t2);
            if (fabs(x2 - x) < epsilon)
              return t2;
            if (x > x2)
              t0 = t2;
            else
              t1 = t2;
            t2 = (t1 - t0) * .5 + t0;
          }
          
          // Failure.
          return t2;
        }
        
        double solve(double x, double epsilon)
        {
          return sampleCurveY(solveCurveX(x, epsilon));
        }
        
      private:
        double ax;
        double bx;
        double cx;
        double ay;
        double by;
        double cy;
      };
    }
    

    ios的三阶贝塞尔曲线

    苹果提供了关于三阶贝塞尔曲线的类CAMediaTimingFunction
    从这个类中我们读取到苹果规定的

    CA_EXTERN NSString * const kCAMediaTimingFunctionLinear
        CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    CA_EXTERN NSString * const kCAMediaTimingFunctionEaseIn
        CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    CA_EXTERN NSString * const kCAMediaTimingFunctionEaseOut
        CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    CA_EXTERN NSString * const kCAMediaTimingFunctionEaseInEaseOut
        CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    CA_EXTERN NSString * const kCAMediaTimingFunctionDefault
    

    样式的贝塞尔曲线的顶点值。

    - (void)getControlPointAtIndex:(size_t)idx values:(float[_Nonnull 2])ptr;
    

    该函数是获取顶点的api

    该类使用在layer动画中

    - (void)viewDidLoad {
        [super viewDidLoad];
    
    
        // 初始化layer
        CALayer *layer        = [CALayer layer];
        layer.frame           = CGRectMake(50, 50, 200, 2);
        layer.backgroundColor = [UIColor blackColor].CGColor;
        
        
        // 终点位置
        CGPoint endPosition = CGPointMake(layer.position.x, layer.position.y + 200);
        
        
        // 动画
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
        animation.fromValue         = [NSValue valueWithCGPoint:layer.position];
        animation.toValue           = [NSValue valueWithCGPoint:endPosition];
        animation.timingFunction    = [CAMediaTimingFunction functionWithControlPoints:0.20 :0.03 :0.13 :1.00];
        layer.position              = endPosition;
        animation.duration          = 1.f;
       
        // 添加动画
        [layer addAnimation:animation forKey:nil];
        // 添加layer
        [self.view.layer addSublayer:layer];
    }
    

    绘制贝塞尔曲线与时间的关系

    坐标系没有进行转换,坐标原点再左上角

    float  cx;
    float  bx ;
    float  ax;
    
    float   cy ;
    float   by ;
    float   ay ;
    @interface DrawPoint()
    {
    
    }
    
    
    @property (nonatomic,assign) CGPoint control1;
    @property (nonatomic,assign) CGPoint control2;
    
    
    @end
    
    @implementation DrawPoint
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            self.backgroundColor = [UIColor redColor];
            CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
            float point[2];
          for (int i =0;i<4;i++) {
                float point[2];
                [function getControlPointAtIndex:i values:point];
                self.control1 = CGPointMake(point[0], point[1]);
                NSLog(@"%@",NSStringFromCGPoint(self.control1));
            }
            [function getControlPointAtIndex:1 values:point];
            self.control1 = CGPointMake(point[0], point[1]);
            [function getControlPointAtIndex:2 values:point];
            self.control2 = CGPointMake(point[0], point[1]);
            cx = 3.0 * self.control1.x;
            bx = 3.0 * (self.control2.x - self.control1.x) - cx;
            ax = 1.0 - cx -bx;
            cy = 3.0 * self.control1.y;
            by = 3.0 * (self.control2.y - self.control1.y) - cy;
            ay = 1.0 - cy - by;
        }
        return self;
    }
    double sampleCurveX(double t)
    {
        // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
        return ((ax * t + bx) * t + cx) * t;
    }
    ///时间对应的点y
    double sampleCurveY(double t)
    {
        return ((ay * t + by) * t + cy) * t;
    }
    - (void)drawRect:(CGRect)rect {
        UIColor *color = [UIColor colorWithRed:0 green:0 blue:0.7 alpha:1];
        [color set];
        int n = 1000;
        for (int i=0; i<n; i++) {
            UIBezierPath* aPath = [UIBezierPath bezierPathWithRect:CGRectMake(sampleCurveX(i*1.0/n)*self.bounds.size.width, sampleCurveY(i*1.0/n)*self.bounds.size.height, 1, 1)]; // 2.创建图形相应的
            [aPath fill];
        }
    }
    
    
    
    /*
    // Only override drawRect: if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    
    */
    
    @end
    
    

    上面是测试代码

    kCAMediaTimingFunctionLinear

    image.png
    2018-09-18 16:40:56.085033+0800 draw[16389:15584186] {0, 0}
    2018-09-18 16:40:56.086905+0800 draw[16389:15584186] {0, 0}
    2018-09-18 16:40:56.087094+0800 draw[16389:15584186] {1, 1}
    2018-09-18 16:40:56.087281+0800 draw[16389:15584186] {1, 1}
    
    

    kCAMediaTimingFunctionEaseIn

    image.png
    2018-09-18 16:40:12.168905+0800 draw[16362:15583293] {0, 0}
    2018-09-18 16:40:12.169112+0800 draw[16362:15583293] {0.41999998688697815, 0}
    2018-09-18 16:40:12.169238+0800 draw[16362:15583293] {1, 1}
    2018-09-18 16:40:12.169362+0800 draw[16362:15583293] {1, 1}
    

    kCAMediaTimingFunctionEaseOut

    image.png
    2018-09-18 16:39:31.150050+0800 draw[16336:15582505] {0, 0}
    2018-09-18 16:39:31.150593+0800 draw[16336:15582505] {0, 0}
    2018-09-18 16:39:31.151359+0800 draw[16336:15582505] {0.57999998331069946, 1}
    2018-09-18 16:39:31.151518+0800 draw[16336:15582505] {1, 1}
    
    

    kCAMediaTimingFunctionEaseInEaseOut

    image.png
    2018-09-18 16:38:36.048302+0800 draw[16305:15581503] {0, 0}
    2018-09-18 16:38:36.048784+0800 draw[16305:15581503] {0.41999998688697815, 0}
    2018-09-18 16:38:36.049117+0800 draw[16305:15581503] {0.57999998331069946, 1}
    2018-09-18 16:38:36.049575+0800 draw[16305:15581503] {1, 1}
    

    kCAMediaTimingFunctionDefault

    image.png
    2018-09-18 16:38:05.253326+0800 draw[16284:15580826] {0, 0}
    2018-09-18 16:38:05.253511+0800 draw[16284:15580826] {0.25, 0.10000000149011612}
    2018-09-18 16:38:05.253643+0800 draw[16284:15580826] {0.25, 1}
    2018-09-18 16:38:05.253774+0800 draw[16284:15580826] {1, 1}
    
    

    自定义几个值看看

            CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithControlPoints:0.25 :0.5 :0.75 :0.5];
    
    image.png

    相关文章

      网友评论

          本文标题:贝赛尔曲线知识详解

          本文链接:https://www.haomeiwen.com/subject/fmnugftx.html