Have you ever wondered how bezier paths work? What are the control points, and how exactly do they affect the line? In this episode we'll build our own visualization of how a bézier path is constructed to help understand it better.
Episode Links Example Source Code The Bézier Game - a really unique game that teaches you the pen tool, which is a tool used to work with bézier curves. It starts out easy and gets progressively harder. De Casteljau's Algorithm - Worth a read if you're interested in the math behind this technique. Drawing a Bezier Curve Between 2 Points Drawing a bezier curve requires 2 control points, which influence the curve of the line: CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor) CGContextSetLineWidth(context, 4) CGContextMoveToPoint(context, startPoint.x, startPoint.y) CGContextMoveToPoint(context, startPoint.x, startPoint.y) CGContextAddCurveToPoint(context, control1.x, control1.y, control2.x, control2.y, endPoint.x, endPoint.y ) CGContextStrokePath(context) How does this work? To understand we can draw some lines & points to visualize it. Some Utility Functions First we need some utility functions to make drawing a little easier. The first is to draw a point with a specified radius and color: func drawPoint(context: CGContextRef, point: Point, color: UIColor, radius: CGFloat = 4, outlineColor: UIColor? = nil) { let rect = CGRectMake(point.x - radius, point.y - radius, radius * 2, radius * 2) CGContextSetFillColorWithColor(context, color.CGColor) CGContextFillEllipseInRect(context, rect) if let outline = outlineColor { CGContextSetStrokeColorWithColor(context, outline.CGColor) CGContextSetLineWidth(context, 1) CGContextStrokeEllipseInRect(context, rect) } } We also need a way to interpolate a position on a line between two points: func interpolatePosition(p1: Point, p2: Point, t: CGFloat) -> Point { return Point( x: (1 - t) * p1.x + t * p2.x, y: (1 - t) * p1.y + t * p2.y ) } Connecting the Lines Given 4 points (start, control1, control2, end) you can connect the points with 3 lines like this: CGContextMoveToPoint(context, startPoint.x, startPoint.y) if control1 != nil { CGContextAddLineToPoint(context, control1!.x, control1!.y) } if control2 != nil { CGContextAddLineToPoint(context, control2!.x, control2!.y) } CGContextAddLineToPoint(context, endPoint.x, endPoint.y) CGContextSetLineWidth(context, 1) CGContextSetStrokeColorWithColor(context, UIColor.lightGrayColor().CGColor) CGContextStrokePath(context) Interpolating points along the lines Now we can create interpolated points on each line segment, t1, t2, t3. As we change t these points travel along their respective lines. let t1 = interpolatePosition(startPoint, p2: control1!, t: t) let t2 = interpolatePosition(control1!, p2: control2!, t: t) let t3 = interpolatePosition(control2!, p2: endPoint, t: t) drawPoint(context, point: t1, color: UIColor.greenColor(), radius: 4) drawPoint(context, point: t2, color: UIColor.greenColor(), radius: 4) drawPoint(context, point: t3, color: UIColor.greenColor(), radius: 4) We can then use these points to connect 2 lines: CGContextMoveToPoint(context, t1.x, t1.y) CGContextAddLineToPoint(context, t2.x, t2.y) CGContextAddLineToPoint(context, t3.x, t3.y) CGContextSetStrokeColorWithColor(context, UIColor(white: 0.8, alpha: 1.0).CGColor) CGContextStrokePath(context) This gives us 2 lines, on which we can interpolate 2 more points along time t: let u1 = interpolatePosition(t1, p2: t2, t: t) let u2 = interpolatePosition(t2, p2: t3, t: t) drawPoint(context, point: u1, color: UIColor.purpleColor(), radius: 4) drawPoint(context, point: u2, color: UIColor.purpleColor(), radius: 4) And with these 2 points we can form another line: CGContextMoveToPoint(context, u1.x, u1.y) CGContextAddLineToPoint(context, u2.x, u2.y) CGContextSetStrokeColorWithColor(context, UIColor(white: 0.7, alpha: 1.0).CGColor) CGContextStrokePath(context) Finally, we can interpolate a point along this line, also across time t. This point, happens to trace the bézier curve exactly. let v1 = interpolatePosition(u1, p2: u2, t: t) drawPoint(context, point: v1, color: UIColor.orangeColor(), radius: 4)