2022-12-10 15:20:23 +04:00

934 lines
34 KiB
Swift

import Foundation
import UIKit
import Display
struct PointWeighted {
let point: CGPoint
let weight: CGFloat
static let zero = PointWeighted(point: CGPoint.zero, weight: 0)
}
struct LineSegment {
let start: CGPoint
let end: CGPoint
var length: CGFloat {
return start.distance(to: end)
}
func average(with line: LineSegment) -> LineSegment {
return LineSegment(start: start.average(with: line.start), end: end.average(with: line.end))
}
func normalLine(from weightedPoint: PointWeighted) -> LineSegment {
return normalLine(withMiddle: weightedPoint.point, weight: weightedPoint.weight)
}
func normalLine(withMiddle middle: CGPoint, weight: CGFloat) -> LineSegment {
let relativeEnd = start.diff(to: end)
guard weight != 0 && relativeEnd != CGPoint.zero else {
return LineSegment(start: middle, end: middle)
}
let moddle = weight / 2
let lengthK = moddle / length
let k = CGPoint(x: relativeEnd.x * lengthK, y: relativeEnd.y * lengthK)
var normalLineStart = CGPoint(x: k.y, y: -k.x)
var normalLineEnd = CGPoint(x: -k.y, y: k.x)
normalLineStart.x += middle.x;
normalLineStart.y += middle.y;
normalLineEnd.x += middle.x;
normalLineEnd.y += middle.y;
return LineSegment(start: normalLineStart, end: normalLineEnd)
}
}
extension CGPoint {
func average(with point: CGPoint) -> CGPoint {
return CGPoint(x: (x + point.x) * 0.5, y: (y + point.y) * 0.5)
}
func diff(to point: CGPoint) -> CGPoint {
return CGPoint(x: point.x - x, y: point.y - y)
}
func forward(to point: CGPoint, by: CGFloat) -> CGPoint {
let diff = diff(to: point)
let distance = sqrt(pow(diff.x, 2) + pow(diff.y, 2))
let k = by / distance
return CGPoint(x: point.x + diff.x * k, y: point.y + diff.y * k)
}
}
final class PenTool: DrawingElement {
class RenderLayer: SimpleLayer, DrawingRenderLayer {
func setup(size: CGSize) {
self.shouldRasterize = true
self.contentsScale = 1.0
let bounds = CGRect(origin: .zero, size: size)
self.frame = bounds
}
private var paths: [UIBezierPath] = []
private var tempPath: UIBezierPath?
private var color: UIColor?
fileprivate func draw(paths: [UIBezierPath], tempPath: UIBezierPath?, color: UIColor, rect: CGRect) {
self.paths = paths
self.tempPath = tempPath
self.color = color
self.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0))
}
override func draw(in ctx: CGContext) {
guard let color = self.color else {
return
}
ctx.setFillColor(color.cgColor)
for path in self.paths {
ctx.addPath(path.cgPath)
ctx.fillPath()
}
if let tempPath = self.tempPath {
ctx.addPath(tempPath.cgPath)
ctx.fillPath()
}
}
}
let uuid = UUID()
let drawingSize: CGSize
let color: DrawingColor
let lineWidth: CGFloat
let arrow: Bool
var path: Polyline?
var boundingBox: CGRect?
var didSetupArrow = false
let renderLineWidth: CGFloat
var bezierPaths: [UIBezierPath] = []
var tempBezierPath: UIBezierPath?
var translation = CGPoint()
private var currentRenderLayer: DrawingRenderLayer?
var bounds: CGRect {
return self.path?.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
}
var _points: [Polyline.Point] = []
var points: [Polyline.Point] {
var points: [Polyline.Point] = []
for point in self._points {
points.append(point.offsetBy(self.translation))
}
return points
}
private let pointsPerLine: Int = 4
private var nextPointIndex: Int = 0
private var drawPoints = [PointWeighted](repeating: PointWeighted.zero, count: 4)
func containsPoint(_ point: CGPoint) -> Bool {
return false
// return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false
}
func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
if let linePath = self.path {
let pathBoundingBox = path.bounds
if self.bounds.intersects(pathBoundingBox) {
for point in linePath.points {
if path.contains(point.location.offsetBy(self.translation)) {
return true
}
}
}
}
return false
}
required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
self.drawingSize = drawingSize
self.color = color
self.lineWidth = lineWidth
self.arrow = arrow
let minLineWidth = max(1.0, min(drawingSize.width, drawingSize.height) * 0.003)
let maxLineWidth = max(10.0, min(drawingSize.width, drawingSize.height) * 0.09)
let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth
self.renderLineWidth = lineWidth
// self.renderLine = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth)
// if arrow {
// self.renderLineArrow1 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8)
// self.renderLineArrow2 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8)
// }
}
func setupRenderLayer() -> DrawingRenderLayer? {
let layer = RenderLayer()
layer.setup(size: self.drawingSize)
self.currentRenderLayer = layer
return layer
}
func updateWithLocation(_ point: CGPoint, ended: Bool = false) {
if ended {
if let path = tempBezierPath {
bezierPaths.last?.append(path)
}
tempBezierPath = nil
nextPointIndex = 0
} else {
addPoint(point)
}
}
func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) {
guard case let .location(point) = path else {
return
}
self._points.append(point)
switch state {
case .began:
addPoint(point.location)
case .changed:
if self._points.count > 1 {
self.updateTouchPoints(point: self._points[self._points.count - 1].location, previousPoint: self._points[self._points.count - 2].location)
self.updateWithLocation(point.location)
}
case .ended:
self.updateWithLocation(point.location, ended: true)
case .cancelled:
break
}
if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
currentRenderLayer.draw(paths: self.bezierPaths, tempPath: self.tempBezierPath, color: self.color.toUIColor(), rect: CGRect(origin: .zero, size: self.drawingSize))
}
// self.path = line
//
// let rect = self.renderLine.draw(at: point)
// if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
// currentRenderLayer.draw(line: self.renderLine, rect: rect)
// }
// self.path = bezierPath
// if self.arrow && polyline.isComplete, polyline.points.count > 2 {
// let lastPoint = lastPosition
// var secondPoint = polyline.points[polyline.points.count - 2]
// if secondPoint.location.distance(to: lastPoint) < self.renderArrowLineWidth {
// secondPoint = polyline.points[polyline.points.count - 3]
// }
// let angle = lastPoint.angle(to: secondPoint.location)
// let point1 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle - CGFloat.pi * 0.15)
// let point2 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle + CGFloat.pi * 0.15)
//
// let arrowPath = UIBezierPath()
// arrowPath.move(to: point2)
// arrowPath.addLine(to: lastPoint)
// arrowPath.addLine(to: point1)
// let arrowThickPath = arrowPath.cgPath.copy(strokingWithWidth: self.renderArrowLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
//
// combinedPath.usesEvenOddFillRule = false
// combinedPath.append(UIBezierPath(cgPath: arrowThickPath))
// }
// let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
// self.renderPath = cgPath
// if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
// currentRenderLayer.updatePath(cgPath)
// }
}
private let minDistance: CGFloat = 2
private func addPoint(_ point: CGPoint) {
if isFirstPoint {
startNewLine(from: PointWeighted(point: point, weight: 2.0))
} else {
let previousPoint = self.drawPoints[nextPointIndex - 1].point
guard previousPoint.distance(to: point) >= minDistance else {
return
}
if isStartOfNextLine {
finalizeBezier(nextLineStartPoint: point)
startNewLine(from: self.drawPoints[3])
}
let weightedPoint = PointWeighted(point: point, weight: weightForLine(between: previousPoint, and: point))
addPoint(point: weightedPoint)
}
let newBezier = generateBezierPath(withPointIndex: nextPointIndex - 1)
self.tempBezierPath = newBezier
}
private var isFirstPoint: Bool {
return nextPointIndex == 0
}
private var isStartOfNextLine: Bool {
return nextPointIndex >= pointsPerLine
}
private func startNewLine(from weightedPoint: PointWeighted) {
drawPoints[0] = weightedPoint
nextPointIndex = 1
}
private func addPoint(point: PointWeighted) {
drawPoints[nextPointIndex] = point
nextPointIndex += 1
}
private func finalizeBezier(nextLineStartPoint: CGPoint) {
let touchPoint2 = drawPoints[2].point
let newTouchPoint3 = touchPoint2.average(with: nextLineStartPoint)
drawPoints[3] = PointWeighted(point: newTouchPoint3, weight: weightForLine(between: touchPoint2, and: newTouchPoint3))
guard let bezier = generateBezierPath(withPointIndex: 3) else {
return
}
self.bezierPaths.append(bezier)
}
private func generateBezierPath(withPointIndex index: Int) -> UIBezierPath? {
switch index {
case 0:
return UIBezierPath.dot(with: drawPoints[0])
case 1:
return UIBezierPath.curve(withPointA: drawPoints[0], pointB: drawPoints[1])
case 2:
return UIBezierPath.curve(withPointA: drawPoints[0], pointB: drawPoints[1], pointC: drawPoints[2])
case 3:
return UIBezierPath.curve(withPointA: drawPoints[0], pointB: drawPoints[1], pointC: drawPoints[2], pointD: drawPoints[3])
default:
return nil
}
}
private func weightForLine(between pointA: CGPoint, and pointB: CGPoint) -> CGFloat {
let length = pointA.distance(to: pointB)
let limitRange: CGFloat = 50
var lowerer: CGFloat = 0.2
var constant: CGFloat = 2
let toolWidth = self.renderLineWidth
constant = toolWidth - 3.0
lowerer = 0.25 * toolWidth / 10.0
let r = min(limitRange, length)
// var r = limitRange - length
// if r < 0 {
// r = 0
// }
// print(r * lowerer)
return (r * lowerer) + constant
}
public var firstPoint: CGPoint = .zero
public var currentPoint: CGPoint = .zero
private var previousPoint: CGPoint = .zero
private var previousPreviousPoint: CGPoint = .zero
private func setTouchPoints(point: CGPoint, previousPoint: CGPoint) {
self.previousPoint = previousPoint
self.previousPreviousPoint = previousPoint
self.currentPoint = point
}
private func updateTouchPoints(point: CGPoint, previousPoint: CGPoint) {
self.previousPreviousPoint = self.previousPoint
self.previousPoint = previousPoint
self.currentPoint = point
}
private func calculateMidPoint(_ p1 : CGPoint, p2 : CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) * 0.5, y: (p1.y + p2.y) * 0.5);
}
private func getMidPoints() -> (CGPoint, CGPoint) {
let mid1 : CGPoint = calculateMidPoint(previousPoint, p2: previousPreviousPoint)
let mid2 : CGPoint = calculateMidPoint(currentPoint, p2: previousPoint)
return (mid1, mid2)
}
func draw(in context: CGContext, size: CGSize) {
context.saveGState()
context.translateBy(x: self.translation.x, y: self.translation.y)
context.setShouldAntialias(true)
context.setFillColor(self.color.toCGColor())
for path in self.bezierPaths {
context.addPath(path.cgPath)
context.fillPath()
}
context.restoreGState()
}
}
extension UIBezierPath {
class func dot(with weightedPoint: PointWeighted) -> UIBezierPath {
let path = UIBezierPath()
path.addArc(withCenter: weightedPoint.point, radius: weightedPoint.weight / 2.0, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
return path
}
class func curve(withPointA pointA: PointWeighted, pointB: PointWeighted) -> UIBezierPath {
let lines = normalToLine(from: pointA, to: pointB)
let path = UIBezierPath()
path.move(to: lines.0.start)
path.addLine(to: lines.1.start)
let arcA = lines.1.start
let arcB = lines.1.end
path.addQuadCurve(to: arcB, controlPoint: pointA.point.forward(to: pointB.point, by: arcA.distance(to: arcB) / 1.1))
path.addLine(to: lines.0.end)
path.close()
return path
}
class func curve(withPointA pointA: PointWeighted, pointB: PointWeighted, pointC: PointWeighted) -> UIBezierPath {
let linesAB = normalToLine(from: pointA, to: pointB)
let linesBC = normalToLine(from: pointB, to: pointC)
let lineA = linesAB.0
let lineB = linesAB.1.average(with: linesBC.0)
let lineC = linesBC.1
let path = UIBezierPath()
path.move(to: lineA.start)
path.addQuadCurve(to: lineC.start, controlPoint: lineB.start)
let arcA = lineC.start
let arcB = lineC.end
path.addQuadCurve(to: arcB, controlPoint: pointB.point.forward(to: pointC.point, by: arcA.distance(to: arcB) / 1.1))
path.addQuadCurve(to: lineA.end, controlPoint: lineB.end)
path.close()
return path
}
class func line(withPointA pointA: PointWeighted, pointB: PointWeighted, pointC: PointWeighted, prevLineSegment: LineSegment, roundedEnd: Bool = true) -> (UIBezierPath, LineSegment) {
let linesAB = normalToLine(from: pointA, to: pointB)
let linesBC = normalToLine(from: pointB, to: pointC)
// let lineA = linesAB.0
let lineB = linesAB.1.average(with: linesBC.0)
let lineC = linesBC.1
let path = UIBezierPath()
path.move(to: prevLineSegment.start)
path.addQuadCurve(to: lineC.start, controlPoint: lineB.start)
if roundedEnd {
let arcA = lineC.start
let arcB = lineC.end
path.addQuadCurve(to: arcB, controlPoint: pointB.point.forward(to: pointC.point, by: arcA.distance(to: arcB) / 1.1))
} else {
path.addLine(to: lineC.end)
}
path.addQuadCurve(to: prevLineSegment.end, controlPoint: lineB.end)
path.close()
return (path, lineC)
}
class func curve(withPointA pointA: PointWeighted, pointB: PointWeighted, pointC: PointWeighted, pointD: PointWeighted) -> UIBezierPath {
let linesAB = normalToLine(from: pointA, to: pointB)
let linesBC = normalToLine(from: pointB, to: pointC)
let linesCD = normalToLine(from: pointC, to: pointD)
let lineA = linesAB.0
let lineB = linesAB.1.average(with: linesBC.0)
let lineC = linesBC.1.average(with: linesCD.0)
let lineD = linesCD.1
let path = UIBezierPath()
path.move(to: lineA.start)
path.addCurve(to: lineD.start, controlPoint1: lineB.start, controlPoint2: lineC.start)
let arcA = lineD.start
let arcB = lineD.end
path.addQuadCurve(to: arcB, controlPoint: pointC.point.forward(to: pointD.point, by: arcA.distance(to: arcB) / 1.1))
path.addCurve(to: lineA.end, controlPoint1: lineC.end, controlPoint2: lineB.end)
path.close()
return path
}
class func normalToLine(from pointA: PointWeighted, to pointB: PointWeighted) -> (LineSegment, LineSegment) {
let line = LineSegment(start: pointA.point, end: pointB.point)
return (line.normalLine(from: pointA), line.normalLine(from: pointB))
}
}
//
//final class PenTool: DrawingElement {
// class RenderLayer: SimpleLayer, DrawingRenderLayer {
// func setup(size: CGSize) {
// self.shouldRasterize = true
// self.contentsScale = 1.0
//
// let bounds = CGRect(origin: .zero, size: size)
// self.frame = bounds
// }
//
// private var line: StrokeLine?
// fileprivate func draw(line: StrokeLine, rect: CGRect) {
// self.line = line
// self.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0))
// }
//
// override func draw(in ctx: CGContext) {
// self.line?.drawInContext(ctx)
// }
// }
//
// let uuid = UUID()
//
// let drawingSize: CGSize
// let color: DrawingColor
// let lineWidth: CGFloat
// let arrow: Bool
//
// var path: Polyline?
// var boundingBox: CGRect?
//
// private var renderLine: StrokeLine
// var didSetupArrow = false
// private var renderLineArrow1: StrokeLine?
// private var renderLineArrow2: StrokeLine?
// let renderLineWidth: CGFloat
//
// var translation = CGPoint()
//
// private var currentRenderLayer: DrawingRenderLayer?
//
// var bounds: CGRect {
// return self.path?.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero
// }
//
// var points: [Polyline.Point] {
// guard let linePath = self.path else {
// return []
// }
// var points: [Polyline.Point] = []
// for point in linePath.points {
// points.append(point.offsetBy(self.translation))
// }
// return points
// }
//
// func containsPoint(_ point: CGPoint) -> Bool {
// return false
// // return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false
// }
//
// func hasPointsInsidePath(_ path: UIBezierPath) -> Bool {
// if let linePath = self.path {
// let pathBoundingBox = path.bounds
// if self.bounds.intersects(pathBoundingBox) {
// for point in linePath.points {
// if path.contains(point.location.offsetBy(self.translation)) {
// return true
// }
// }
// }
// }
// return false
// }
//
// required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) {
// self.drawingSize = drawingSize
// self.color = color
// self.lineWidth = lineWidth
// self.arrow = arrow
//
// let minLineWidth = max(1.0, min(drawingSize.width, drawingSize.height) * 0.003)
// let maxLineWidth = max(10.0, min(drawingSize.width, drawingSize.height) * 0.09)
// let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth
//
// self.renderLineWidth = lineWidth
//
// self.renderLine = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth)
// if arrow {
// self.renderLineArrow1 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8)
// self.renderLineArrow2 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8)
// }
// }
//
// func setupRenderLayer() -> DrawingRenderLayer? {
// let layer = RenderLayer()
// layer.setup(size: self.drawingSize)
// self.currentRenderLayer = layer
// return layer
// }
//
// func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) {
// guard case let .polyline(line) = path, let point = line.points.last else {
// return
// }
// self.path = line
//
// let rect = self.renderLine.draw(at: point)
// if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
// currentRenderLayer.draw(line: self.renderLine, rect: rect)
// }
// // self.path = bezierPath
//
// // if self.arrow && polyline.isComplete, polyline.points.count > 2 {
// // let lastPoint = lastPosition
// // var secondPoint = polyline.points[polyline.points.count - 2]
// // if secondPoint.location.distance(to: lastPoint) < self.renderArrowLineWidth {
// // secondPoint = polyline.points[polyline.points.count - 3]
// // }
// // let angle = lastPoint.angle(to: secondPoint.location)
// // let point1 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle - CGFloat.pi * 0.15)
// // let point2 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle + CGFloat.pi * 0.15)
// //
// // let arrowPath = UIBezierPath()
// // arrowPath.move(to: point2)
// // arrowPath.addLine(to: lastPoint)
// // arrowPath.addLine(to: point1)
// // let arrowThickPath = arrowPath.cgPath.copy(strokingWithWidth: self.renderArrowLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
// //
// // combinedPath.usesEvenOddFillRule = false
// // combinedPath.append(UIBezierPath(cgPath: arrowThickPath))
// // }
//
// // let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0)
// // self.renderPath = cgPath
//
// // if let currentRenderLayer = self.currentRenderLayer as? RenderLayer {
// // currentRenderLayer.updatePath(cgPath)
// // }
// }
//
// func draw(in context: CGContext, size: CGSize) {
// context.saveGState()
//
// context.translateBy(x: self.translation.x, y: self.translation.y)
//
// context.setShouldAntialias(true)
//
// if self.arrow, let path = self.path, let lastPoint = path.points.last {
// var lastPointWithVelocity: Polyline.Point?
// for point in path.points.reversed() {
// if point.velocity > 0.0 {
// lastPointWithVelocity = point
// break
// }
// }
// if !self.didSetupArrow, let lastPointWithVelocity = lastPointWithVelocity {
// let w = self.renderLineWidth
// var dist: CGFloat = 18.0 * sqrt(w)
// let spread: CGFloat = .pi * max(0.05, 0.03 * sqrt(w))
//
// let suffix = path.points.suffix(100).reversed()
//
// var p0 = suffix.first!
//
// var p2 = suffix.last!
// var d: CGFloat = 0
// for p in suffix {
// d += hypot(p0.location.x - p.location.x, p0.location.y - p.location.y)
// if d >= dist {
// p2 = p
// break
// }
// p0 = p
// }
//
// p0 = suffix.first!
// dist = min(dist, hypot(p0.location.x - p2.location.x, p0.location.y - p2.location.y))
//
// var i = 0
// for spread in [-spread, spread] {
// var points: [CGPoint] = []
// points.append(lastPoint.location)
//
// p0 = suffix.first!
// var prev = p0.location
// d = 0
// for p in suffix {
// let d1 = hypot(p0.location.x - p.location.x, p0.location.y - p.location.y)
// d += d1
// if d >= dist {
// break
// }
// let d2 = d1 / cos(spread)
// let angle = atan2(p.location.y - p0.location.y, p.location.x - p0.location.x)
// let cur = CGPoint(x: prev.x + d2 * cos(angle + spread), y: prev.y + d2 * sin(angle + spread))
//
// points.append(
// cur
// )
//
// p0 = p
// prev = cur
// }
//
// for point in points {
// if i == 0 {
// let _ = self.renderLineArrow1?.draw(at: Polyline.Point(location: point, force: 0.0, altitudeAngle: 0.0, azimuth: 0.0, velocity: lastPointWithVelocity.velocity, touchPoint: lastPointWithVelocity.touchPoint))
// } else if i == 1 {
// let _ = self.renderLineArrow2?.draw(at: Polyline.Point(location: point, force: 0.0, altitudeAngle: 0.0, azimuth: 0.0, velocity: lastPointWithVelocity.velocity, touchPoint: lastPointWithVelocity.touchPoint))
// }
// }
// i += 1
// }
// self.didSetupArrow = true
// }
// self.renderLineArrow1?.drawInContext(context)
// self.renderLineArrow2?.drawInContext(context)
// }
//
// self.renderLine.drawInContext(context)
//
// context.restoreGState()
// }
//}
//
//private class StrokeLine {
// struct Segment {
// let a: CGPoint
// let b: CGPoint
// let c: CGPoint
// let d: CGPoint
// let abWidth: CGFloat
// let cdWidth: CGFloat
// }
//
// struct Point {
// let position: CGPoint
// let width: CGFloat
//
// init(position: CGPoint, width: CGFloat) {
// self.position = position
// self.width = width
// }
// }
//
// private(set) var points: [Point] = []
// private var smoothPoints: [Point] = []
// private var segments: [Segment] = []
// private var lastWidth: CGFloat?
//
// private let minLineWidth: CGFloat
// let lineWidth: CGFloat
//
// let color: UIColor
//
// init(color: UIColor, minLineWidth: CGFloat, lineWidth: CGFloat) {
// self.color = color
// self.minLineWidth = minLineWidth
// self.lineWidth = lineWidth
// }
//
// func draw(at point: Polyline.Point) -> CGRect {
// let width = extractLineWidth(from: point.velocity)
// self.lastWidth = width
//
// let point = Point(position: point.location, width: width)
// return appendPoint(point)
// }
//
// func drawInContext(_ context: CGContext) {
// self.drawSegments(self.segments, inContext: context)
// }
//
// func extractLineWidth(from velocity: CGFloat) -> CGFloat {
// let minValue = self.minLineWidth
// let maxValue = self.lineWidth
//
// var size = max(minValue, min(maxValue + 1 - (velocity / 150), maxValue))
// if let lastWidth = self.lastWidth {
// size = size * 0.2 + lastWidth * 0.8
// }
// return size
// }
//
// func appendPoint(_ point: Point) -> CGRect {
// self.points.append(point)
//
// guard self.points.count > 2 else { return .null }
//
// let index = self.points.count - 1
// let point0 = self.points[index - 2]
// let point1 = self.points[index - 1]
// let point2 = self.points[index]
//
// let newSmoothPoints = smoothPoints(
// fromPoint0: point0,
// point1: point1,
// point2: point2
// )
//
// let lastOldSmoothPoint = smoothPoints.last
// smoothPoints.append(contentsOf: newSmoothPoints)
//
// guard smoothPoints.count > 1 else { return .null }
//
// let newSegments: ([Segment], CGRect) = {
// guard let lastOldSmoothPoint = lastOldSmoothPoint else {
// return segments(fromSmoothPoints: newSmoothPoints)
// }
// return segments(fromSmoothPoints: [lastOldSmoothPoint] + newSmoothPoints)
// }()
// segments.append(contentsOf: newSegments.0)
//
// return newSegments.1
// }
//
// func smoothPoints(fromPoint0 point0: Point, point1: Point, point2: Point) -> [Point] {
// var smoothPoints = [Point]()
//
// let midPoint1 = (point0.position + point1.position) * 0.5
// let midPoint2 = (point1.position + point2.position) * 0.5
//
// let segmentDistance = 2.0
// let distance = midPoint1.distance(to: midPoint2)
// let numberOfSegments = min(128, max(floor(distance/segmentDistance), 32))
//
// let step = 1.0 / numberOfSegments
// for t in stride(from: 0, to: 1, by: step) {
// let position = midPoint1 * pow(1 - t, 2) + point1.position * 2 * (1 - t) * t + midPoint2 * t * t
// let size = pow(1 - t, 2) * ((point0.width + point1.width) * 0.5) + 2 * (1 - t) * t * point1.width + t * t * ((point1.width + point2.width) * 0.5)
// let point = Point(position: position, width: size)
// smoothPoints.append(point)
// }
//
// let finalPoint = Point(position: midPoint2, width: (point1.width + point2.width) * 0.5)
// smoothPoints.append(finalPoint)
//
// return smoothPoints
// }
//
// func segments(fromSmoothPoints smoothPoints: [Point]) -> ([Segment], CGRect) {
// var segments = [Segment]()
// var updateRect = CGRect.null
// for i in 1 ..< smoothPoints.count {
// let previousPoint = smoothPoints[i - 1].position
// let previousWidth = smoothPoints[i - 1].width
// let currentPoint = smoothPoints[i].position
// let currentWidth = smoothPoints[i].width
// let direction = currentPoint - previousPoint
//
// guard !currentPoint.isEqual(to: previousPoint, epsilon: 0.0001) else {
// continue
// }
//
// var perpendicular = CGPoint(x: -direction.y, y: direction.x)
// let length = perpendicular.length
// if length > 0.0 {
// perpendicular = perpendicular / length
// }
//
// let a = previousPoint + perpendicular * previousWidth / 2
// let b = previousPoint - perpendicular * previousWidth / 2
// let c = currentPoint + perpendicular * currentWidth / 2
// let d = currentPoint - perpendicular * currentWidth / 2
//
// let ab: CGPoint = {
// let center = (b + a)/2
// let radius = center - b
// return .init(x: center.x - radius.y, y: center.y + radius.x)
// }()
// let cd: CGPoint = {
// let center = (c + d)/2
// let radius = center - c
// return .init(x: center.x + radius.y, y: center.y - radius.x)
// }()
//
// let minX = min(a.x, b.x, c.x, d.x, ab.x, cd.x)
// let minY = min(a.y, b.y, c.y, d.y, ab.y, cd.y)
// let maxX = max(a.x, b.x, c.x, d.x, ab.x, cd.x)
// let maxY = max(a.y, b.y, c.y, d.y, ab.y, cd.y)
//
// updateRect = updateRect.union(CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY))
//
// segments.append(Segment(a: a, b: b, c: c, d: d, abWidth: previousWidth, cdWidth: currentWidth))
// }
// return (segments, updateRect)
// }
//
// func drawSegments(_ segments: [Segment], inContext context: CGContext) {
// for segment in segments {
// context.beginPath()
//
// context.setStrokeColor(color.cgColor)
// context.setFillColor(color.cgColor)
//
// context.move(to: segment.b)
//
// let abStartAngle = atan2(segment.b.y - segment.a.y, segment.b.x - segment.a.x)
// context.addArc(
// center: (segment.a + segment.b)/2,
// radius: segment.abWidth/2,
// startAngle: abStartAngle,
// endAngle: abStartAngle + .pi,
// clockwise: true
// )
// context.addLine(to: segment.c)
//
// let cdStartAngle = atan2(segment.c.y - segment.d.y, segment.c.x - segment.d.x)
// context.addArc(
// center: (segment.c + segment.d)/2,
// radius: segment.cdWidth/2,
// startAngle: cdStartAngle,
// endAngle: cdStartAngle + .pi,
// clockwise: true
// )
// context.closePath()
//
// context.fillPath()
// context.strokePath()
// }
// }
//}
//