├── .gitignore ├── Explorations └── CGAngle+Exploration.swift ├── Nonstandard ├── CGAffineTransform+Utility+Nonstandard.swift ├── CGAngle+Utility+Nonstandard.swift ├── CGPoint+Utility+Nonstandard.swift ├── CGRect+Utility+Nonstandard.swift ├── CGScale+Utility+Nonstandard.swift ├── CGSize+Utility+Nonstandard.swift └── CGVector+Utility+Nonstandard.swift ├── Package.swift ├── README.md └── Sources ├── Affine ├── CGAffineTransform+Math.swift └── CGAffineTransform+Utility.swift ├── Angle ├── CGAngle+Math.swift ├── CGAngle+Utility.swift └── CGAngle.swift ├── Explorations ├── CGAngle+Exploration.swift └── CGPoint+PolarAngle.swift ├── Form Migration ├── CGForm.swift ├── CGPoint+Utility+Nonstandard.swift ├── CGScale+Utility+Nonstandard.swift ├── CGSize+Utility+Nonstandard.swift └── CGVector+Utility+Nonstandard.swift ├── Point ├── CGPoint+Math.swift └── CGPoint+Utility.swift ├── Ray └── CGRay.swift ├── Rect ├── CGRect+Math.swift └── CGRect+Utility.swift ├── Scale ├── CGScale+Math.swift ├── CGScale+Utility.swift └── CGScale.swift ├── Segment ├── CGSegment+Math.swift ├── CGSegment+Utility.swift └── CGSegment.swift ├── Size ├── CGSize+Math.swift └── CGSize+Utility.swift ├── Type Changes ├── CGDouble+Utility.swift └── CGFloat+Utility.swift ├── Unlabeled and Shorthand Initializers ├── CGAffineTransform+Unlabeled.swift ├── CGAngle+Unlabeled.swift ├── CGPoint+Unlabeled.swift ├── CGRect+Unlabeled.swift ├── CGScale+Unlabeled.swift ├── CGSize+Unlabeled.swift └── CGVector+Unlabeled.swift └── Vector ├── CGVector+Math.swift └── CGVector+Utility.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary files. 2 | *~ 3 | 4 | # Xcode user data. 5 | xcuserdata 6 | 7 | # Finder metadata 8 | .DS_Store 9 | 10 | # Built site content. 11 | /_site 12 | -------------------------------------------------------------------------------- /Explorations/CGAngle+Exploration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Core Graphics-facing Angles 12 | extension CGAngle { 13 | /// Quadrant 14 | /// 15 | /// - seeAlso: http://mathforum.org/library/drmath/view/65647.html 16 | public enum Quadrant { case I, II, III, IV, axisIandII, axisIIandIII, axisIIIandIV, axisIVandI, undefined } 17 | 18 | /// Returns quadrant 19 | public var quandrant: Quadrant { 20 | switch standardized.radians { 21 | case CGAngle.halfPi: return .axisIandII 22 | case CGAngle.pi, -CGAngle.pi: return .axisIIandIII 23 | case -CGAngle.halfPi: return .axisIIIandIV 24 | case 0: return .axisIVandI 25 | case 0 ..< CGAngle.halfPi: return .I 26 | case 0 ..< CGFloat.infinity: return .II 27 | case -CGFloat.infinity ..< -CGAngle.halfPi: return .III 28 | case -CGAngle.halfPi ..< 0: return .IV 29 | default: return .undefined 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Nonstandard/CGAffineTransform+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Anonymous initializers 12 | extension CGAffineTransform { 13 | public init (_ vector: CGVector) { self.init(vector: vector) } 14 | public init (_ scale: CGScale) { self.init(scale: scale) } 15 | public init (_ angle: CGAngle) { self.init(angle: angle) } 16 | } 17 | -------------------------------------------------------------------------------- /Nonstandard/CGAngle+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGAngle { 12 | /// Initialize implicitly with radians 13 | public init(_ radians: CGFloat) { self.init(radians: radians) } 14 | } 15 | -------------------------------------------------------------------------------- /Nonstandard/CGPoint+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Forced transforms and anonymous initialization 12 | extension CGPoint { 13 | /// Returns vector representation 14 | public func asVector() -> CGVector { return CGVector(dx: x, dy: y) } 15 | 16 | /// Returns size representation 17 | public func asSize() -> CGSize { return CGSize(width: x, height: y) } 18 | 19 | /// Returns scale representation 20 | public func asScale() -> CGScale { return CGScale(sx: x, sy: y) } 21 | 22 | // Returns rectangle representation 23 | public func asRect() -> CGRect { return CGRect(origin: self, size: .zero) } 24 | 25 | /// Unlabeled initializer 26 | public init(_ x: CGFloat, _ y: CGFloat) { (self.x, self.y) = (x, y) } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Nonstandard/CGRect+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Anonymous Initializers 12 | extension CGRect { 13 | 14 | /// Initialize from size 15 | public init(_ size: CGSize) { self.init(origin: .zero, size: size) } 16 | 17 | /// Initialize from origin 18 | public init(_ origin: CGPoint) { self.init(origin: origin, size: .zero) } 19 | 20 | /// Initialize from vector 21 | public init(_ vector: CGVector) { self.init(origin: .zero, size: CGSize(vector.dx, vector.dy)) } 22 | 23 | /// Initialize using width and height 24 | public init(width w: CGFloat, height h: CGFloat) { (self.origin, self.size) = (.zero, CGSize(w, h)) } 25 | 26 | /// Initialize using shorthand width and height 27 | public init(w: CGFloat, h: CGFloat) { (self.origin, self.size) = (.zero, CGSize(w, h)) } 28 | 29 | /// Initialize using unlabeled width and height 30 | public init(_ width: CGFloat, _ height: CGFloat) { (self.origin, self.size) = (.zero, CGSize(width, height)) } 31 | 32 | /// Initialize a la CGRectMake 33 | public init(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) { 34 | self.init(x: x, y: y, width: width, height: height) 35 | } 36 | 37 | /// Initialize with non-literal Swift Double a la CGRectMake 38 | public init(_ x: Double, _ y: Double, _ width: Double, _ height: Double) { 39 | self.init(x: x, y: y, width: width, height: height) 40 | } 41 | 42 | /// Initialize with non-literal Swift Int a la CGRectMake 43 | public init(_ x: Int, _ y: Int, _ width: Int, _ height: Int) { 44 | self.init(x: x, y: y, width: width, height: height) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Nonstandard/CGScale+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGScale { 12 | /// Returns size representation 13 | public func asPoint() -> CGPoint { return CGPoint(x: sx, y: sy) } 14 | 15 | /// Returns vector representation 16 | public func asVector() -> CGVector { return CGVector(dx: sx, dy: sy) } 17 | 18 | /// Returns size representation 19 | public func asSize() -> CGSize { return CGSize(width: sx, height: sy) } 20 | 21 | /// Unlabeled initializer 22 | public init(_ sx: CGFloat, _ sy: CGFloat) { (self.sx, self.sy) = (sx, sy) } 23 | } 24 | -------------------------------------------------------------------------------- /Nonstandard/CGSize+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGSize { 12 | /// Returns point representation 13 | public func asPoint() -> CGPoint { return CGPoint(x: width, y: height) } 14 | 15 | /// Returns vector representation 16 | public func asVector() -> CGVector { return CGVector(dx: width, dy: height) } 17 | 18 | /// Returns scale representation 19 | public func asScale() -> CGScale { return CGScale(sx: width, sy: height) } 20 | 21 | /// Returns rectangle representation 22 | public func asRect() -> CGRect { return CGRect(origin: .zero, size: self) } 23 | 24 | /// Unlabeled initializer 25 | public init(_ width: CGFloat, _ height: CGFloat) { (self.width, self.height) = (width, height) } 26 | 27 | /// Shorthand initializer 28 | public init(w: CGFloat, h: CGFloat) { (self.width, self.height) = (w, h) } 29 | } 30 | -------------------------------------------------------------------------------- /Nonstandard/CGVector+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGVector { 12 | /// Returns point representation 13 | public func asPoint() -> CGPoint { return CGPoint(x: dx, y: dy) } 14 | 15 | /// Returns vector representation 16 | public func asSize() -> CGSize { return CGSize(width: dx, height: dy) } 17 | 18 | /// Returns scale representation 19 | public func asScale() -> CGScale { return CGScale(sx: dx, sy: dy) } 20 | 21 | /// Returns rectangle representation 22 | public func asRect() -> CGRect { return CGRect(origin: .zero, size: self.asSize()) } 23 | 24 | /// Unlabeled initializer 25 | public init(_ dx: CGFloat, _ dy: CGFloat) { (self.dx, self.dy) = (dx, dy) } 26 | } 27 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "SwiftGeometry" 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Geometry 2 | -------------------------------------------------------------------------------- /Sources/Affine/CGAffineTransform+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | /* 12 | 13 | In this imagining, you can add and subtract vectors and angles 14 | but you multiply and divide by scale 15 | 16 | I don't allow math with points, sizes, and rects on the rhs 17 | Instead they apply transforms when they appear on the lhs 18 | 19 | */ 20 | 21 | extension CGAffineTransform { 22 | // MARK: Apply relative elements to transforms 23 | 24 | /// Add vector translation to transform 25 | public static func + (transform: CGAffineTransform, vector: CGVector) -> CGAffineTransform { 26 | return transform.translatedBy(vector) 27 | } 28 | 29 | /// Subtract vector translation from transform 30 | public static func - (transform: CGAffineTransform, vector: CGVector) -> CGAffineTransform { 31 | return transform.translatedBy(-vector) 32 | } 33 | 34 | /// Add angle rotation to transform 35 | public static func + (transform: CGAffineTransform, angle: CGAngle) -> CGAffineTransform { 36 | return transform.rotatedBy(radians: angle.radians) 37 | } 38 | 39 | /// Subtract angle rotation from transform 40 | public static func - (transform: CGAffineTransform, angle: CGAngle) -> CGAffineTransform { 41 | return transform.rotatedBy(radians: -angle.radians) 42 | } 43 | 44 | /// Scale transform by factor 45 | public static func * (transform: CGAffineTransform, factor: CGFloat) -> CGAffineTransform { 46 | return transform.scaledBy(factor: factor) 47 | } 48 | 49 | /// Scale transform by inverse factor 50 | public static func / (transform: CGAffineTransform, factor: CGFloat) -> CGAffineTransform { 51 | return transform.scaledBy(factor: 1 / factor) 52 | } 53 | 54 | /// Scale transform 55 | public static func * (transform: CGAffineTransform, scale: CGScale) -> CGAffineTransform { 56 | return transform.scaledBy(scale) 57 | } 58 | 59 | /// Inverse scale transform 60 | public static func / (transform: CGAffineTransform, scale: CGScale) -> CGAffineTransform { 61 | return transform.scaledBy(scale.inverted()) 62 | } 63 | 64 | // MARK: Apply transforms to concrete elements 65 | 66 | /// Apply transform to point 67 | public static func * (point: CGPoint, transform: CGAffineTransform) -> CGPoint { 68 | return point.applying(transform) 69 | } 70 | 71 | /// Apply transform to size 72 | public static func * (size: CGSize, transform: CGAffineTransform) -> CGSize { 73 | return size.applying(transform) 74 | } 75 | 76 | /// Apply transform to rect 77 | public static func * (rect: CGRect, transform: CGAffineTransform) -> CGRect { 78 | return rect.applying(transform) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/Affine/CGAffineTransform+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | /* 12 | ┌ ┐ 13 | │ a b 0 │ 14 | │ c d 0 │ 15 | │ tx ty 1 │ 16 | └ ┘ 17 | */ 18 | 19 | /// Exposing core properties 20 | extension CGAffineTransform { 21 | /// The transform's translation as an (dx, dy) pair 22 | public var translation: CGVector { 23 | return CGVector(dx: tx, dy: ty) 24 | } 25 | 26 | /// The transform's scale as an (sx, sy) pair 27 | public var scale: CGScale { 28 | return CGScale(sx: sx, sy: sy) 29 | } 30 | 31 | /// The transform's scale along the x axis 32 | public var sx: CGFloat { 33 | return sqrt(a * a + c * c) 34 | } 35 | 36 | /// The transform's scale along the y axis 37 | public var sy: CGFloat { 38 | return sqrt(b * b + d * d) 39 | } 40 | 41 | /// The transform's scale in radians 42 | public var radians: CGFloat { 43 | return atan2(b, a) 44 | } 45 | 46 | /// The transform's scale in degrees 47 | public var degrees: CGFloat { 48 | return radians * 180 / CGFloat(Double.pi) 49 | } 50 | 51 | /// The transform's scale in pi count 52 | public var piCount: CGFloat { 53 | return radians / CGFloat(Double.pi) 54 | } 55 | 56 | /// The transform's scale as angle 57 | public var angle: CGAngle { 58 | return CGAngle(radians: radians) 59 | } 60 | } 61 | 62 | /// Vend flips 63 | extension CGAffineTransform { 64 | 65 | /// Vertical flip 66 | public static var vflip = CGAffineTransform(scaleX: 1, y: -1) 67 | 68 | /// Horizontal flip 69 | public static var hflip = CGAffineTransform(scaleX: -1, y: 1) 70 | } 71 | 72 | // Translation Initializers 73 | extension CGAffineTransform { 74 | 75 | /// Returns a transform which translates by `(tx, ty)': 76 | /// 77 | /// `t = [ 1 0 0 1 tx ty ]` 78 | public init(tx: CGFloat, ty: CGFloat) { 79 | (a, b, c, d, self.tx, self.ty) = (1, 0, 0, 1, tx, ty) 80 | } 81 | 82 | /// Returns a vector-initialized transform which translates by `(dx, dy)': 83 | /// 84 | /// `t = [ 1 0 0 1 dx dy ]` 85 | /// 86 | /// Vectors provide the most natural expression for translation 87 | public init(vector: CGVector) { 88 | (a, b, c, d, self.tx, self.ty) = (1, 0, 0, 1, vector.dx, vector.dy) 89 | } 90 | } 91 | 92 | // Scaling Initializers 93 | extension CGAffineTransform { 94 | 95 | /// Returns a transform which scales by `(sx, sy)`: 96 | /// 97 | /// `t = [ sx, 0, 0, sy, 0, 0 ]` 98 | /// 99 | public init(sx: CGFloat, sy: CGFloat) { 100 | (a, b, c, d, tx, ty) = (sx, 0, 0, sy, 0, 0) 101 | } 102 | 103 | /// Returns a transform which scales using custom CGScale 104 | public init(scale: CGScale) { 105 | (a, b, c, d, tx, ty) = (scale.sx, 0, 0, scale.sy, 0, 0) 106 | } 107 | 108 | /// Returns a transform which scales using single scaling factor 109 | public init(factor: CGFloat) { 110 | (a, b, c, d, tx, ty) = (factor, 0, 0, factor, 0, 0) 111 | } 112 | } 113 | 114 | // Rotation Initializers 115 | extension CGAffineTransform { 116 | /// Returns a transform which rotates by radians 117 | /// 118 | /// `t = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] ` 119 | /// 120 | public init(radians θ: CGFloat) { 121 | let (cosθ, sinθ) = (cos(θ), sin(θ)) 122 | (a, b, c, d, tx, ty) = (cosθ, sinθ, -sinθ, cosθ, 0, 0) 123 | } 124 | 125 | /// Returns a transform which rotates by degrees 126 | /// 127 | /// `t = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] ` 128 | /// 129 | public init(degrees: CGFloat) { 130 | let θ = CGAngle(degrees: degrees).radians 131 | self.init(radians: θ) 132 | } 133 | 134 | /// Returns a transform which rotates by count * π radians 135 | /// For example, 45 degrees is 0.25 piCount. 180 is 1. 360 is 2. 136 | /// 137 | /// `t = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] ` 138 | /// 139 | public init(piCount: CGFloat) { 140 | self.init(radians: piCount * CGAngle.pi) 141 | } 142 | 143 | /// Returns a transform initialized with a custom CGAngle type 144 | public init(angle: CGAngle) { 145 | self.init(radians: angle.radians) 146 | } 147 | } 148 | 149 | // Translations 150 | extension CGAffineTransform { 151 | /// Translate self by (tx, ty) and return the results. 152 | /// This is what `translatedBy(x:, y:)` should be 153 | public func translatedBy(tx: CGFloat, ty: CGFloat) -> CGAffineTransform { 154 | return self.translatedBy(x: tx, y: ty) 155 | } 156 | 157 | /// Translate self by a vector and return the results. 158 | /// The label is omitted as this is the natural translation argument 159 | public func translatedBy(_ vector: CGVector) -> CGAffineTransform { 160 | return self.translatedBy(tx: vector.dx, ty: vector.dy) 161 | } 162 | } 163 | 164 | // Scaling 165 | extension CGAffineTransform { 166 | /// Scaling by (sx, sy), which is what `scaledBy(x:,y:)` should be 167 | public func scaledBy(sx: CGFloat, sy: CGFloat) -> CGAffineTransform { 168 | return self.scaledBy(x: sx, y: sy) 169 | } 170 | 171 | /// Scales s by (factor, factor) and returns the results 172 | public func scaledBy(factor: CGFloat) -> CGAffineTransform { 173 | return self.scaledBy(sx: factor, sy: factor) 174 | } 175 | 176 | /// Scales s by (sx, sy) and returns the results 177 | /// The label is omitted as this is the natural scaling argument 178 | public func scaledBy(_ scale: CGScale) -> CGAffineTransform { 179 | return self.scaledBy(sx: scale.sx, sy: scale.sy) 180 | } 181 | } 182 | 183 | // Rotation 184 | extension CGAffineTransform { 185 | /// Rotation by radians, which is what `rotated(by:)` should be 186 | public func rotatedBy(radians: CGFloat) -> CGAffineTransform { 187 | return self.rotated(by: radians) 188 | } 189 | 190 | /// Rotation by degrees, using what should be the standard approach 191 | public func rotatedBy(degrees: CGFloat) -> CGAffineTransform { 192 | return self.rotated(by: degrees * CGFloat(Double.pi) / 180) 193 | } 194 | 195 | /// Rotation by π count, using what should be the standard approach 196 | public func rotatedBy(piCount: CGFloat) -> CGAffineTransform { 197 | return self.rotated(by: piCount * CGFloat(Double.pi)) 198 | } 199 | } 200 | 201 | // Equality 202 | // 203 | // It would be easy to extend this to == vector, == scale, and == angle 204 | // but I don't see a compelling use case. I have omitted them at this time 205 | // 206 | extension CGAffineTransform { 207 | // Basic comparison. This is otherwise available via 208 | // `public func __equalTo(_ t2: CGAffineTransform) -> Bool` 209 | public static func == (lhs: CGAffineTransform, rhs: CGAffineTransform) -> Bool { 210 | return lhs.a == rhs.a && lhs.b == rhs.b && 211 | lhs.c == rhs.c && lhs.d == rhs.d && 212 | lhs.tx == rhs.tx && lhs.ty == rhs.ty 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Sources/Angle/CGAngle+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Vending Common angles 12 | extension CGAngle { 13 | /// Fractional pi: 30°, 45°, 60°, 90°, 120°, 135°, 150° 14 | public static let (sixthPi, quarterPi, thirdPi, halfPi, twothirdsPi, threeQuartersPi, fiveSixthsPi) = 15 | (pi / 6, 0.25 * pi, pi / 3, 0.5 * pi, 2 * pi / 3, 3 * pi / 4, 5 * pi / 6) 16 | } 17 | 18 | extension CGAngle { 19 | private func apply(_ f: (Double) -> Double) -> CGFloat { return CGFloat(f(Double(radians))) } 20 | 21 | /// Returns sine 22 | public var sin: CGFloat { return apply(Foundation.sin) } 23 | 24 | /// Returns cosine 25 | public var cos: CGFloat { return apply(Foundation.cos) } 26 | 27 | /// Returns tangent 28 | public var tan: CGFloat { return apply(Foundation.tan) } 29 | 30 | /// Returns cosecant 31 | public var csc: CGFloat { return 1 / sin } 32 | 33 | /// Returns secant 34 | public var sec: CGFloat { return 1 / cos } 35 | 36 | /// Returns cotangent 37 | public var cot: CGFloat { return 1 / tan } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Angle/CGAngle+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | /* 12 | 13 | I've chosen not to include isEqual(to: angle) or comparisons 14 | 15 | */ 16 | 17 | extension CGAngle { 18 | // Negation 19 | func negated() -> CGAngle { 20 | return CGAngle(radians: -radians) 21 | } 22 | 23 | // Negation 24 | static prefix func - (angle: CGAngle) -> CGAngle { 25 | return angle.negated() 26 | } 27 | } 28 | 29 | extension CGAngle { 30 | /// Sign 31 | public var sign: CGFloat.Sign { return radians.sign } 32 | 33 | /// Returns angle between 0 and 2π 34 | public var positive: CGAngle { 35 | var r = radians % CGAngle.tau 36 | if r < 0 { r = r + CGAngle.tau } 37 | return CGAngle(radians: r) 38 | } 39 | 40 | /// Returns angle between -π and π 41 | public var standardized: CGAngle { 42 | let r = positive.radians 43 | switch positive.radians { 44 | case 0 ... CGAngle.pi: return CGAngle(radians: r) 45 | default: return CGAngle(radians: r - CGAngle.tau) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Angle/CGAngle.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Core Graphics-facing Angles 12 | public struct CGAngle: CustomStringConvertible { 13 | 14 | /// The pi constant 15 | public static let (pi, π) = (CGFloat(Double.pi), CGFloat(Double.pi)) 16 | 17 | /// The tau constant 18 | public static let (tau, τ) = (2 * pi, 2 * pi) 19 | 20 | /// Init 21 | public init() { _radians = 0 } 22 | 23 | /// Initialize with radians 24 | public init(radians: CGFloat) { _radians = radians } 25 | 26 | /// Initialize with degrees 27 | public init(degrees: CGFloat) { _radians = degrees * CGAngle.pi / 180.0 } 28 | 29 | /// Initialize with count of π's 30 | public init(piCount: CGFloat) { _radians = piCount * CGAngle.pi } 31 | 32 | /// Express angle in degrees 33 | public var degrees: CGFloat { return _radians * 180.0 / CGAngle.pi } 34 | 35 | /// Express angles as a count of pi 36 | public var piCount: CGFloat { return _radians / CGAngle.pi } 37 | 38 | /// Express angle in (native) radians 39 | public var radians: CGFloat { return _radians } 40 | 41 | /// String convertible support 42 | public var description: String { 43 | return "\(degrees)°, \(piCount)π, \(radians) rads" 44 | } 45 | 46 | /// Internal radian store 47 | private let _radians: CGFloat 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Sources/Explorations/CGAngle+Exploration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Core Graphics-facing Angles 12 | extension CGAngle { 13 | /// Quadrant 14 | /// 15 | /// - seeAlso: http://mathforum.org/library/drmath/view/65647.html 16 | public enum Quadrant { case I, II, III, IV, axisIandII, axisIIandIII, axisIIIandIV, axisIVandI, undefined } 17 | 18 | /// Returns quadrant 19 | public var quandrant: Quadrant { 20 | switch standardized.radians { 21 | case CGAngle.halfPi: return .axisIandII 22 | case CGAngle.pi, -CGAngle.pi: return .axisIIandIII 23 | case -CGAngle.halfPi: return .axisIIIandIV 24 | case 0: return .axisIVandI 25 | case 0 ..< CGAngle.halfPi: return .I 26 | case 0 ..< CGFloat.infinity: return .II 27 | case -CGFloat.infinity ..< -CGAngle.halfPi: return .III 28 | case -CGAngle.halfPi ..< 0: return .IV 29 | default: return .undefined 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Explorations/CGPoint+PolarAngle.swift: -------------------------------------------------------------------------------- 1 | public extension CGPoint { 2 | 3 | public init(polarAngle: CGFloat, length: CGFloat) { 4 | self.x = cos(polarAngle) * (length) 5 | self.y = sin(polarAngle) * -(length) 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /Sources/Form Migration/CGForm.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | 12 | extension CGPoint { 13 | public var vectorForm: CGVector { return CGVector(dx: x, dy: y) } 14 | public var sizeForm: CGSize { return CGSize(width: x, height: y) } 15 | } 16 | 17 | extension CGSize { 18 | public var vectorForm: CGVector { return CGVector(dx: width, dy: height) } 19 | public var pointForm: CGPoint { return CGPoint(x: width, y: height) } 20 | } 21 | 22 | extension CGVector { 23 | public var pointForm: CGPoint { return CGPoint(x: dx, y: dy) } 24 | public var sizeForm: CGSize { return CGSize(width: dx, height: dy) } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Form Migration/CGPoint+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Forced transforms 12 | extension CGPoint { 13 | /// Returns vector representation 14 | public func asVector() -> CGVector { return CGVector(dx: x, dy: y) } 15 | 16 | /// Returns size representation 17 | public func asSize() -> CGSize { return CGSize(width: x, height: y) } 18 | 19 | /// Returns scale representation 20 | public func asScale() -> CGScale { return CGScale(sx: x, sy: y) } 21 | 22 | // Returns rectangle representation 23 | public func asRect() -> CGRect { return CGRect(origin: self, size: .zero) } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Sources/Form Migration/CGScale+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGScale { 12 | /// Returns size representation 13 | public func asPoint() -> CGPoint { return CGPoint(x: sx, y: sy) } 14 | 15 | /// Returns vector representation 16 | public func asVector() -> CGVector { return CGVector(dx: sx, dy: sy) } 17 | 18 | /// Returns size representation 19 | public func asSize() -> CGSize { return CGSize(width: sx, height: sy) } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Form Migration/CGSize+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGSize { 12 | /// Returns point representation 13 | public func asPoint() -> CGPoint { return CGPoint(x: width, y: height) } 14 | 15 | /// Returns vector representation 16 | public func asVector() -> CGVector { return CGVector(dx: width, dy: height) } 17 | 18 | /// Returns scale representation 19 | public func asScale() -> CGScale { return CGScale(sx: width, sy: height) } 20 | 21 | /// Returns rectangle representation 22 | public func asRect() -> CGRect { return CGRect(origin: .zero, size: self) } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Form Migration/CGVector+Utility+Nonstandard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGVector { 12 | /// Returns point representation 13 | public func asPoint() -> CGPoint { return CGPoint(x: dx, y: dy) } 14 | 15 | /// Returns vector representation 16 | public func asSize() -> CGSize { return CGSize(width: dx, height: dy) } 17 | 18 | /// Returns scale representation 19 | public func asScale() -> CGScale { return CGScale(sx: dx, sy: dy) } 20 | 21 | /// Returns rectangle representation 22 | public func asRect() -> CGRect { return CGRect(origin: .zero, size: self.asSize()) } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Point/CGPoint+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGPoint { 12 | // MARK: Negation 13 | 14 | /// Returns negative extents 15 | public func negated() -> CGPoint { return CGPoint(x: -x, y: -y) } 16 | 17 | /// Negate point 18 | public static prefix func - (point: CGPoint) -> CGPoint { 19 | return point.negated() 20 | } 21 | 22 | // MARK: Rect 23 | 24 | /// Point addition 25 | public static func + (p1: CGPoint, p2: CGPoint) -> CGRect { 26 | let size = CGSize(width: p2.x - p1.x, height: p2.y - p1.y) 27 | return CGRect(origin: p1, size: size).standardized 28 | } 29 | 30 | /// Point + size addition 31 | public static func + (point: CGPoint, size: CGSize) -> CGRect { 32 | return CGRect(origin: point, size: size).standardized 33 | } 34 | 35 | /// Returns standardized rectangle by subtracting size from point 36 | public static func - (point: CGPoint, size: CGSize) -> CGRect { 37 | return CGRect(origin: point, size: size.negated()).standardized 38 | } 39 | 40 | // MARK: Vectors 41 | 42 | /// Returns vector formed by subtracting point from point 43 | public static func - (point1: CGPoint, point2: CGPoint) -> CGVector { 44 | return CGVector(dx: point1.x - point2.x, dy: point1.y - point2.y) 45 | } 46 | 47 | /// Returns point formed by adding vector to point 48 | public static func + (point: CGPoint, vector: CGVector) -> CGPoint { 49 | return CGPoint(x: point.x + vector.dx, y: point.y + vector.dy) 50 | } 51 | 52 | /// Returns point formed by subtracting vector from point 53 | public static func - (point: CGPoint, vector: CGVector) -> CGPoint { 54 | return CGPoint(x: point.x - vector.dx, y: point.y - vector.dy) 55 | } 56 | 57 | // MARK: Scaling 58 | 59 | /// Returns point after applying scaling factor through multiplication 60 | public static func * (point: CGPoint, factor: CGFloat) -> CGPoint { 61 | return CGPoint(x: point.x * factor, y: point.y * factor) 62 | } 63 | 64 | /// Returns point after applying scaling factor through multiplication 65 | public static func * (point: CGPoint, scale: CGScale) -> CGPoint { 66 | return CGPoint(x: point.x * scale.sx, y: point.y * scale.sy) 67 | } 68 | 69 | /// Returns point after applying scaling factor through division 70 | public static func / (point: CGPoint, factor: CGFloat) -> CGPoint { 71 | return CGPoint(x: point.x / factor, y: point.y / factor) 72 | } 73 | 74 | /// Returns point after applying scaling factor through division 75 | public static func / (point: CGPoint, scale: CGScale) -> CGPoint { 76 | return CGPoint(x: point.x / scale.sx, y: point.y / scale.sy) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/Point/CGPoint+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Point 12 | extension CGPoint { 13 | /// Returns integral version 14 | /// Thanks Mike Ash for the floor workaround 15 | public var integral: CGPoint { 16 | return CGPoint( 17 | x: CoreGraphics.floor(x), 18 | y: CoreGraphics.floor(y)) 19 | } 20 | 21 | /// Returns max position 22 | public var max: CGFloat { return fmax(x, y) } 23 | 24 | /// Returns min position 25 | public var min: CGFloat { return fmin(x, y) } 26 | 27 | /// Constricts position in rect 28 | /// - parameter rect: the target rectangle 29 | /// - returns: the point clamped to the target rectangle 30 | public func clamp(to rect: CGRect) -> CGPoint { 31 | func _clamp( 32 | _ value: CGFloat, 33 | minValue: CGFloat, 34 | maxValue: CGFloat) -> CGFloat { 35 | return fmax(minValue, fmin(maxValue, value)) 36 | } 37 | let px: CGFloat = _clamp(x, minValue: rect.minX, maxValue: rect.maxX) 38 | let py: CGFloat = _clamp(y, minValue: rect.minY, maxValue: rect.maxY) 39 | return CGPoint(x: px, y: py) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Ray/CGRay.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | /// You can call me "Ray" 12 | public struct CGRay { 13 | /// Ray's origin 14 | public var origin: CGPoint 15 | 16 | /// Ray's unit direction 17 | public var direction: CGVector { 18 | get { return _direction } 19 | set { _direction = CGRay._normalize(vector: newValue) } 20 | } 21 | 22 | /// Establish new instance with origin and direction 23 | public init(origin: CGPoint, direction vector: CGVector) { 24 | self.origin = origin 25 | self._direction = CGRay._normalize(vector: vector) 26 | } 27 | 28 | /// Private normalized vector 29 | private var _direction: CGVector 30 | 31 | /// Return normalized vector 32 | private static func _normalize(vector: CGVector) -> CGVector { 33 | assert(vector.dx != 0 || vector.dy != 0, "Vector must be normalizable") 34 | let magnitude = hypot(vector.dx, vector.dy) 35 | return CGVector(dx: vector.dx / magnitude, 36 | dy: vector.dy / magnitude) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Rect/CGRect+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGRect { 12 | 13 | // Rect math. Is there a natural operator for inset? 14 | // What about scale in place? Around center? 15 | 16 | /// Offset rectangle by vector 17 | public static func + (rect: CGRect, vector: CGVector) -> CGRect { 18 | return rect.offsetBy(dx: vector.dx, dy: vector.dy) 19 | } 20 | 21 | /// Offset rectangle by -vector 22 | public static func - (rect: CGRect, vector: CGVector) -> CGRect { 23 | return rect.offsetBy(dx: -vector.dx, dy: -vector.dy) 24 | } 25 | 26 | /// Add size to rectangle 27 | public static func + (rect: CGRect, size: CGSize) -> CGRect { 28 | return CGRect(origin: rect.origin, size: rect.size + size) 29 | } 30 | 31 | /// Scale rectangle size by scale factor 32 | public static func * (rect: CGRect, factor: CGFloat) -> CGRect { 33 | return CGRect(origin: rect.origin, size: rect.size * factor) 34 | } 35 | 36 | /// Scale rectangle size by scale 37 | public static func * (rect: CGRect, scale: CGScale) -> CGRect { 38 | return CGRect(origin: rect.origin, size: rect.size * scale) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Rect/CGRect+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Rectangle Positions 12 | extension CGRect { 13 | /// Sets and returns top left corner 14 | public var topLeft: CGPoint { 15 | get { return origin } 16 | set { origin = newValue } 17 | } 18 | 19 | /// Sets and returns top center point 20 | public var topCenter: CGPoint { 21 | get { return CGPoint(x: midX, y: minY) } 22 | set { origin = CGPoint(x: newValue.x - width / 2, 23 | y: newValue.y) } 24 | } 25 | 26 | /// Returns top right corner 27 | public var topRight: CGPoint { 28 | get { return CGPoint(x: maxX, y: minY) } 29 | set { origin = CGPoint(x: newValue.x - width, 30 | y: newValue.y) } 31 | } 32 | 33 | /// Returns center left point 34 | public var centerLeft: CGPoint { 35 | get { return CGPoint(x: minX, y: midY) } 36 | set { origin = CGPoint(x: newValue.x, 37 | y: newValue.y - height / 2) } 38 | } 39 | 40 | /// Sets and returns center point 41 | public var center: CGPoint { 42 | get { return CGPoint(x: midX, y: midY) } 43 | set { origin = CGPoint(x: newValue.x - width / 2, 44 | y: newValue.y - height / 2) } 45 | } 46 | 47 | /// Returns center right point 48 | public var centerRight: CGPoint { 49 | get { return CGPoint(x: maxX, y: midY) } 50 | set { origin = CGPoint(x: newValue.x - width, 51 | y: newValue.y - height / 2) } 52 | } 53 | 54 | /// Returns bottom left corner 55 | public var bottomLeft: CGPoint { 56 | get { return CGPoint(x: minX, y: maxY) } 57 | set { origin = CGPoint(x: newValue.x, 58 | y: newValue.y - height) } 59 | } 60 | 61 | /// Returns bottom center point 62 | public var bottomCenter: CGPoint { 63 | get { return CGPoint(x: midX, y: maxY) } 64 | set { origin = CGPoint(x: newValue.x - width / 2, 65 | y: newValue.y - height) } 66 | } 67 | 68 | /// Returns bottom right corner 69 | public var bottomRight: CGPoint { 70 | get { return CGPoint(x: maxX, y: maxY) } 71 | set { origin = CGPoint(x: newValue.x - width, 72 | y: newValue.y - height) } 73 | } 74 | } 75 | 76 | // Exposed fields without indirection 77 | extension CGRect { 78 | /// Returns origin x 79 | public var x: CGFloat { 80 | get { return origin.x } 81 | set { origin.x = newValue } 82 | } 83 | 84 | /// Returns origin y 85 | public var y: CGFloat { 86 | get { return origin.y } 87 | set { origin.y = newValue } 88 | } 89 | 90 | /// Returns size width 91 | public var width: CGFloat { 92 | get { return size.width } // normally built-in 93 | set { size.width = newValue } 94 | } 95 | 96 | /// Returns size height 97 | public var height: CGFloat { 98 | get { return size.height } // normally built-in 99 | set { size.height = newValue } 100 | } 101 | } 102 | 103 | // Rectangle Properties 104 | 105 | extension CGRect { 106 | /// Return rectangle's area 107 | public var area: CGFloat { return width * height } 108 | 109 | /// Return rectangle's diagonal extent 110 | public var diagonalExtent: CGFloat { return hypot(width, height) } 111 | } 112 | 113 | // Moving Rects 114 | extension CGRect { 115 | 116 | /// Returns rect translated to origin 117 | public var zeroed: CGRect { 118 | return CGRect(origin: .zero, size: size) 119 | } 120 | 121 | /// Constructs a rectangle around a center with the given size 122 | public static func around(_ center: CGPoint, size: CGSize) -> CGRect { 123 | var rect = CGRect(origin: .zero, size: size) 124 | rect.center = center 125 | return rect 126 | } 127 | 128 | /// Returns rect centered around point 129 | func around(_ center: CGPoint) -> CGRect { 130 | return CGRect.around(center, size: size) 131 | } 132 | 133 | /// Returns rect with coaligned centers 134 | public func centered(in mainRect: CGRect) -> CGRect { 135 | return CGRect.around(mainRect.center, size: size) 136 | } 137 | 138 | } 139 | 140 | // Fitting and Filling 141 | extension CGRect { 142 | /// Calculates the fitting aspect scale between a source size 143 | /// and a destination rectangle 144 | public func fittingAspect(of sourceSize: CGSize) -> CGFloat { 145 | let scaleW = width / sourceSize.width 146 | let scaleH = height / sourceSize.height 147 | return fmin(scaleW, scaleH) 148 | } 149 | 150 | /// Calculates the filling aspect scale between a source size 151 | /// and a destination rectangle 152 | public func fillingAspect(of sourceSize: CGSize) -> CGFloat { 153 | let scaleW = width / sourceSize.width 154 | let scaleH = height / sourceSize.height 155 | return fmax(scaleW, scaleH) 156 | } 157 | 158 | /// Fitting into a destination rectangle 159 | public func fitting(_ destination: CGRect) -> CGRect { 160 | let aspect = destination.fittingAspect(of: size) 161 | let targetSize = CGSize(width: width * aspect, height: height * aspect) 162 | return CGRect.around(destination.center, size: targetSize) 163 | } 164 | 165 | /// Filling a destination rectangle 166 | public func filling(_ destination: CGRect) -> CGRect { 167 | let aspect = destination.fillingAspect(of: size) 168 | let targetSize = CGSize(width: width * aspect, height: height * aspect) 169 | return CGRect.around(destination.center, size: targetSize) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Sources/Scale/CGScale+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGScale { 12 | /// Invert the scale 13 | public func inverted() -> CGScale { 14 | let inverseX = (sx == 0) ? CGFloat.infinity : (1 / sx) 15 | let inverseY = (sy == 0) ? CGFloat.infinity : (1 / sy) 16 | return CGScale(sx: inverseX, sy: inverseY) 17 | } 18 | 19 | /// Returns negative extents 20 | public func negated() -> CGScale { return CGScale(sx: -sx, sy: -sy) } 21 | 22 | /// Negate scale 23 | public static prefix func - (scale: CGScale) -> CGScale { 24 | return scale.inverted() // should this be negated? 25 | } 26 | 27 | /// Returns scale scaled by scale factor 28 | public static func * (scale: CGScale, factor: CGFloat) -> CGScale { 29 | return CGScale(sx: scale.sx * factor, sy: scale.sy * factor) 30 | } 31 | 32 | /// Returns scaled vector 33 | public static func * (scale: CGScale, vector: CGVector) -> CGVector { 34 | return CGVector(dx: vector.dx * scale.sx, dy: vector.dy * scale.sy) 35 | } 36 | 37 | /// Returns scaled size 38 | public static func * (scale: CGScale, size: CGSize) -> CGSize { 39 | return CGSize(width: size.width * scale.sx, height: size.height * scale.sy) 40 | } 41 | 42 | /// Returns scaled rect 43 | public static func * (scale: CGScale, rect: CGRect) -> CGRect { 44 | return CGRect(origin: rect.origin, size: scale * rect.size) 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Sources/Scale/CGScale+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // This extension intentionally left blank 12 | -------------------------------------------------------------------------------- /Sources/Scale/CGScale.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | /// Core Graphics-facing scale, currently missing 12 | /// from the CG structure suite 13 | public struct CGScale { 14 | 15 | /// Scale 16 | public var (sx, sy): (CGFloat, CGFloat) 17 | 18 | /// Returns default scale ratios of 1:1 19 | public init() { self.init(sx: 1, sy: 1) } 20 | 21 | /// The CGScale version of .zero 22 | public var one: CGScale { return CGScale() } 23 | 24 | /// Initialize with caller-supplied values 25 | public init(sx: CGFloat, sy: CGFloat) { 26 | (self.sx, self.sy) = (sx, sy) 27 | } 28 | 29 | /// Initialize with a single scale factor 30 | public init(factor: CGFloat) { 31 | (self.sx, self.sy) = (factor, factor) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Segment/CGSegment+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGSegment { 12 | /// Multiply by factor 13 | public static func * (segment: CGSegment, factor: CGFloat) -> CGSegment { 14 | return CGSegment(point: segment.p1, vector: segment.vector * factor) 15 | } 16 | 17 | /// Offset by percent travel 18 | public func travel(by factor: CGFloat) -> CGPoint { 19 | return p1 + vector * factor 20 | } 21 | 22 | /// Returns the segment midpoint 23 | public var midpoint: CGPoint { return self.travel(by: 0.5) } 24 | } 25 | 26 | // Segment-Point Math 27 | 28 | extension CGSegment { 29 | /// Returns a point `extent` along the midpoint perpendicular 30 | public func perpendicular(extent: CGFloat ) -> CGPoint { 31 | let testPoint = CGPoint(x: p1.y + ((p1.x == p1.y) ? 5.0: 0.0), y: p1.x) 32 | let point = testPoint.projected(toPerpendicular: self) 33 | let vector = (point - midpoint).unitForm * extent 34 | return midpoint + vector 35 | } 36 | } 37 | 38 | extension CGPoint { 39 | /// Returns a point projected to a line segment 40 | public func projected(toLine segment: CGSegment) -> CGPoint { 41 | let lengthSquared = segment.length * segment.length 42 | let vectorAB = segment.p2 - segment.p1 43 | let vectorAP = self - segment.p1 44 | let t = (vectorAP.dx * vectorAB.dx + vectorAP.dy * vectorAB.dy) / lengthSquared 45 | let projection = segment.p1 + vectorAB * t 46 | return projection 47 | } 48 | 49 | /// Returns the distance from the point to a line segment 50 | public func distance(to segment: CGSegment) -> CGFloat { 51 | let projectedPoint = projected(toLine: segment) 52 | return CGSegment(p1: self, p2: projectedPoint).length 53 | } 54 | 55 | /// Returns the distance from a point to another point 56 | public func distance(to point: CGPoint) -> CGFloat { 57 | return CGSegment(p1: self, p2: point).length 58 | } 59 | 60 | /// Returns a point mirrored across a line segment 61 | public func mirrored(acrossLine segment: CGSegment) -> CGPoint { 62 | let intersection = projected(toLine: segment) 63 | return intersection + (intersection - self) 64 | } 65 | 66 | /// Returns a point projected to the perpendicular of a line segment 67 | public func projected(toPerpendicular segment: CGSegment) -> CGPoint { 68 | let midpoint = segment.midpoint 69 | let intersection = projected(toLine: segment) 70 | let vector = self - intersection 71 | return midpoint + vector 72 | } 73 | 74 | /// Returns a point mirrored across the perpendicular of a line segment 75 | public func mirrored(acrossPerpendicular segment: CGSegment) -> CGPoint { 76 | let perpPoint = projected(toPerpendicular: segment) 77 | let midline = CGSegment(p1: perpPoint, p2: segment.midpoint) 78 | return mirrored(acrossLine: midline) 79 | } 80 | } 81 | 82 | // Half Plane Tests 83 | 84 | extension CGSegment { 85 | 86 | /// Perform the half plane test on a point 87 | public func halfPlaneTest(at p: CGPoint) -> CGFloat { 88 | let (dx, dy) = (p2.x - p1.x, p2.y - p1.y) 89 | let (tx, ty) = (p.x - p1.x, p.y - p1.y) 90 | return dx * ty - tx * dy 91 | } 92 | 93 | /// Return yes if the segment crosses another segment 94 | public func crosses(segment: CGSegment) -> Bool { 95 | if p1.equalTo(segment.p1) { return true } 96 | if p1.equalTo(segment.p2) { return true } 97 | if p2.equalTo(segment.p1) { return true } 98 | if p2.equalTo(segment.p2) { return true } 99 | 100 | let hp1 = halfPlaneTest(at: segment.p1) < 0.0 101 | let hp2 = halfPlaneTest(at: segment.p2) < 0.0 102 | 103 | let hp3 = segment.halfPlaneTest(at: p1) < 0.0 104 | let hp4 = segment.halfPlaneTest(at: p2) < 0.0 105 | 106 | if hp1 == hp2 { return false } 107 | if hp3 == hp4 { return false } 108 | return true 109 | } 110 | 111 | /// Returns the point at which a segment crosses another segment 112 | public func intersection(withLine segment: CGSegment) -> CGPoint? { 113 | if !crosses(segment: segment) { return nil } 114 | let tx = (p1.x * p2.y - p1.y * p2.x) * (segment.p1.x - segment.p2.x) - 115 | (segment.p1.x * segment.p2.y - segment.p1.y * segment.p2.x) * (p1.x - p2.x) 116 | let ty = (p1.x * p2.y - p1.y * p2.x) * (segment.p1.y - segment.p2.y) - 117 | (segment.p1.x * segment.p2.y - segment.p1.y * segment.p2.x) * (p1.y - p2.y) 118 | let d = (p1.x - p2.x) * (segment.p1.y - segment.p2.y) - 119 | (p1.y - p2.y) * (segment.p1.x - segment.p2.x) 120 | return CGPoint(x: tx / d, y: ty / d) 121 | } 122 | } 123 | 124 | // Circle From Segment 125 | 126 | extension CGSegment { 127 | /// Return a circle's center that passes through both segment endpoints 128 | public func centerWithRadius(radius: CGFloat, clockwise: Bool) -> CGPoint? { 129 | let pa = clockwise ? p1: p2 130 | let pb = clockwise ? p2: p1 131 | let segment = CGSegment(p1: pa, p2: pb) 132 | 133 | let diameter = radius * 2.0 134 | if segment.length > diameter { return nil } 135 | 136 | let midpoint = segment.midpoint 137 | let x = midpoint.x + sqrt(pow(radius, 2) - pow(segment.length / 2.0, 2)) * (pa.y - pb.y) / segment.length 138 | let y = midpoint.y + sqrt(pow(radius, 2) - pow(segment.length / 2.0, 2)) * (pb.x - pa.x) / segment.length 139 | return CGPoint(x: x, y: y) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Sources/Segment/CGSegment+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | line segment geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // This extension intentionally left blank 12 | 13 | -------------------------------------------------------------------------------- /Sources/Segment/CGSegment.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | line segment geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | 12 | // Line segments 13 | 14 | public struct CGSegment { 15 | 16 | /// Initialize with two points 17 | public init(p1: CGPoint, p2: CGPoint) { 18 | (self.p1, self.p2) = (p1, p2) 19 | } 20 | 21 | /// Initialize with a point and a vector 22 | public init(point: CGPoint, vector: CGVector) { 23 | (self.p1, self.p2) = (point, point + vector) 24 | } 25 | 26 | /// Default initializer 27 | public init() { (self.p1, self.p2) = (.zero, .zero) } 28 | 29 | /// Store two endpoints 30 | public var (p1, p2): (CGPoint, CGPoint) 31 | 32 | /// Returns segment dy 33 | public var dy: CGFloat { return p2.y - p1.y } 34 | 35 | /// Returns segment dx 36 | public var dx: CGFloat { return p2.x - p1.x } 37 | 38 | /// Returns the segment slope 39 | public var slope: CGFloat { return dy / dx } 40 | 41 | /// Returns the segment length 42 | public var length: CGFloat { return hypot(dx, dy) } 43 | 44 | /// Returns vector representation 45 | public var vector: CGVector { return p2 - p1 } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Sources/Size/CGSize+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGSize { 12 | /// Returns integral version of size 13 | public var integral: CGSize { 14 | return CGSize( 15 | width: CoreGraphics.floor(width), 16 | height: CoreGraphics.floor(height)) 17 | } 18 | 19 | /// Returns max extent 20 | public var max: CGFloat { return fmax(width, height) } 21 | 22 | /// Returns min extent 23 | public var min: CGFloat { return fmin(width, height) } 24 | } 25 | 26 | extension CGSize { 27 | 28 | // MARK: Scaling 29 | 30 | /// Scales size by multiplying factor 31 | public static func * (size: CGSize, factor: CGFloat) -> CGSize { 32 | return CGSize(width: size.width * factor, height: size.height * factor) 33 | } 34 | 35 | /// Scales size by multiplication 36 | public static func * (size: CGSize, scale: CGScale) -> CGSize { 37 | return CGSize(width: size.width * scale.sx, height: size.height * scale.sy) 38 | } 39 | 40 | /// Scales size by dividing factor 41 | public static func / (size: CGSize, factor: CGFloat) -> CGSize { 42 | return CGSize(width: size.width / factor, height: size.height / factor) 43 | } 44 | 45 | /// Scales size by division 46 | public static func / (size: CGSize, scale: CGScale) -> CGSize { 47 | return CGSize(width: size.width / scale.sx, height: size.height / scale.sy) 48 | } 49 | 50 | // MARK: Addition and Subtraction 51 | 52 | /// Adds sizes 53 | public static func + (size1: CGSize, size2: CGSize) -> CGSize { 54 | return CGSize(width: size1.width + size2.width, height: size1.height + size2.height) 55 | } 56 | 57 | /// Subtract sizes 58 | public static func - (size1: CGSize, size2: CGSize) -> CGSize { 59 | return CGSize(width: size1.width - size2.width, height: size1.height - size2.height) 60 | } 61 | 62 | /// Add scalar to size 63 | public static func + (size: CGSize, amount: CGFloat) -> CGSize { 64 | return CGSize(width: size.width + amount, height: size.height + amount) 65 | } 66 | 67 | /// Subtract scalar from size 68 | public static func - (size: CGSize, amount: CGFloat) -> CGSize { 69 | return CGSize(width: size.width - amount, height: size.height - amount) 70 | } 71 | 72 | /// Add vector to size 73 | public static func + (size: CGSize, vector: CGVector) -> CGSize { 74 | return CGSize(width: size.width + vector.dx, height: size.height + vector.dy) 75 | } 76 | 77 | /// Subtract vector from size 78 | public static func - (size: CGSize, vector: CGVector) -> CGSize { 79 | return CGSize(width: size.width - vector.dx, height: size.height - vector.dy) 80 | } 81 | 82 | /// Add size + rectangle (Should this be removed?) 83 | public static func + (size: CGSize, rect: CGRect) -> CGRect { 84 | return CGRect(origin: rect.origin, size: size + rect.size) 85 | } 86 | 87 | /// Add size + point = rect 88 | public static func + (size: CGSize, point: CGPoint) -> CGRect { 89 | return CGRect(origin: point, size: size) 90 | } 91 | 92 | /// Returns negative extents 93 | public func negated() -> CGSize { return CGSize(width: -self.width, height: -self.height) } 94 | 95 | /// Negate size 96 | public static prefix func - (size: CGSize) -> CGSize { 97 | return size.negated() 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /Sources/Size/CGSize+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry Math 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // This extension intentionally left blank 12 | -------------------------------------------------------------------------------- /Sources/Type Changes/CGDouble+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Core Graphics-facing Double utility 12 | extension Double { 13 | 14 | /// Returns self as CGFloat 15 | public var cgFloat: CGFloat { return CGFloat(self) } 16 | 17 | /// Rounds to n decimal places, applying 18 | /// "schoolbook" rounding rules 19 | public func rounded(places: Int) -> Double { 20 | let degree = pow(10.0, Double(places)) 21 | return (self * degree).rounded() / degree 22 | } 23 | } 24 | 25 | // Custom CGAngle Utilities for Double 26 | extension Double { 27 | 28 | /// Returns CGAngle of `self` degrees, e.g. `60.cgDegrees` 29 | public var cgDegrees: CGAngle { return CGAngle(degrees: self.cgFloat) } 30 | 31 | /// Return an angle of `self` radians, e.g. `Double.pi.cgRadians` 32 | public var cgRadians: CGAngle { return CGAngle(radians: self.cgFloat) } 33 | 34 | /// Return an angle using `self` number of π's. 35 | /// For example, 90 is 0.5π, and 360 is 2π: `2.cgPiCount` 36 | public var cgPiCount: CGAngle { return CGAngle(piCount: self.cgFloat) } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Sources/Type Changes/CGFloat+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // CGFloat utility extensions 12 | extension CGFloat { 13 | /// The pi constant 14 | public static let (pi, π) = (CGFloat(Double.pi), CGFloat(Double.pi)) 15 | 16 | /// The tau constant 17 | public static let (tau, τ) = (2 * pi, 2 * pi) 18 | 19 | /// Fractional pi: 30°, 45°, 60°, 90°, 120°, 135°, 150° 20 | public static let (sixthPi, quarterPi, thirdPi, halfPi, twothirdsPi, threeQuartersPi, fiveSixthsPi) = 21 | (pi / 6, 0.25 * pi, pi / 3, 0.5 * pi, 2 * pi / 3, 3 * pi / 4, 5 * pi / 6) 22 | 23 | /// Return CGFloat as Double 24 | public var double: Double { return Double(self) } 25 | 26 | /// Rounded to n decimal places, with "schoolbook" rounding 27 | public func rounded(places: Int) -> CGFloat { 28 | let degree = pow(10.0, CGFloat(places)) 29 | return (self * degree).rounded() / degree 30 | } 31 | } 32 | 33 | // Sign 34 | extension CGFloat { 35 | /// Signs 36 | public enum Sign { case negative, zero, positive } 37 | 38 | /// Returns sign of value 39 | public var sign: Sign { return self == 0.0 ? .zero : ((self < 0) ? .negative : .positive) } 40 | } 41 | 42 | // Modulo 43 | extension CGFloat { 44 | /// Returns modulo 45 | static func % (dividend: CGFloat, divisor: CGFloat) -> CGFloat { 46 | return CGFloat(fmod(Double(dividend), Double(divisor))) 47 | } 48 | } 49 | 50 | // Custom Core Graphics Angle Utilities for CGFloat 51 | extension CGFloat { 52 | 53 | /// Return a CGAngle of `self` degrees, e.g. `CGFloat(60).degrees` 54 | public var degrees: CGAngle { return CGAngle(degrees: self) } 55 | 56 | /// Return an angle of `self` radians, e.g. `CGFloat.pi.radians` 57 | public var radians: CGAngle { return CGAngle(radians: self) } 58 | 59 | /// Return an angle using this number of π's. 60 | /// For example, 180 is 0.5π 61 | public var piCount: CGAngle { return CGAngle(piCount: self) } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /Sources/Unlabeled and Shorthand Initializers/CGAffineTransform+Unlabeled.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Anonymous initializers 12 | extension CGAffineTransform { 13 | public init (_ vector: CGVector) { self.init(vector: vector) } 14 | public init (_ scale: CGScale) { self.init(scale: scale) } 15 | public init (_ angle: CGAngle) { self.init(angle: angle) } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Unlabeled and Shorthand Initializers/CGAngle+Unlabeled.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGAngle { 12 | /// Initialize implicitly with radians 13 | public init(_ radians: CGFloat) { self.init(radians: radians) } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Unlabeled and Shorthand Initializers/CGPoint+Unlabeled.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGPoint { 12 | /// Unlabeled initializer 13 | public init(_ x: CGFloat, _ y: CGFloat) { (self.x, self.y) = (x, y) } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Sources/Unlabeled and Shorthand Initializers/CGRect+Unlabeled.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // Anonymous Initializers 12 | extension CGRect { 13 | 14 | /// Initialize from size 15 | public init(_ size: CGSize) { self.init(origin: .zero, size: size) } 16 | 17 | /// Initialize from origin 18 | public init(_ origin: CGPoint) { self.init(origin: origin, size: .zero) } 19 | 20 | /// Initialize from vector 21 | public init(_ vector: CGVector) { self.init(origin: .zero, size: CGSize(vector.dx, vector.dy)) } 22 | 23 | /// Initialize using width and height 24 | public init(width w: CGFloat, height h: CGFloat) { (self.origin, self.size) = (.zero, CGSize(w, h)) } 25 | 26 | /// Initialize using shorthand width and height 27 | public init(w: CGFloat, h: CGFloat) { (self.origin, self.size) = (.zero, CGSize(w, h)) } 28 | 29 | /// Initialize using unlabeled width and height 30 | public init(_ width: CGFloat, _ height: CGFloat) { (self.origin, self.size) = (.zero, CGSize(width, height)) } 31 | 32 | /// Initialize a la CGRectMake 33 | public init(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) { 34 | self.init(x: x, y: y, width: width, height: height) 35 | } 36 | 37 | /// Initialize with non-literal Swift Double a la CGRectMake 38 | public init(_ x: Double, _ y: Double, _ width: Double, _ height: Double) { 39 | self.init(x: x, y: y, width: width, height: height) 40 | } 41 | 42 | /// Initialize with non-literal Swift Int a la CGRectMake 43 | public init(_ x: Int, _ y: Int, _ width: Int, _ height: Int) { 44 | self.init(x: x, y: y, width: width, height: height) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Unlabeled and Shorthand Initializers/CGScale+Unlabeled.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGScale { 12 | /// Unlabeled initializer 13 | public init(_ sx: CGFloat, _ sy: CGFloat) { (self.sx, self.sy) = (sx, sy) } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Sources/Unlabeled and Shorthand Initializers/CGSize+Unlabeled.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGSize { 12 | /// Unlabeled initializer 13 | public init(_ width: CGFloat, _ height: CGFloat) { (self.width, self.height) = (width, height) } 14 | 15 | /// Shorthand initializer 16 | public init(w: CGFloat, h: CGFloat) { (self.width, self.height) = (w, h) } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Sources/Unlabeled and Shorthand Initializers/CGVector+Unlabeled.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGVector { 12 | /// Unlabeled initializer 13 | public init(_ dx: CGFloat, _ dy: CGFloat) { (self.dx, self.dy) = (dx, dy) } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Sources/Vector/CGVector+Math.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | extension CGVector { 12 | 13 | // MARK: Magnitude 14 | 15 | /// Returns magnitude 16 | public var magnitude: CGFloat { 17 | return hypot(dx, dy) 18 | } 19 | 20 | // MARK: Unit Vector 21 | 22 | /// Returns unit vector 23 | public var unitForm: CGVector { 24 | return CGVector(dx: dx / magnitude, dy: dy / magnitude) 25 | } 26 | 27 | } 28 | 29 | extension CGVector { 30 | 31 | // MARK: Scaling 32 | 33 | /// Multiplies vector by factor 34 | public static func * (vector: CGVector, factor: CGFloat) -> CGVector { 35 | return CGVector(dx: vector.dx * factor, dy: vector.dy * factor) 36 | } 37 | 38 | /// Multiplies vector by scale 39 | public static func * (vector: CGVector, scale: CGScale) -> CGVector { 40 | return CGVector(dx: vector.dx * scale.sx, dy: vector.dy * scale.sy) 41 | } 42 | 43 | /// Divides vector by factor 44 | public static func / (vector: CGVector, factor: CGFloat) -> CGVector { 45 | return CGVector(dx: vector.dx / factor, dy: vector.dy / factor) 46 | } 47 | 48 | /// Divides vector by scale 49 | public static func / (vector: CGVector, scale: CGScale) -> CGVector { 50 | return CGVector(dx: vector.dx / scale.sy, dy: vector.dy / scale.sx) 51 | } 52 | 53 | // MARK: Vector addition and subtraction 54 | 55 | /// Adds two vectors 56 | public static func + (v1: CGVector, v2: CGVector) -> CGVector { 57 | return CGVector(dx: v1.dx + v2.dx, dy: v1.dy + v2.dy) 58 | } 59 | 60 | /// Vector subtraction 61 | public static func - (v1: CGVector, v2: CGVector) -> CGVector { 62 | return CGVector(dx: v1.dx - v2.dx, dy: v1.dy - v2.dy) 63 | } 64 | 65 | // MARK: Negation 66 | 67 | /// Returns negative extents 68 | public func negated() -> CGVector { 69 | return CGVector(dx: -dx, dy: -dy) 70 | } 71 | 72 | /// Returns RHS/CCW perpendicular 73 | public func rhperpendicular() -> CGVector { 74 | return CGVector(dx: -dy, dy: dx) 75 | } 76 | 77 | /// Returns LHS/CW perpendicular 78 | public func lhperpendicular() -> CGVector { 79 | return CGVector(dx: dy, dy: -dx) 80 | } 81 | 82 | /// Returns (RHS, CCW) perpendicular 83 | public func perpendicular() -> CGVector { 84 | return rhperpendicular() 85 | } 86 | 87 | /// Negation 88 | public static prefix func - (vector: CGVector) -> CGVector { 89 | return vector.negated() 90 | } 91 | } 92 | 93 | infix operator ** 94 | 95 | extension CGVector { 96 | 97 | // MARK: Dot Product 98 | 99 | /// Returns dot product of two vectors 100 | public static func * (v1: CGVector, v2: CGVector) -> CGFloat { 101 | let dot: CGFloat = (v1.dx * v2.dx) + (v1.dy * v2.dy) 102 | return dot 103 | } 104 | 105 | /// Returns unit dot product of two vectors 106 | public static func ** (v1: CGVector, v2: CGVector) -> CGFloat { 107 | return (v1 * v2) / (v1.magnitude * v2.magnitude) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/Vector/CGVector+Utility.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | erica sadun, ericasadun.com 4 | Core Geometry Math 5 | 6 | */ 7 | 8 | import Foundation 9 | import QuartzCore 10 | 11 | // This extension intentionally left blank 12 | --------------------------------------------------------------------------------