Skip to content

Instantly share code, notes, and snippets.

@danscan
Created June 8, 2015 14:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danscan/c4bc627429e6e414c269 to your computer and use it in GitHub Desktop.
Save danscan/c4bc627429e6e414c269 to your computer and use it in GitHub Desktop.
PointSequence.swift
//
// PointSequence.swift
// ThoughtPad
//
// Created by Daniel Scanlon on 6/5/15.
// Copyright (c) 2015 Present. All rights reserved.
//
import UIKit
class PointSequence: Printable {
// MARK: Printable Conformance
var description: String {
return points.description
}
// MARK: State
var points: [CGPoint]
var closeDistanceThresholdFactor: CGFloat = 0.1
// MARK: Dynamic Properties
var firstPoint: CGPoint {
return points.first ?? CGPointMake(0, 0)
}
var lastPoint: CGPoint {
return points.last ?? CGPointMake(0, 0)
}
var bezierPath: UIBezierPath {
// Get Hermite-interpolated bezier path
let bezierPath = self.getHermiteInterpolatedBezierPath()
return bezierPath
}
var closed: Bool {
// Get bounds
let bounds = self.getBounds()
// Get close distance (x,y) thresholds via factor * bounds dimension
let closeDistanceXThreshold = bounds.width * closeDistanceThresholdFactor
let closeDistanceYThreshold = bounds.height * closeDistanceThresholdFactor
// Get close distances
let closeDistanceX = abs(lastPoint.x - firstPoint.x)
let closeDistanceY = abs(lastPoint.y - firstPoint.y)
// Closed if (x,y) close distances are within close distance thresholds
if closeDistanceX < closeDistanceXThreshold && closeDistanceY < closeDistanceYThreshold {
return true
} else {
return false
}
}
// MARK: Init
init(initialPoint: CGPoint) {
self.points = [initialPoint]
}
init(points: [CGPoint]) {
self.points = points
}
// MARK: Public mutation methods
func appendPoint(point: CGPoint) {
points.append(point)
}
// MARK: Private helper methods
private func getBezierPath() -> UIBezierPath {
// Create bezier path
let bezierPath = UIBezierPath()
// Move bezier path to first point
bezierPath.moveToPoint(firstPoint)
// Draw lines to subsequent points in sequence
for point: CGPoint in points {
bezierPath.addLineToPoint(point)
}
return bezierPath
}
private func getBounds() -> CGRect {
// Get bounds of bezier path
let bezierPath = self.getBezierPath()
let bounds = bezierPath.bounds
return bounds
}
private func getHermiteInterpolatedBezierPath() -> UIBezierPath {
// Create bezier path, and move to first point
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(firstPoint)
// Return simple bezier path if < 2 points
if points.count < 2 {
return self.getBezierPath()
}
// Get number of curves in path
var numberOfCurves = closed ? points.count : points.count - 1
// Iterate through points in curves
for var i=0; i < numberOfCurves; ++i {
var nextPointIndex = (i + 1) % points.count
var previousPointIndex = (i - 1) < 0 ? points.count - 1 : i - 1
var currentPoint: CGPoint = points[i]
var previousPoint: CGPoint = points[previousPointIndex]
var nextPoint: CGPoint = points[nextPointIndex]
var endPoint: CGPoint = nextPoint
// Calculate middle between current, previous, and next points
var middleX: CGFloat
var middleY: CGFloat
if closed || i > 0 {
middleX = (nextPoint.x - currentPoint.x)*0.5 + (currentPoint.x - previousPoint.x)*0.5
middleY = (nextPoint.y - currentPoint.y)*0.5 + (currentPoint.y - previousPoint.y)*0.5
} else {
middleX = (nextPoint.x - currentPoint.x)*0.5
middleY = (nextPoint.y - currentPoint.y)*0.5
}
// Create control point 1
let controlPoint1X: CGFloat = currentPoint.x + middleX / 3
let controlPoint1Y: CGFloat = currentPoint.y + middleY / 3
let controlPoint1 = CGPointMake(controlPoint1X, controlPoint1Y)
// Advance current point to next point
currentPoint = points[nextPointIndex]
nextPointIndex = (nextPointIndex + 1) % points.count
previousPointIndex = i
previousPoint = points[previousPointIndex]
nextPoint = points[nextPointIndex]
// Calculate middle between current, previous, and next points
if closed || i < numberOfCurves - 1 {
middleX = (nextPoint.x - currentPoint.x)*0.5 + (currentPoint.x - previousPoint.x)*0.5
middleY = (nextPoint.y - currentPoint.y)*0.5 + (currentPoint.y - previousPoint.y)*0.5
} else {
middleX = (nextPoint.x - currentPoint.x)*0.5
middleY = (nextPoint.y - currentPoint.y)*0.5
}
// Create control point 2
let controlPoint2X: CGFloat = currentPoint.x + middleX / 3
let controlPoint2Y: CGFloat = currentPoint.y + middleY / 3
let controlPoint2 = CGPointMake(controlPoint1X, controlPoint1Y)
// Add curve to to end point through control points to bezier path
bezierPath.addCurveToPoint(endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
}
// Close bezier path if this is closed
if closed {
bezierPath.closePath()
}
return bezierPath
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment