๐ ฟ๏ธ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
Last updated
Was this helpful?