Skip to content

Instantly share code, notes, and snippets.

@swhitty
Last active February 20, 2020 00:40
Show Gist options
  • Save swhitty/2f2c04f56f9e36879ea0a353a99d82e9 to your computer and use it in GitHub Desktop.
Save swhitty/2f2c04f56f9e36879ea0a353a99d82e9 to your computer and use it in GitHub Desktop.
import CoreGraphics
extension CGPath {
// Creates a smooth CGPath line between supplied points.
//
// Typical Polyline, straight line from point to point.
//
// /\
// __ ___/ \ /
// \__/ \/
//
//
// Smooth Polyline, cubic curve from point to point.
//
// ,"\ /
// __,' \ /
// "-._,' "
static func makeSmoothPolyline(between points: [CGPoint]) -> CGPath {
let path = CGMutablePath()
guard points.count > 1 else {
return path.copy()!
}
path.move(to: points[0])
for i in 1..<points.count {
let cps = controlPoint(current: points[i - 1],
previous: points[safe: i - 2],
next: points[i],
rotate: 0)
let cpe = controlPoint(current: points[i],
previous: points[i - 1],
next: points[safe: i + 1],
rotate: .pi)
path.addCurve(to: points[i], control1: cps, control2: cpe)
}
return path.copy()!
}
private static func opposingLine(from previous: CGPoint, to next: CGPoint) -> (length: CGFloat, angle: CGFloat) {
let dx = next.x - previous.x
let dy = next.y - previous.y
return (length: sqrt(dx * dx + dy * dy),
angle: atan2(dy, dx))
}
private static func controlPoint(current: CGPoint, previous: CGPoint?, next: CGPoint?, smoothing: CGFloat = 0.2, rotate: CGFloat) -> CGPoint {
let (length, angle) = opposingLine(from: previous ?? current, to: next ?? current)
return CGPoint(x: current.x + cos(angle + rotate) * length * smoothing,
y: current.y + sin(angle + rotate) * length * smoothing)
}
}
private extension Array where Element == CGPoint {
subscript(safe index: Int) -> Element? {
guard indices.contains(index) else { return nil }
return self[index]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment