Background:
In my application I have to draw a curve along the points where user taps the screen. I found below blog that explains how to calculate control points based on path points for bezier curves.
Spline Interpolation - Scaled Innovation
The example was in javascript and I adapted it to iOS. Below is the code I am using in my app
void (^calculateAndAddControlPoints)(CGPoint firstPoint, CGPoint secondPoint, CGPoint thirdPoint) = ^void (CGPoint firstPoint, CGPoint secondPoint, CGPoint thirdPoint)
{
CGPoint controlPoint1;
CGPoint controlPoint2;
CGFloat distance12 = sqrt(pow(firstPoint.x-secondPoint.x, 2) + pow(firstPoint.y-secondPoint.y, 2));
CGFloat distance23 = sqrt(pow(secondPoint.x-thirdPoint.x, 2) + pow(secondPoint.y-thirdPoint.y, 2));
CGFloat scallingFactor12 = (tension * distance12) / (distance12 + distance23);
CGFloat scallingFactor23 = tension - scallingFactor12;
controlPoint1.x = secondPoint.x - (scallingFactor12 * (thirdPoint.x - firstPoint.x));
controlPoint1.y = secondPoint.y - (scallingFactor12 * (thirdPoint.y - firstPoint.y));
controlPoint2.x = secondPoint.x + (scallingFactor23 * (thirdPoint.x - firstPoint.x));
controlPoint2.y = secondPoint.y + (scallingFactor23 * (thirdPoint.y - firstPoint.y));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor yellowColor].CGColor);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:controlPoint1];
[path addLineToPoint:controlPoint2];
[path stroke];
[controlPoints addObject:[NSValue valueWithCGPoint:controlPoint1]];
[controlPoints addObject:[NSValue valueWithCGPoint:controlPoint2]];
};
NSUInteger curvePointsCount = [curvePoints count];
if (_closed) {
NSValue *lastPoint = [curvePoints lastObject];
[curvePoints addObject:curvePoints[0]];
[curvePoints addObject:curvePoints[1]];
[curvePoints insertObject:lastPoint atIndex:0];
for (NSInteger i=0; i < curvePointsCount; ++i) {
calculateAndAddControlPoints([curvePoints[i + 0] CGPointValue],
[curvePoints[i + 1] CGPointValue],
[curvePoints[i + 2] CGPointValue]);
}
[controlPoints addObject:controlPoints[0]];
[controlPoints addObject:controlPoints[1]];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:[curvePoints[1] CGPointValue]];
for (NSInteger i=1; i < curvePointsCount + 1; ++i) {
[path addCurveToPoint:[curvePoints[i + 1] CGPointValue]
controlPoint1:[controlPoints[2 * i + 0] CGPointValue]
controlPoint2:[controlPoints[2 * i + 1] CGPointValue]];
}
[path stroke];
} else {
for (NSInteger i=0; i < curvePointsCount - 2; ++i) {
calculateAndAddControlPoints([curvePoints[i + 0] CGPointValue],
[curvePoints[i + 1] CGPointValue],
[curvePoints[i + 2] CGPointValue]);
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:[curvePoints[0] CGPointValue]];
[path addQuadCurveToPoint:[curvePoints[1] CGPointValue]
controlPoint:[controlPoints[0] CGPointValue]];
for (NSInteger i=1; i < curvePointsCount - 2; ++i) {
[path addCurveToPoint:[curvePoints[i + 1] CGPointValue]
controlPoint1:[controlPoints[2 * i + 0] CGPointValue]
controlPoint2:[controlPoints[2 * i + 1] CGPointValue]];
}
[path moveToPoint:[curvePoints[curvePointsCount - 2] CGPointValue]];
[path addQuadCurveToPoint:[curvePoints[curvePointsCount - 1] CGPointValue]
controlPoint:[controlPoints[[controlPoints count] - 1] CGPointValue]];
[path stroke];
}
Problem:
The output in iOS is different from the output in javascript, as you can see below the curves are not smooth near the path points. The white lines are actual curve lines, the yellow lines are lines joining the control points and red dots are the curve points.
This is how the same curve looks in javascript.
The curve points used: (100, 100), (300, 100), (300, 300), (100, 300)
The control points generated: (50, 150), (150, 50), (250, 50), (350, 150), (350, 250), (250, 350), (150, 350), (50, 250)
Why didn't I get a circle in iOS, how to fix this?
Solution:
It turns out I was calculating control points properly but mapping them wrongly to curve points. Thanks to @gabbler I fixed it now, below is the proper loop for drawing the curve
for (NSInteger i=1; i < curvePointsCount + 1; ++i) {
[_bezierPath moveToPoint:[curvePoints[i] CGPointValue]];
[_bezierPath addCurveToPoint:[curvePoints[i + 1] CGPointValue]
controlPoint1:[controlPoints[2 * i - 1] CGPointValue]
controlPoint2:[controlPoints[2 * i + 0] CGPointValue]];
}