接上一篇Blog,这里用贝塞尔曲线来平滑多个点。
和样条插值不同,在多个点上作贝塞尔曲线的时候,曲线只穿过首尾两个点,中间的点都是作为控制点。
移动控制点,曲线也随之形变,可以造成一种拉扯的效果。在各种作图工具中,经常使用贝塞尔曲线来画曲线。一般的操作都是先画一条线段,然后可以通过拖动一个控制点来调整线段的弯曲程度。
作多点贝塞尔曲线只需要一个公式。所有的点的X值,被归一化到[0,1]区间内。
具体理论,可以参考这个页面Bézier curves。89年创建的,可有年头了。
这里还是贴代码吧。
首先需要得到X区间的总长度。
CGPoint startPt = [[_points objectAtIndex:0] CGPointValue];
CGPoint endPt = [[_points objectAtIndex:(self.pointCount - 1)] CGPointValue];
float amount = endPt.x - startPt.x;
然后就是曲线方程了,这个比样条插值要简单不少。
rank是指总的阶数,也就是实际的点数。这个函数表示n个点的贝塞尔曲线在x处的值。
这里的ux属于区间[0,1]
float (^bezierSpline)(int rank, float ux) = ^(int rank, float ux) {
float p = 0.0f;
for (int i = 0; i < rank; i++)
{
CGPoint pt = [[_points objectAtIndex:i] CGPointValue];
p += pt.y * powf((1 - ux), (rank - i - 1)) * powf(ux, i) * (factorial(rank - 1)
/ (factorial(i) * factorial(rank - i - 1)));
}
return p;
};
下面很容易了,画图的时候步长为1,求得ux之后,带入方程得到点的y值,画曲线。
[path moveToPoint:startPt];
for (float curX = startPt.x; (curX - endPt.x) < 1e-5; curX += 1.0f)
{
float u = (curX - startPt.x) / amount;
[path addLineToPoint:
CGPointMake(curX, bezierSpline(self.pointCount, u))];
}
CGContextSetLineWidth(context, 1.0f);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextAddPath(context, path.CGPath);
CGContextStrokePath(context);
}
和三次样条相比,由于不经过中间点,丢失细节比较多,平滑度更好。
平滑前
三次样条
贝塞尔曲线
示例代码: Sample-CurveFit
和上个例子放在一个repo里。
参考: Wiki
微策略作ios开发是不是大都数是用于统计图标画图画曲线之类的?哈哈
呵呵,这是基础,早就做稳定了。主要还是做App本身的逻辑。
里面有点小问题,并不能通过x算出t 也就是ux,而应该使用ux算出x和y,不然会和期望的曲线不一致。