Created
June 23, 2025 16:19
-
-
Save colbyn/de2e52d93ddf67fcf4111bdf20b58b4c to your computer and use it in GitHub Desktop.
Turn Angles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// A type-safe representation of an angle in full turns. | |
/// One full turn equals 360 degrees or 2π radians. | |
struct Turn: Hashable, Codable { | |
/// The raw value in full turns. 1.0 = 360° = 2π radians. | |
let value: Double | |
/// Creates a `Turn` from a value in full turns. | |
/// - Parameter value: The angle in full turns. | |
init(_ value: Double) { | |
self.value = value | |
} | |
/// Creates a `Turn` from radians. | |
/// - Parameter radians: Angle in radians. | |
init(radians: Double) { | |
self.value = radians / (2 * .pi) | |
} | |
/// Creates a `Turn` from degrees. | |
/// - Parameter degrees: Angle in degrees. | |
init(degrees: Double) { | |
self.value = degrees / 360.0 | |
} | |
/// Returns the angle in radians. | |
var radians: Double { | |
value * 2 * .pi | |
} | |
/// Returns the angle in degrees. | |
var degrees: Double { | |
value * 360.0 | |
} | |
/// Returns the angle wrapped into the range `[0, 1)`. | |
var wrapped: Turn { | |
let v = value.truncatingRemainder(dividingBy: 1.0) | |
return Turn(v >= 0 ? v : v + 1.0) | |
} | |
/// A constant representing 0 turns (0°). | |
static var zero: Turn { .init(0) } | |
/// A constant representing 1/4 turn (90°). | |
static var quarter: Turn { .init(0.25) } | |
/// A constant representing 1/2 turn (180°). | |
static var half: Turn { .init(0.5) } | |
/// A constant representing 1 full turn (360°). | |
static var full: Turn { .init(1.0) } | |
} | |
//static func ≈ (lhs: Turn, rhs: Turn) -> Bool { | |
// abs(lhs.wrapped.value - rhs.wrapped.value) < .ulpOfOne | |
//} | |
// MARK: - Arithmetic | |
extension Turn { | |
/// Adds two `Turn` values. | |
static func + (lhs: Turn, rhs: Turn) -> Turn { | |
Turn(lhs.value + rhs.value) | |
} | |
/// Subtracts one `Turn` from another. | |
static func - (lhs: Turn, rhs: Turn) -> Turn { | |
Turn(lhs.value - rhs.value) | |
} | |
/// Multiplies a `Turn` by a scalar. | |
static func * (lhs: Turn, rhs: Double) -> Turn { | |
Turn(lhs.value * rhs) | |
} | |
/// Multiplies a scalar by a `Turn`. | |
static func * (lhs: Double, rhs: Turn) -> Turn { | |
Turn(lhs * rhs.value) | |
} | |
/// Divides a `Turn` by a scalar. | |
static func / (lhs: Turn, rhs: Double) -> Turn { | |
Turn(lhs.value / rhs) | |
} | |
/// Negates a `Turn`. | |
static prefix func - (angle: Turn) -> Turn { | |
Turn(-angle.value) | |
} | |
} | |
// MARK: - Trigonometry | |
extension Turn { | |
/// Sine of the angle. | |
var sin: Double { Foundation.sin(radians) } | |
/// Cosine of the angle. | |
var cos: Double { Foundation.cos(radians) } | |
/// Tangent of the angle. | |
var tan: Double { Foundation.tan(radians) } | |
/// Inverse sine: returns an angle in the range `[0, 0.5]` turns. | |
/// - Parameter value: Must be in `[-1, 1]`. | |
static func arcsin(_ value: Double) -> Turn { | |
Turn(radians: Foundation.asin(value)).wrapped | |
} | |
/// Inverse cosine: returns an angle in the range `[0, 0.5]` turns. | |
/// - Parameter value: Must be in `[-1, 1]`. | |
static func arccos(_ value: Double) -> Turn { | |
Turn(radians: Foundation.acos(value)).wrapped | |
} | |
/// Inverse tangent: returns an angle in the range `[0, 1)` turns. | |
/// - Parameter value: Any real number. | |
static func arctan(_ value: Double) -> Turn { | |
Turn(radians: Foundation.atan(value)).wrapped | |
} | |
/// Inverse tangent with two arguments, returning angle in full-turn range `[0, 1)`. | |
/// - Parameters: | |
/// - y: Y-coordinate (opposite). | |
/// - x: X-coordinate (adjacent). | |
static func arctan2(y: Double, x: Double) -> Turn { | |
Turn(radians: Foundation.atan2(y, x)).wrapped | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment