๐Ÿ…ฟ๏ธVector2D

swift โŸฉ type โŸฉ custom โŸฉ package โŸฉ GeometryKit โŸฉ Vector2D

// History:
// โ€ข 2024.12.09 - draft
// โ€ข 2024.12.11 - version 1.0

// Inheritance Hierarchy
// โ€ข MetricSpace -> Vector -> Vector2D -> ComplextNumber

// ---------------------
//      ๐Ÿ…ฟ๏ธ Vector2D
// ---------------------

/// ้€š็”จ็š„ไบŒ็ถญๅ‘้‡ๅ”ๅฎš๏ผŒๅฏ้ฉ็”จๆ–ผ `CGPoint`, `CGSize` ็ญ‰
///
/// ```
/// import CoreGraphics
///
/// // CGPoint + Vector2D
/// extension CGPoint: Vector2D {
///     public typealias Scalar = CGFloat
/// }
///
/// // CGSize + Vector2D
/// extension CGSize: Vector2D {
///     public typealias Scalar = CGFloat
///     public var y: CGFloat { height }
///     public init(x: CGFloat, y: CGFloat) {
///         self.init(width: x, height: y)
///     }
/// }
/// ```
public protocol Vector2D: Vector {
    
    // โญ๏ธ required properties
    var x: Scalar { get }
    var y: Scalar { get }
    
    // โญ๏ธ required initializer
    init(x: Scalar, y: Scalar)
    
}


// ------------------------------------------
//      ๐ŸŒ€ Vector2D: Vector Operations
// ------------------------------------------

// MARK: - Vector Operations -

public extension Vector2D {
    
    /* ------- Constants ------- */
    
    /// `Self.zero = (0, 0)`
    static var zero: Self {
        return Self.init(x: 0, y: 0)
    }
    
    /// `Self.i = (1, 0)`
    static var i: Self {
        return Self.init(x: 1, y: 0)
    }
    
    /// `Self.j = (0, 1)`
    static var j: Self {
        return Self.init(x: 0, y: 1)
    }
    
    /* ------- Initializers ------- */
    
    /// convenience initializer, ex: `CGPoint(1, 2`
    init(_ x: Scalar, _ y: Scalar) {
        self.init(x: x, y: y)
    }
    
    /* ------- Geometry Properties ------- */
    
    /// ้•ทๅบฆๅนณๆ–น |z|ยฒ = xยฒ + yยฒ
    var normSquared: Scalar {
        x * x + y * y
    }
        
    /// ้•ทๅบฆ |z| = โˆš(xยฒ + yยฒ)
    var magnitude: Scalar {
        (x * x + y * y).squareRoot()
    }
        
    /// ้•ทๅบฆ |z|
    var norm: Scalar {
        (x * x + y * y).squareRoot()
    }
    
    /// ๅ–ฎไฝๅ‘้‡
    var unitVector: Self {
        precondition(self.magnitude != 0, "โ›” Zero vector can not be normalized.")
        return self / self.magnitude
    }
    
    
    /* ------- Linear Combinations ------- */
    
    /// u + v (vector addition)
    @inlinable    // ๆธ›ๅฐ‘้‹็ฎ—้ ป็น่ชฟ็”จ็š„้–‹้Šท
    static func + (u: Self, v: Self) -> Self {
        return Self.init(x: u.x + v.x, y: u.y + v.y)
    }
    
    /// u += v (vector addition)
    static func += (u: inout Self, v: Self) {
        u = u + v
    }
    
    /// -v (additive inverse)
    static prefix func - (v: Self) -> Self {
        return Self.init(x: -v.x, y: -v.y)
    }
    
    /// u - v (vector subtraction)
    @inlinable    // ๆธ›ๅฐ‘้‹็ฎ—้ ป็น่ชฟ็”จ็š„้–‹้Šท
    static func - (u: Self, v: Self) -> Self {
        return Self.init(x: u.x - v.x, y: u.y - v.y)
    }
    
    /// u -= v (vector subtraction)
    static func -= (u: inout Self, v: Self) {
        u = u - v
    }
    
    /* ------- Scalar Multiplication ------- */
    
    /// v * a (scalar multiplication)
    /// (a * v : default behavior provided by `Vector`)
    @inlinable    // ๆธ›ๅฐ‘้‹็ฎ—้ ป็น่ชฟ็”จ็š„้–‹้Šท
    static func * (v: Self, a: Scalar) -> Self {
        return Self.init(x: a * v.x, y: a * v.y)
    }
    
    /// v *= a (scalar multiplicatio)
    static func *= (v: inout Self, a: Scalar) {
        v = v * a
    }
    
    // v / a (scalar multiplication)
    // default behavior provided by `Vector`
    
    /// v /= a (scalar multiplicatio)
    static func /= (v: inout Self, a: Scalar) {
        precondition(a != 0, "โ›” Division by zero is not allowed.")
        v = v / a
    }

    
    /* ------- Dot Product ------- */
        
    /// dot product
    /// u.dot(v) = u โ€ข v = u.x * v.x + u.y * v.y
    func dot(_ v: Self) -> Scalar {
        return x * v.x + y * v.y
    }
    
    /* ------- Cross Product ------- */
    
    /// cross product:
    /// u ร— v = u.x * v.y - u.y * v.x
    ///
    /// ```
    /// // Example:
    /// let u = CGPoint(1, 2) // u = (1, 2)
    /// let v = CGPoint(3, 4) // v = (3, 4)
    /// u.cross(v)            // u ร— v = -2
    /// ```
    func cross(_ v: Self) -> Scalar {
        return x * v.y - y * v.x
    }
    
}

// ---------------------------------------
//      ๐ŸŒ€ Vector2D: Other Utilities
// ---------------------------------------

// MARK: - Utility -

public extension Vector2D {
        
    /* ------- non-proportional scale ------- */
    
    /// ```
    /// // non-proportional scale
    /// let u = CGPoint(1, 2) // u = (1, 2)
    /// let v = CGPoint(3, 4) // v = (3, 4)
    /// u.scale(v)            // (1*3, 2*4) = (3, 8)
    /// ```
    func scaled(by v: Self) -> Self {
        return Self.init(x: x * v.x, y: y * v.y)
    }
    
    /// non-proportional scale
    /// u = (a, b)
    /// v = (c, d)
    /// u.divided(by: v) = (a/c, b/d)
    func divided(by v: Self) -> Self {
        precondition(v.x != 0 && v.y != 0, "โ›” Division by zero is not allowed.")
        return Self.init(x: x / v.x, y: y / v.y)
    }

}


// ----------------------------------------------
//      ๐ŸŒ€ Vector2D: Geometry Utilities
// ----------------------------------------------

// MARK: - Geometry -

extension Vector2D {
    
    // โญ๏ธ required by MetricSpace
    // p.distance(to: q)
    public func distance(to other: Self) -> Scalar {
        return (self - other).magnitude
    }
    
}


// ้œ€่ฆ cos(), sin(), atan2() ็ญ‰็ญ‰ๅ‡ฝๆ•ธ่€…ๆ”พ้€™่ฃก
import  CoreGraphics        // for CGFloat, CGPoint , etc

// cos(), sin(), etc use CGFloat
extension Vector2D where Scalar == CGFloat {
    
    /* ------- Geometry Utilities ------- */
    
    /// ๅฐ‡ๅ‘้‡ๆ—‹่ฝ‰ๆŸๅ€‹่ง’ๅบฆ (in radians)
    /// `vector.rotated(by: angle)`
    public func rotated(by angle: Scalar) -> Self {
        let cosA = cos(angle)
        let sinA = sin(angle)
        // transformed by rotation matrix
        return Self.init(
            x: x * cosA - y * sinA,
            y: x * sinA + y * cosA
        )
    }
    
    /// u ๅˆฐ v ็š„ๆœ‰ๅ‘่ง’(in radians)๏ผŒ
    /// ็ฏ„ๅœ๏ผš-๐œ‹ < ฮธ <= ๐œ‹
    public func angle(to v: Self) -> Scalar {
        
        // dot = |u||v|cosฮธ
        let dot = self.dot(v)
        // u โจ‰ v = |u||v|sinฮธ
        let area = self.cross(v)
        
        // |u||v|
        let mn = self.magnitude * v.magnitude
        
        precondition(mn != 0,
            "โ›” Angle to/from zero vector is not defined."
        )
        
        // (cosฮธ, sinฮธ)
        let (c, s) = (dot/mn, area/mn)
        
        // atan2(y, x)
        return atan2(s, c)
    }
    
    /* ------- Polar Coordinates ------- */
    
    // cos(x), sin(x) -> Foundation framework
    
    /// polar form: `Self.polar(radius: 2, angle: .pi/3)`
    public static func polar(radius: Scalar, angle: Scalar) -> Self {
        let (r, a) = (radius, angle)
        return Self.init(x: r * cos(a), y: r * sin(a))
    }
    
    /// convert to CGPoint: `v.point`
    public var point: CGPoint { CGPoint(x: x, y: y) }
    
    /* ------- Frame related  ------- */
    
    /// vector's origin: `v.origin == Self.zero`
    public var origin: CGPoint { .zero }
    
    /// convert to CGSize: `v.size`
    public var size  : CGSize  {  CGSize(width: x, height: y) }
}

History

  • 2020.09.30๏ผšโž• CGRect conforms to Vector2D

  • 2020.10.08๏ผšโž• unit vectors: Vector2D.i, Vector2D.j, v / a (scalar division)

  • 2020.10.10๏ผš โžŠ ไฝฟ็”จ associatedtype Field

  • 2020.10.22๏ผšโž• .polar(r, a) (conditional conformance)

Versions

  1. 2020.10.10

  2. 2022.02.09

  3. 2022.03.09

Last updated