理解与运用贝塞尔曲线

作者: 秦明Qinmin | 来源:发表于2017-01-16 13:59 被阅读1798次

    1.曲线介绍

    贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。在计算机图形学中也是相当重要的参数曲线。

    2.公式介绍

    线性公式

    给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:


    线性公式.png

    当参数t变化时,其过程如下:


    线性.gif
    且其等同于线性插值。

    二次方公式

    二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:


    二次方公式.png

    当参数t变化时,其过程如下:


    二次.gif
    TrueType字型就运用了以贝兹样条组成的二次贝兹曲线。

    三次方公式

    P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。
    曲线的参数形式为:

    三次方公式.png

    当参数t变化时,其过程如下:


    三次.gif

    一般参数公式

    阶贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即:
    如上公式可如下递归表达: 用表示由点P0、P1、…、Pn所决定的贝兹曲线。
    用平常话来说,阶的贝兹曲线,即双阶贝兹曲线之间的插值。


    一般参数公式.png

    3.公式运用

    根据公式自己用线段代替曲线实现系统贝塞尔曲线效果

    实现效果:

    线.gif

    1.首先确定控制点,以及其它两点
    2.确定切分信息,在此我把[0,1]切分成了100份
    3.根据公式计算切分的每个点
    4.创建定时器每一定时间绘制特定点数
    5.利用系统函数创建贝塞尔曲线,与自己绘制的进行对比。

    //
    //  FormulaView.m
    //  Bezier
    //
    //  Created by qinmin on 2017/1/16.
    //  Copyright © 2017年 qinmin. All rights reserved.
    //
    
    #import "FormulaView.h"
    
    @interface FormulaView ()
    {
        CGFloat _t;
        CGFloat _deltaT;
        
        CGPoint _p1;
        CGPoint _p2;
        CGPoint _control;
        
        CGPoint *_pointArr;
        CADisplayLink   *_displayLink;
        
        int     _currentIndex;
    }
    @end
    
    @implementation FormulaView
    
    - (void)dealloc
    {
        if (_pointArr) {
            free(_pointArr);
            _pointArr = NULL;
        }
    }
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            [self setBackgroundColor:[UIColor whiteColor]];
            [self setupPoint];
            [self setupSliceInfo];
            [self createBezierPoint];
            [self setupTimer];
        }
        return self;
    }
    
    - (void)setupPoint
    {
        _p1 = CGPointMake(10, 100);
        _p2 = CGPointMake(400, 100);
        _control = CGPointMake(100, 800);
        
    }
    
    // 切分点信息
    - (void)setupSliceInfo
    {
        _t = 0.0;
        _deltaT = 0.01;
        _currentIndex = 0;
    }
    
    // 创建线段的切分点
    - (void)createBezierPoint
    {
        int count = 1.0/_deltaT;
        _pointArr = (CGPoint *)malloc(sizeof(CGPoint) * (count+1));
    
        // t的范围[0,1]
        for (int i = 0; i < count+1; i++) {
            float t = i * _deltaT;
            
            // 二次方计算公式
            float cx = (1-t)*(1-t)*_p1.x + 2*t*(1-t)*_control.x + t*t*_p2.x;
            float cy = (1-t)*(1-t)*_p1.y + 2*t*(1-t)*_control.y + t*t*_p2.y;
            _pointArr[i] = CGPointMake(cx, cy);
        }
    }
    
    - (void)setupTimer
    {
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerTick:)];
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    - (void)timerTick:(CADisplayLink *)displayLink
    {
        _currentIndex += 1;
        int count = 1.0/_deltaT;
        if (_currentIndex > count+1) {
            _currentIndex = 1;
        }
        [self setNeedsDisplay];
    }
    
    // 画图
    - (void)drawRect:(CGRect)rect
    {
        if (_pointArr == NULL) {
            return;
        }
        
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextSetLineWidth(ctx, 4);
        
        // 系统贝塞尔曲线
        [[UIColor blackColor] setStroke];
        CGContextMoveToPoint(ctx, _p1.x, _p1.y);
        CGContextAddQuadCurveToPoint(ctx, _control.x, _control.y, _p2.x, _p2.y);
        CGContextDrawPath(ctx, kCGPathStroke);
        
        // 线段代替曲线
        [[UIColor redColor] setStroke];
        CGContextMoveToPoint(ctx, _pointArr[0].x, _pointArr[0].y);
        for (int i = 1; i < _currentIndex; i++) {
            CGContextAddLineToPoint(ctx, _pointArr[i].x, _pointArr[i].y);
        }
        CGContextDrawPath(ctx, kCGPathStroke);
    }
    @end
    

    这里最重要的是根据公式,计算每一个点的信息


    二次方公式.png

    实现类似弹簧效果

    实现效果:

    物理.gif

    1.首先确定除控制点以外的其它两点,也就是绳子上两球的球心
    2.设置整个场景球的重力,以及绳子的弹力
    3.计算贝塞尔曲线的控制点,由于控制点并不是最终和球接触的那个点,这个点因该是t=0.5时曲线上的点
    4.创建定时器每一定时间更新小球位置
    5.检测小球碰撞到绳子的时候,给小球施加弹力,并且让绳子与小球共同运动。
    6.小球向上运动与绳子分离的时候,撤掉弹力的作用。

    //
    //  MyView.h
    //  UIkit
    //
    //  Created by qinmin on 2017/1/14.
    //  Copyright © 2017年 qinmin. All rights reserved.
    //
    
    #import <UIKit/UIKit.h>
    
    @interface Ball : NSObject
    @property (nonatomic, assign) float speed;
    @property (nonatomic, assign) float accelerate;
    @property (nonatomic, assign) CGPoint position;
    @property (nonatomic, assign) CGSize size;
    @end
    
    @interface Rope : NSObject
    @property (nonatomic, assign) float k;
    @property (nonatomic, assign) float x;
    @property (nonatomic, assign) CGPoint position;
    @property (nonatomic, assign) CGPoint control;
    @property (nonatomic, assign) CGPoint start;
    @property (nonatomic, assign) CGPoint end;
    @end
    
    @interface BallView : UIView
    - (void)stop;
    - (void)start;
    @end
    
    //
    //  MyView.m
    //  UIkit
    //
    //  Created by qinmin on 2017/1/14.
    //  Copyright © 2017年 qinmin. All rights reserved.
    //
    
    #import "BallView.h"
    
    @implementation Ball
    
    @end
    
    @implementation Rope
    
    @end
    
    
    @interface BallView ()
    {
        CADisplayLink   *_displayLink;
        Ball            *_ball;
        Rope            *_rope;
    }
    @end
    
    @implementation BallView
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            [self setBackgroundColor:[UIColor whiteColor]];
            [self setupTimer];
            [self setupBall];
            [self setupRope];
        }
        return self;
    }
    
    - (void)setupTimer
    {
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerTick:)];
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    - (void)setupBall
    {
        _ball = [[Ball alloc] init];
        _ball.accelerate = 300; //加速度
        _ball.speed = 0; //速度
        _ball.size = CGSizeMake(30, 30); //球大小
        _ball.position = CGPointMake(195, 100); //球的初始位置
    }
    
    - (void)setupRope
    {
        _rope = [[Rope alloc] init];
        _rope.position = CGPointMake(200, 420); //绳子中间点的位置
        _rope.start = CGPointMake(150, 420); //绳子起点
        _rope.end = CGPointMake(270, 420); //绳子终点
        _rope.control = CGPointMake(210, 420); //绳子的控制点
        _rope.x = 0; //绳子偏移量
        _rope.k = 20; //劲度系数
    }
    
    - (void)timerTick:(CADisplayLink *)displayLink
    {
        //NSLog(@"%f", displayLink.duration);
        CGRect ballRect = CGRectMake(_ball.position.x, _ball.position.y+1, _ball.size.width, _ball.size.height);
        
        BOOL ropeMove = NO;
        //球与绳子碰撞检测
        if (CGRectContainsPoint(ballRect, _rope.position)) {
            ropeMove = YES;
            // delta x
            // f = kx
            _rope.x = _rope.position.y - _rope.end.y;
        }else {
            //球向上离开绳子
            _rope.x = 0;
        }
        
        _ball.speed += (_ball.accelerate - _rope.k * _rope.x) * displayLink.duration;
        float s = _ball.speed * displayLink.duration;
        _ball.position = CGPointMake(_ball.position.x, _ball.position.y + s);
        
        // 球与绳子碰撞检测
        if (ropeMove) {
            float x = _ball.position.x + _ball.size.width/2;
            float y = _ball.position.y + _ball.size.height;
            
            // 中间点 公式的t为0.5
            float t = 0.5;
            
            // 根据公式逆推出控制点
            float cx = (x - (1-t)*(1-t)*_rope.start.x - t*t*_rope.end.x)/(2*t*(1-t));
            float cy = (y - (1-t)*(1-t)*_rope.start.y - t*t*_rope.end.y)/(2*t*(1-t));
            
            _rope.position = CGPointMake(x, y);
            _rope.control = CGPointMake(cx, cy);
            
            // fix 小球未与绳子接触,小球的位置高于绳子的位置
            if (y <= _rope.end.y) {
                _rope.position = CGPointMake((_rope.end.x+_rope.start.x)/2, _rope.start.y);
                _rope.control = _rope.position;
            }
        }
        
        //fix position
        if (_ball.position.y < 100) {
            _ball.position = CGPointMake(_ball.position.x, 100);
        }
        
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect
    {
        // 画两个固定点
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGMutablePathRef path = CGPathCreateMutable();
    
        CGPathAddPath(path, NULL, [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_rope.start.x-_ball.size.width/2, _rope.start.y-_ball.size.height/2, _ball.size.width,  _ball.size.height)].CGPath);
        CGPathAddPath(path, NULL, [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_rope.end.x-_ball.size.width/2, _rope.end.y-_ball.size.height/2, _ball.size.width, _ball.size.height)].CGPath);
        CGRect ballRect = CGRectMake(_ball.position.x, _ball.position.y, _ball.size.width, _ball.size.height);
        CGPathAddPath(path, NULL, [UIBezierPath bezierPathWithOvalInRect:ballRect].CGPath);
    
        CGContextAddPath(ctx, path);
        CGContextDrawPath(ctx, kCGPathFillStroke);
        CGPathRelease(path);
        
        // 画绳子的贝塞尔曲线
        CGContextMoveToPoint(ctx, _rope.start.x, _rope.start.y);
        CGContextAddQuadCurveToPoint(ctx, _rope.control.x, _rope.control.y, _rope.end.x, _rope.end.y);
        
        // 画最高点标记线
        CGContextMoveToPoint(ctx, _ball.position.x-30, 100);
        CGContextAddLineToPoint(ctx, _ball.position.x +50, 100);
        
        CGContextDrawPath(ctx, kCGPathStroke);
    }
    
    - (void)stop
    {
        [_displayLink invalidate];
        _displayLink = nil;
    }
    
    - (void)start
    {
        [self setupTimer];
    }
    
    @end
    

    这里最重要的是根据公式,t=0.5 计算出控制点的位置

    二次方公式.png

    实现类似小船在波浪行驶的效果

    效果:

    波浪.gif

    1.首先用贝塞尔曲线作为波浪,由于需要实现连续不断的效果。所以至少需要两个曲线,当然可以加入更多波形,在此只弄了简单的两个波形。
    2.设置波形的水平速度,然后连续运。当波浪离开右侧屏幕上,让其重新回到左侧屏幕。
    3.计算t值,主要是根据当前小球在贝塞尔曲线的比例,比例也就是我们需要的t值。
    4.根据三次方公式计算出波形对应y值,也就是小球的y值。

    //
    //  ShipView.m
    //  Bezier
    //
    //  Created by qinmin on 2017/1/16.
    //  Copyright © 2017年 qinmin. All rights reserved.
    //
    
    #import "ShipView.h"
    
    @implementation Ship
    
    @end
    
    @implementation Wave
    
    @end
    
    
    @interface ShipView ()
    {
        CADisplayLink   *_displayLink;
        Wave            *_wave1;
        Wave            *_wave2;
        Ship            *_ship;
    }
    @end
    
    @implementation ShipView
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            [self setBackgroundColor:[UIColor whiteColor]];
            [self setupTimer];
            
            [self setupWave];
            [self setupShip];
        }
        return self;
    }
    
    - (void)setupTimer
    {
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerTick:)];
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    - (void)setupWave
    {
        _wave1 = [[Wave alloc] init];
        _wave1.cp1 = CGPointMake(200, 150);
        _wave1.cp2 = CGPointMake(300, 280);
        _wave1.p1 = CGPointMake(0, 200);
        _wave1.p2 = CGPointMake(420, 200);
        _wave1.speed = 320;
        
        _wave2 = [[Wave alloc] init];
        _wave2.cp1 = CGPointMake(200-420+1, 150);
        _wave2.cp2 = CGPointMake(300-420+1, 250);
        _wave2.p1 = CGPointMake(0-420+1, 200);
        _wave2.p2 = CGPointMake(420-420+1, 200);
        _wave2.speed = _wave1.speed;
    }
    
    - (void)setupShip
    {
        _ship = [[Ship alloc] init];
        _ship.size = CGSizeMake(30, 30);
        _ship.position = CGPointMake(210, 250);
    }
    
    - (void)addDeltaX:(CGFloat)x forWave:(Wave *)wave
    {
        // fix 无线循环
        if (wave.p1.x + x >= 420) {
            x = - 420 * 2 + 8;
        }
        
        CGPoint cp1 = wave.cp1;
        cp1.x += x;
        wave.cp1 = cp1;
        
        CGPoint cp2 = wave.cp2;
        cp2.x += x;
        wave.cp2 = cp2;
        
        CGPoint p1 = wave.p1;
        p1.x += x;
        wave.p1 = p1;
        
        CGPoint p2 = wave.p2;
        p2.x += x;
        wave.p2 = p2;
    }
    
    - (void)timerTick:(CADisplayLink *)displayLink
    {
        CGFloat delta = displayLink.duration * _wave1.speed;
        [self addDeltaX:delta forWave:_wave1];
        [self addDeltaX:delta forWave:_wave2];
        
        Wave *currentWave;
        if (_wave1.p1.x <= _ship.position.x && _ship.position.x <= _wave1.p2.x) {
            currentWave = _wave1;
        }else {
            currentWave = _wave2;
        }
        
        // 计算t值
        float t = (_ship.position.x - currentWave.p1.x)/(currentWave.p2.x - currentWave.p1.x);
        
        // 由t值,计算出y值
        float y = currentWave.p1.y*pow(1-t, 3) + 3*currentWave.cp1.y*t*pow(1-t, 2) +3*currentWave.cp2.y*t*t*(1-t)+currentWave.p2.y*pow(t, 3);
        CGPoint position = _ship.position;
        position.y = y-_ship.size.height-2;
        _ship.position = position;
        
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect
    {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        
        CGMutablePathRef path = CGPathCreateMutable();
        
        CGPathAddPath(path, NULL, [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_ship.position.x, _ship.position.y, _ship.size.width,  _ship.size.height)].CGPath);
        
        CGContextAddPath(ctx, path);
        CGContextDrawPath(ctx, kCGPathFillStroke);
        CGPathRelease(path);
        
        
        CGContextMoveToPoint(ctx, _wave1.p1.x, _wave1.p1.y);
        CGContextAddCurveToPoint(ctx, _wave1.cp1.x, _wave1.cp1.y, _wave1.cp2.x, _wave1.cp2.y, _wave1.p2.x, _wave1.p2.y);
        CGContextDrawPath(ctx, kCGPathStroke);
        
        CGContextMoveToPoint(ctx, _wave2.p1.x, _wave2.p1.y);
        CGContextAddCurveToPoint(ctx, _wave2.cp1.x, _wave2.cp1.y, _wave2.cp2.x, _wave2.cp2.y, _wave2.p2.x, _wave2.p2.y);
        CGContextDrawPath(ctx, kCGPathStroke);
    }
    
    @end
    

    实现类似橡皮泥效果

    效果:


    橡皮泥效果.gif

    这里我就不过多解释代码,代码还值得优化

    //
    //  DotView.m
    //  Bezier
    //
    //  Created by qinmin on 2017/1/15.
    //  Copyright © 2017年 qinmin. All rights reserved.
    //
    
    #import "DotView.h"
    
    @implementation Dot
    
    @end
    
    @interface DotView ()
    {
        Dot     *_startDot;
        Dot     *_endDot;
        
        CGPoint _controlPoint;
        CGFloat _lastDistance;
    }
    @end
    
    @implementation DotView
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            [self setBackgroundColor:[UIColor whiteColor]];
            [self setupDots];
            [self setupGesture];
            [self setupControlPoint];
        }
        return self;
    }
    
    - (void)setupDots
    {
        _startDot = [[Dot alloc] init];
        _startDot.position = CGPointMake(100, 200);
        _startDot.size = CGSizeMake(50, 50);
        
        _endDot = [[Dot alloc] init];
        _endDot.position = CGPointMake(100, 290);
        _endDot.size = CGSizeMake(50, 50);
    }
    
    - (void)setupGesture
    {
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
        [self addGestureRecognizer:panGesture];
    }
    
    - (void)setupControlPoint
    {
        CGFloat cpx = (_startDot.position.x + _endDot.position.x)/2;
        CGFloat cpy = 0.5;
        _controlPoint = CGPointMake(cpx, cpy);
    }
    
    - (void)handleGesture:(UIPanGestureRecognizer *)gesture
    {
        CGPoint offset = [gesture locationInView:self];
        //CGPoint velocity = [gesture translationInView:self];
        //NSLog(@"%@", NSStringFromCGPoint(velocity));
        
        CGPoint endPos = _endDot.position;
        endPos.x = offset.x;
        endPos.y = offset.y;
        _endDot.position = endPos;
        
        float distance = sqrtf(pow(_startDot.position.x - _endDot.position.x, 2) + pow(_startDot.position.y - _endDot.position.y, 2));
        
        CGSize size;
        if (distance - _lastDistance < 0.00000) {
            size = _startDot.size;
            size.width = size.width + 0.001 * distance;
            size.height = size.width;
            NSLog(@"%@", NSStringFromCGSize(size));
            if (size.width > 50 ) {
                size.width = size.height = 30;
            }
        }else {
            size = _startDot.size;
            size.width = size.width - 0.001 * distance;
            size.height = size.width;
            NSLog(@"%@", NSStringFromCGSize(size));
            if (size.width < 15 ) {
                size.width = size.height = 15;
            }
        }
        _startDot.size = _endDot.size = size;
        _lastDistance = distance;
        
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect
    {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGMutablePathRef path = CGPathCreateMutable();
        
        CGPathAddPath(path, NULL, [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_startDot.position.x-_startDot.size.width/2, _startDot.position.y-_startDot.size.height/2, _startDot.size.width,  _startDot.size.height)].CGPath);
        
        CGPathAddPath(path, NULL, [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_endDot.position.x-_endDot.size.width/2, _endDot.position.y-_endDot.size.height/2, _endDot.size.width,  _endDot.size.height)].CGPath);
        
        CGContextAddPath(ctx, path);
        CGContextDrawPath(ctx, kCGPathFillStroke);
        CGPathRelease(path);
        
        // 求动点相对于x轴的偏移角
        // a = (1,0), b = (end.x-start.x, end.y-start.y), cost=a*b/(|a||b|)
        float cost = (_endDot.position.x-_startDot.position.x)/sqrtf(pow(_endDot.position.x-_startDot.position.x, 2) + pow(_endDot.position.y-_startDot.position.y, 2));
        float t = acosf(cost);
        float sint = sin(t);
        
        // 修正动点在定点上方时候的角度问题
        int i = 1;
        if (_endDot.position.y < _startDot.position.y) {
            cost = cos(-t);
            sint = sin(-t);
            i = -1;
        }
        
        float deltax = _startDot.size.width/2 * sint;
        float deltay = _startDot.size.height/2 * cost;
        
        float cpx = (_startDot.position.x+_endDot.position.x)/2;
        float cpy = (_startDot.position.y+_endDot.position.y)/2 - _controlPoint.y*cost;
        float cpy1 = (_startDot.position.y+_endDot.position.y)/2 + _controlPoint.y*cost;
        
        // 画四边形
        CGContextSetLineWidth(ctx, 0);
        CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
        CGContextMoveToPoint(ctx, _startDot.position.x+deltax-1*i, _startDot.position.y-deltay);
        CGContextAddLineToPoint(ctx, _startDot.position.x-deltax+1*i, _startDot.position.y+deltay);
        CGContextAddLineToPoint(ctx, _endDot.position.x+deltax-1*i, _endDot.position.y-deltay);
        CGContextMoveToPoint(ctx, _endDot.position.x+deltax-1*i, _endDot.position.y-deltay);
        CGContextAddLineToPoint(ctx, _endDot.position.x-deltax+1*i, _endDot.position.y+deltay);
        CGContextAddLineToPoint(ctx, _startDot.position.x-deltax+1*i, _startDot.position.y+deltay);
        CGContextDrawPath(ctx, kCGPathEOFillStroke);
        
        // 画贝塞尔曲线
        [[UIColor whiteColor] setFill];
        CGContextSetLineWidth(ctx, 0);
        CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
        CGContextMoveToPoint(ctx, _startDot.position.x+deltax+1*i, _startDot.position.y-deltay);
        CGContextAddQuadCurveToPoint(ctx, cpx, cpy, _endDot.position.x+deltax+1*i, _endDot.position.y-deltay);
        CGContextMoveToPoint(ctx, _startDot.position.x-deltax-1*i, _startDot.position.y+deltay);
        CGContextAddQuadCurveToPoint(ctx, cpx, cpy1, _endDot.position.x-deltax-1*i, _endDot.position.y+deltay);
        CGContextDrawPath(ctx, kCGPathEOFillStroke);
      
    }
    @end
    

    最后

    这篇文章讲述了贝塞尔曲线的公式定义,以及用它来实现一些特殊的效果。如果能理解好贝塞尔曲线的原理,对动画效果开发是很有帮助的。

    更深入的理解方程推到过程请参照:
    1.如何得到贝塞尔曲线的曲线长度和 t 的近似关系
    2.贝塞尔曲线扫盲

    目前代码已经放到github上面,传送门:Bezier

    相关文章

      网友评论

        本文标题:理解与运用贝塞尔曲线

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