绘制平滑的曲线 – 需要的方法
如何在移动时在iOS绘图应用程序中平滑一组点? 我试过UIBezierpaths,但我得到的是交错的锯齿端,当我只是移动点1,2,3,4 – 2,3,4,5。 我听说了样条曲线和所有其他types。 我对iPhone编程颇为陌生,不明白如何在我的石英绘图应用程序中进行编程。 一个可靠的例子将不胜感激,我已经花了几个星期运行在圈子里,我永远也找不到任何iOS代码的任务。 大多数post只是链接到一个java模拟或维基百科关于曲线拟合的页面,这对我什么都不做。 另外我不想切换到OpenGL ES。 我希望有人能最终提供代码来回答这个循环的问题。
这是我在UIBezierPath的交叉点的代码///
更新了下面的答案
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] #define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] - (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity { NSMutableArray *points = [(NSMutableArray*)[self pointsOrdered] mutableCopy]; if (points.count < 4) return [self bezierPath]; // Add control points to make the math make sense [points insertObject:[points objectAtIndex:0] atIndex:0]; [points addObject:[points lastObject]]; UIBezierPath *smoothedPath = [self bezierPath]; [smoothedPath removeAllPoints]; [smoothedPath moveToPoint:POINT(0)]; for (NSUInteger index = 1; index < points.count - 2; index++) { CGPoint p0 = POINT(index - 1); CGPoint p1 = POINT(index); CGPoint p2 = POINT(index + 1); CGPoint p3 = POINT(index + 2); // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines for (int i = 1; i < granularity; i++) { float t = (float) i * (1.0f / (float) granularity); float tt = t * t; float ttt = tt * t; CGPoint pi; // intermediate point pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt); pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt); [smoothedPath addLineToPoint:pi]; } // Now add p2 [smoothedPath addLineToPoint:p2]; } // finish by adding the last point [smoothedPath addLineToPoint:POINT(points.count - 1)]; return smoothedPath; } - (PVPoint *)pointAppendingCGPoint:(CGPoint)CGPoint { PVPoint *newPoint = [[PVPoint alloc] initInsertingIntoManagedObjectContext:[self managedObjectContext]]; [newPoint setCGPoint:CGPoint]; [newPoint setOrder:[NSNumber numberWithUnsignedInteger:[[self points] count]]]; [[self mutableSetValueForKey:@"points"] addObject:newPoint]; [(NSMutableArray *)[self pointsOrdered] addObject:newPoint]; [[self bezierPath] addLineToPoint:CGPoint]; return [newPoint autorelease]; if ([self bezierPath] && [pointsOrdered count] > 3) { PVPoint *control1 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 2]; PVPoint *control2 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 1]; [bezierPath moveToPoint:[[pointsOrdered objectAtIndex:[pointsOrdered count] - 3] CGPoint]]; [[self bezierPath] addCurveToPoint:CGPoint controlPoint1:[control1 CGPoint] controlPoint2:[control2 CGPoint]]; } } - (BOOL)isComplete { return [[self points] count] > 1; } - (UIBezierPath *)bezierPath { if (!bezierPath) { bezierPath = [UIBezierPath bezierPath]; for (NSUInteger p = 0; p < [[self points] count]; p++) { if (!p) [bezierPath moveToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]]; else [bezierPath addLineToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]]; } [bezierPath retain]; } return bezierPath; } - (CGPathRef)CGPath { return [[self bezierPath] CGPath]; }
我刚刚在一个正在进行的项目中实现了类似的东西。 我的解决scheme是使用Catmull-Rom样条而不是使用贝塞尔曲线。 这些提供了一个非常平滑的曲线,通过一个点,而不是一个贝塞尔样条“四周”点。
// Based on code from Erica Sadun #import "UIBezierPath+Smoothing.h" void getPointsFromBezier(void *info, const CGPathElement *element); NSArray *pointsFromBezierPath(UIBezierPath *bpath); #define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] #define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] @implementation UIBezierPath (Smoothing) // Get points from Bezier Curve void getPointsFromBezier(void *info, const CGPathElement *element) { NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info; // Retrieve the path element type and its points CGPathElementType type = element->type; CGPoint *points = element->points; // Add the points if they're available (per type) if (type != kCGPathElementCloseSubpath) { [bezierPoints addObject:VALUE(0)]; if ((type != kCGPathElementAddLineToPoint) && (type != kCGPathElementMoveToPoint)) [bezierPoints addObject:VALUE(1)]; } if (type == kCGPathElementAddCurveToPoint) [bezierPoints addObject:VALUE(2)]; } NSArray *pointsFromBezierPath(UIBezierPath *bpath) { NSMutableArray *points = [NSMutableArray array]; CGPathApply(bpath.CGPath, (__bridge void *)points, getPointsFromBezier); return points; } - (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity; { NSMutableArray *points = [pointsFromBezierPath(self) mutableCopy]; if (points.count < 4) return [self copy]; // Add control points to make the math make sense [points insertObject:[points objectAtIndex:0] atIndex:0]; [points addObject:[points lastObject]]; UIBezierPath *smoothedPath = [self copy]; [smoothedPath removeAllPoints]; [smoothedPath moveToPoint:POINT(0)]; for (NSUInteger index = 1; index < points.count - 2; index++) { CGPoint p0 = POINT(index - 1); CGPoint p1 = POINT(index); CGPoint p2 = POINT(index + 1); CGPoint p3 = POINT(index + 2); // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines for (int i = 1; i < granularity; i++) { float t = (float) i * (1.0f / (float) granularity); float tt = t * t; float ttt = tt * t; CGPoint pi; // intermediate point pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt); pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt); [smoothedPath addLineToPoint:pi]; } // Now add p2 [smoothedPath addLineToPoint:p2]; } // finish by adding the last point [smoothedPath addLineToPoint:POINT(points.count - 1)]; return smoothedPath; } @end
原来的Catmull-Rom实现是基于Erica Sadun的一些代码,在她的一本书中,我略微修改了它以允许一个完整的平滑曲线。 这是作为UIBezierPath上的一个类别实施,并为我工作得很好。
使两条贝塞尔曲线平滑连接的关键在于曲线上的相关控制点和起点/终点必须是共线的。 把控制点和端点想象成一条与端点曲线相切的线。 如果一条曲线从另一条曲线的同一点开始,如果它们在该点具有相同的切线,曲线将是平滑的。 这里有一些代码来说明:
- (void)drawRect:(CGRect)rect { #define commonY 117 CGPoint point1 = CGPointMake(20, 20); CGPoint point2 = CGPointMake(100, commonY); CGPoint point3 = CGPointMake(200, 50); CGPoint controlPoint1 = CGPointMake(50, 60); CGPoint controlPoint2 = CGPointMake(20, commonY); CGPoint controlPoint3 = CGPointMake(200, commonY); CGPoint controlPoint4 = CGPointMake(250, 75); UIBezierPath *path1 = [UIBezierPath bezierPath]; UIBezierPath *path2 = [UIBezierPath bezierPath]; [path1 setLineWidth:3.0]; [path1 moveToPoint:point1]; [path1 addCurveToPoint:point2 controlPoint1:controlPoint1 controlPoint2:controlPoint2]; [[UIColor blueColor] set]; [path1 stroke]; [path2 setLineWidth:3.0]; [path2 moveToPoint:point2]; [path2 addCurveToPoint:point3 controlPoint1:controlPoint3 controlPoint2:controlPoint4]; [[UIColor orangeColor] set]; [path2 stroke]; }
请注意, path1
在点2处结束, commonY
2在点2处开始,并且控制点2和3与点2共享相同的Y值commonY
。 您可以随意更改代码中的任何值; 只要这三点都落在一条线上,这两条路就能顺利join。 (在上面的代码中,这条线是y = commonY
,线不必平行于X轴,只是看到这些点是共线的。)
这是上面的代码所绘制的图像:
看完你的代码之后,你的曲线锯齿的原因是你正在把控制点想成曲线上的点。 在贝塞尔曲线中,控制点通常不在曲线上。 由于你从曲线上取得控制点,控制点和交点不是共线的,因此path并不平滑。
@Rakesh是绝对正确的 – 如果你只是想要一条曲线,你不需要使用Catmull-Romalgorithm。 他build议的联系确实是这样的。 所以这里除了他的答案。
下面的代码不使用Catmull-Romalgorithm和粒度,但绘制了一条四条曲线(控制点是为您计算的)。 这实际上就是Rakesh提出的ios写意画教程所做的,但是在一个独立的方法中,你可以放在任何地方(或者在UIBezierpath类别中),并得到一个四开的曲线样条。
你需要有一个包含在NSValue
的CGPoint
数组
+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; CGPoint p1 = [value CGPointValue]; [path moveToPoint:p1]; if (points.count == 2) { value = points[1]; CGPoint p2 = [value CGPointValue]; [path addLineToPoint:p2]; return path; } for (NSUInteger i = 1; i < points.count; i++) { value = points[i]; CGPoint p2 = [value CGPointValue]; CGPoint midPoint = midPointForPoints(p1, p2); [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)]; [path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)]; p1 = p2; } return path; } static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) { return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); } static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) { CGPoint controlPoint = midPointForPoints(p1, p2); CGFloat diffY = abs(p2.y - controlPoint.y); if (p1.y < p2.y) controlPoint.y += diffY; else if (p1.y > p2.y) controlPoint.y -= diffY; return controlPoint; }
结果如下:
在这里有一些很好的答案,虽然我认为他们要么退出(user1244109的答案只支持水平切线,对通用曲线没有用),或过于复杂(抱歉Catmull-Rom粉丝)。
我使用Quad bezier曲线以更简单的方式实现了这一点。 这些需要一个起点,一个终点和一个控制点。 自然而然的做法可能是使用触点作为开始和结束点。 不要这样做! 没有合适的控制点可供使用。 相反,尝试这个想法:使用触摸点作为控制点, 中点作为开始/结束点。 你保证有这样的切线,代码是愚蠢的简单。 algorithm如下:
- “接触”点是path的开始,并将商店
location
存储在prevPoint
。 - 对于每个拖动的位置,计算
midPoint
,即currentPoint
和prevPoint
之间的点。- 如果这是第一个拖动的位置,请将
currentPoint
添加为线段。 - 对于将来的所有点,添加一个终止于
midPoint
的四边形曲线,并使用prevPoint
作为控制点 。 这将创build一个从前一点到当前点轻微曲线的段。
- 如果这是第一个拖动的位置,请将
- 将
currentPoint
存储在prevPoint
,并重复#2直到拖动结束。 - 添加最后一点作为另一个直线段,完成path。
这导致非常好看的曲线,因为使用中点确保曲线在终点处是平滑的切线(参见附图)。
Swift代码如下所示:
var bezierPath = UIBezierPath() var prevPoint: CGPoint? var isFirst = true override func touchesBegan(touchesSet: Set<UITouch>, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) bezierPath.removeAllPoints() bezierPath.moveToPoint(location) prevPoint = location } override func touchesMoved(touchesSet: Set<UITouch>, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) if let prevPoint = prevPoint { let midPoint = CGPoint( x: (location.x + prevPoint.x) / 2, y: (location.y + prevPoint.y) / 2, ) if isFirst { bezierPath.addLineToPoint(midPoint) else { bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint) } isFirst = false } prevPoint = location } override func touchesEnded(touchesSet: Set<UITouch>, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) bezierPath.addLineToPoint(location) }
或者,如果你有一系列的点,并且想要一次构buildUIBezierPath
:
var points: [CGPoint] = [...] var bezierPath = UIBezierPath() var prevPoint: CGPoint? var isFirst = true // obv, there are lots of ways of doing this. let's // please refrain from yak shaving in the comments for point in points { if let prevPoint = prevPoint { let midPoint = CGPoint( x: (point.x + prevPoint.x) / 2, y: (point.y + prevPoint.y) / 2, ) if isFirst { bezierPath.addLineToPoint(midPoint) } else { bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint) } isFirst = false } else { bezierPath.moveToPoint(point) } prevPoint = point } if let prevPoint = prevPoint { bezierPath.addLineToPoint(prevPoint) }
这里是我的笔记:
在捕获点上应用任何algorithm之前,我们需要观察一些事情。
- 通常UIKit不会给出等距离的点数。
- 我们需要计算两个CG点之间的中间点[用触摸移动方法捕捉到的点]
现在要走平稳路线,有很多方法。
有时我们可以通过应用二次多项式或三次多项式或catmullRomSplinealgorithm来实现
- (float)findDistance:(CGPoint)point lineA:(CGPoint)lineA lineB:(CGPoint)lineB { CGPoint v1 = CGPointMake(lineB.x - lineA.x, lineB.y - lineA.y); CGPoint v2 = CGPointMake(point.x - lineA.x, point.y - lineA.y); float lenV1 = sqrt(v1.x * v1.x + v1.y * v1.y); float lenV2 = sqrt(v2.x * v2.x + v2.y * v2.y); float angle = acos((v1.x * v2.x + v1.y * v2.y) / (lenV1 * lenV2)); return sin(angle) * lenV2; } - (NSArray *)douglasPeucker:(NSArray *)points epsilon:(float)epsilon { int count = [points count]; if(count < 3) { return points; } //Find the point with the maximum distance float dmax = 0; int index = 0; for(int i = 1; i < count - 1; i++) { CGPoint point = [[points objectAtIndex:i] CGPointValue]; CGPoint lineA = [[points objectAtIndex:0] CGPointValue]; CGPoint lineB = [[points objectAtIndex:count - 1] CGPointValue]; float d = [self findDistance:point lineA:lineA lineB:lineB]; if(d > dmax) { index = i; dmax = d; } } //If max distance is greater than epsilon, recursively simplify NSArray *resultList; if(dmax > epsilon) { NSArray *recResults1 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(0, index + 1)] epsilon:epsilon]; NSArray *recResults2 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(index, count - index)] epsilon:epsilon]; NSMutableArray *tmpList = [NSMutableArray arrayWithArray:recResults1]; [tmpList removeLastObject]; [tmpList addObjectsFromArray:recResults2]; resultList = tmpList; } else { resultList = [NSArray arrayWithObjects:[points objectAtIndex:0], [points objectAtIndex:count - 1],nil]; } return resultList; } - (NSArray *)catmullRomSplineAlgorithmOnPoints:(NSArray *)points segments:(int)segments { int count = [points count]; if(count < 4) { return points; } float b[segments][4]; { // precompute interpolation parameters float t = 0.0f; float dt = 1.0f/(float)segments; for (int i = 0; i < segments; i++, t+=dt) { float tt = t*t; float ttt = tt * t; b[i][0] = 0.5f * (-ttt + 2.0f*tt - t); b[i][1] = 0.5f * (3.0f*ttt -5.0f*tt +2.0f); b[i][2] = 0.5f * (-3.0f*ttt + 4.0f*tt + t); b[i][3] = 0.5f * (ttt - tt); } } NSMutableArray *resultArray = [NSMutableArray array]; { int i = 0; // first control point [resultArray addObject:[points objectAtIndex:0]]; for (int j = 1; j < segments; j++) { CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue]; float px = (b[j][0]+b[j][1])*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x; float py = (b[j][0]+b[j][1])*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y; [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; } } for (int i = 1; i < count-2; i++) { // the first interpolated point is always the original control point [resultArray addObject:[points objectAtIndex:i]]; for (int j = 1; j < segments; j++) { CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue]; CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue]; float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x; float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y; [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; } } { int i = count-2; // second to last control point [resultArray addObject:[points objectAtIndex:i]]; for (int j = 1; j < segments; j++) { CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue]; CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + (b[j][2]+b[j][3])*pointIp1.x; float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + (b[j][2]+b[j][3])*pointIp1.y; [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; } } // the very last interpolated point is the last control point [resultArray addObject:[points objectAtIndex:(count - 1)]]; return resultArray; }
为了实现这一点,我们需要使用这种方法。 BezierSpline代码用C#生成一个贝塞尔曲线的控制点数组。 我把这个代码转换成了Objective C,对我来说它非常出色。
将C#中的代码转换为Objective C,即使你不知道C#,也要逐行理解C#代码,你一定知道C ++ / Java吗?
转换时:
-
在这里用CGPointreplacePoint结构体。
-
用NSMutableArrayreplacePoint数组,并存储包装CGPoints的NSvalues。
-
用NSMutableArraysreplace所有的双数组,并存储NSNumber包装双重。
-
在下标的情况下使用objectAtIndex:方法来访问数组元素。
-
使用replaceObjectAtIndex:withObject:将对象存储在特定索引处。
请记住,NSMutableArray是一个linkedList,C#使用的是dynamic数组,所以它们已经有了索引。 在你的情况下,如果一个NSMutableArray是空的,你不能像C#代码那样存储随机索引的对象。 他们有时在这个C#代码中,索引1之前填充索引1,他们可以这样做索引1存在。 在NSMutabelArrays这里,索引1应该在那里,如果你想调用它的replaceObject。 所以在存储任何东西之前,请在NSMutableArray中添加n个NSNull对象。
还有:
以及这个逻辑有一个静态的方法,将接受一个点的数组,并给你两个数组:
-
第一个控制点arrays。
-
第二个控制点arrays。
这些数组将为您在第一个数组中传递的两个点之间的每个曲线保存第一个和第二个控制点。
就我而言,我已经有了所有的要点,我可以通过它们来绘制曲线。
在你绘制的情况下,你将需要一些如何提供一组点,通过你想要一个平滑的曲线通过。
并通过调用setNeedsDisplay进行刷新,然后在第一个数组中的两个相邻点之间绘制除了UIBezierPath之外的样条曲线。 并从两个控制点数组中取得控制点。
在你的情况下的问题是,它是难以理解的,同时移动所有的关键点。
你可以做的是:只要移动手指,在前一个点和当前点之间画直线。 线会很小,肉眼不会看到它们是小的小直线,除非放大。
UPDATE
任何对以上链接的Objective C实现感兴趣的人都可以参考
这个 GitHub回购。
我曾经写过,它不支持ARC,但你可以轻松地编辑它,并删除几个发布和autorelease调用,并使其与ARC工作。
这一个只是为一组想要使用贝塞尔样条连接的点生成两个控制点数组。
不需要写这么多的代码。
只需参考ios写意教程 ; 它确实使画图变得平滑,而且caching机制也在那里,即使在不断绘图时性能也不会下降。
我发现了一个非常不错的教程 ,它描述了对Bezier曲线图的轻微修改,它可以很好地平滑边缘。 这基本上就是迦勒所提到的关于把连接终点放在与控制点相同的线上。 这是我读过的最好的教程之一(任何事情)。 它配备了一个完整的Xcode项目。
我尝试了以上所有,但不能使其工作。 其中一个答案甚至给我一个破碎的结果。 在search更多时,我发现这个: https : //github.com/sam-keene/uiBezierPath-hermite-curve 。 我没有写这个代码,但我实现了它,它真的很好。 只需复制UIBezierPath + Interpolation.m / h和CGPointExtension.m / h即可。 然后你这样使用它:
UIBezierPath *path = [UIBezierPath interpolateCGPointsWithHermite:arrayPoints closed:YES];
这是一个真正的健壮和整洁的解决scheme。
迅速:
let point1 = CGPoint(x: 50, y: 100) let point2 = CGPoint(x: 50 + 1 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 200) let point3 = CGPoint(x: 50 + 2 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 250) let point4 = CGPoint(x: 50 + 3 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 50) let point5 = CGPoint(x: 50 + 4 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 100) let points = [point1, point2, point3, point4, point5] let bezier = UIBezierPath() let count = points.count var prevDx = CGFloat(0) var prevDy = CGFloat(0) var prevX = CGFloat(0) var prevY = CGFloat(0) let div = CGFloat(7) for i in 0..<count { let x = points[i].x let y = points[i].y var dx = CGFloat(0) var dy = CGFloat(0) if (i == 0) { bezier.move(to: points[0]) let nextX = points[i + 1].x let nextY = points[i + 1].y prevDx = (nextX - x) / div prevDy = (nextY - y) / div prevX = x prevY = y } else if (i == count - 1) { dx = (x - prevX) / div dy = (y - prevY) / div } else { let nextX = points[i + 1].x let nextY = points[i + 1].y dx = (nextX - prevX) / div; dy = (nextY - prevY) / div; } bezier.addCurve(to: CGPoint(x: x, y: y), controlPoint1: CGPoint(x: prevX + prevDx, y: prevY + prevDy), controlPoint2: CGPoint(x: x - dx, y: y - dy)) prevDx = dx; prevDy = dy; prevX = x; prevY = y; }