diff --git a/Euclid.docc/Extensions/Color.md b/Euclid.docc/Extensions/Color.md index 00bda1a0..66cce8bd 100644 --- a/Euclid.docc/Extensions/Color.md +++ b/Euclid.docc/Extensions/Color.md @@ -4,11 +4,11 @@ ### Creating a Color +- ``Color/init(r:g:b:a:)`` - ``Color/init(_:_:_:_:)`` - ``Color/init(_:_:)`` -- ``Color/init(_:)-25eby`` - ``Color/init(_:)-53lhy`` -- ``Color/init(_:)-7d8un`` +- ``Color/init(_:)-9bvpm`` ### Default Colors diff --git a/Euclid.docc/Extensions/Vector.md b/Euclid.docc/Extensions/Vector.md index 1de01ab7..19c82427 100644 --- a/Euclid.docc/Extensions/Vector.md +++ b/Euclid.docc/Extensions/Vector.md @@ -4,14 +4,12 @@ ### Creating Vectors -- ``Vector/init(_:)-228p6`` -- ``Vector/init(_:)-4eop9`` -- ``Vector/init(_:)-5n3j`` -- ``Vector/init(_:)-63ct7`` -- ``Vector/init(_:)-6nlm`` +- ``Vector/init(x:y:z:)`` - ``Vector/init(_:_:_:)`` - ``Vector/init(size:)-8b34m`` - ``Vector/init(size:)-nkyk`` +- ``Vector/init(_:)-63ct7`` +- ``Vector/init(_:)-602vn`` ### Default Vectors diff --git a/Sources/Color.swift b/Sources/Color.swift index 4084688b..5ebd19a5 100644 --- a/Sources/Color.swift +++ b/Sources/Color.swift @@ -29,6 +29,31 @@ // SOFTWARE. // +/// Protocol for types that can be converted to RGBA color components. +public protocol RGBAConvertible { + /// Get RGBA color components. + var rgbaComponents: (r: Double, g: Double, b: Double, a: Double) { get } +} + +/// Protocol for types that can be represented by RGBA color components. +public protocol RGBARepresentable: RGBAConvertible { + /// Initialize with RGBA components. + /// - Parameters: + /// - r: The red component of the color, from 0 to 1. + /// - g: The green component of the color, from 0 to 1. + /// - b: The blue component of the color, from 0 to 1. + /// - a: The alpha component of the color, from 0 to 1. + init(r: Double, g: Double, b: Double, a: Double) +} + +public extension RGBARepresentable { + /// Initialize with an RGBAConvertible value. + init(_ value: RGBAConvertible) { + let components = value.rgbaComponents + self.init(r: components.r, g: components.g, b: components.b, a: components.a) + } +} + /// A color in RGBA format. /// /// Color can be used as a ``Polygon/material-swift.property`` or as a ``Vertex/color``. @@ -67,6 +92,16 @@ extension Color: Comparable { } } +extension Color: RGBARepresentable { + public var rgbaComponents: (r: Double, g: Double, b: Double, a: Double) { + (r, g, b, a) + } + + public init(r: Double = 0, g: Double = 0, b: Double = 0, a: Double = 1) { + self.init(r, g, b, a) + } +} + extension Color: Codable { private enum CodingKeys: String, CodingKey { case r, g, b, a diff --git a/Sources/Euclid+AppKit.swift b/Sources/Euclid+AppKit.swift index 25e7d0a9..caf6dec3 100644 --- a/Sources/Euclid+AppKit.swift +++ b/Sources/Euclid+AppKit.swift @@ -56,11 +56,9 @@ public extension NSColor { } } -public extension Color { - /// Creates a color from an `NSColor`. - /// - Parameter nsColor: The `NSColor` to convert. - init(_ nsColor: NSColor) { - self.init(nsColor.cgColor) +extension NSColor: RGBAConvertible { + public var rgbaComponents: (r: Double, g: Double, b: Double, a: Double) { + cgColor.rgbaComponents } } diff --git a/Sources/Euclid+CoreGraphics.swift b/Sources/Euclid+CoreGraphics.swift index 82b71fbb..88d76fb3 100644 --- a/Sources/Euclid+CoreGraphics.swift +++ b/Sources/Euclid+CoreGraphics.swift @@ -33,26 +33,35 @@ import CoreGraphics -public extension Vector { - /// Creates a vector from a CoreGraphics `CGPoint`. - /// - Parameter cgPoint: the CoreGraphics point. - init(_ cgPoint: CGPoint) { - self.init(Double(cgPoint.x), Double(cgPoint.y)) +extension CGPoint: XYZRepresentable { + public var xyzComponents: (x: Double, y: Double, z: Double) { + (Double(x), Double(y), 0) } - /// Creates a new vector from a CoreGraphics size. - /// - Parameter cgSize: the CoreGraphics size. - init(_ cgSize: CGSize) { - self.init(Double(cgSize.width), Double(cgSize.height)) + public init(x: Double, y: Double, z _: Double) { + self.init(x: x, y: y) } } -public extension Color { - /// Creates a color from a CoreGraphics `CGColor`. - /// - Parameter cgColor: The CoreGraphics color instance. - init(_ cgColor: CGColor) { - let components = cgColor.components ?? [1] - self.init(unchecked: components.map(Double.init)) +extension CGSize: XYZRepresentable { + public var xyzComponents: (x: Double, y: Double, z: Double) { + (Double(width), Double(height), 0) + } + + public init(x: Double, y: Double, z _: Double) { + self.init(width: x, height: y) + } +} + +extension CGColor: RGBAConvertible { + public var rgbaComponents: (r: Double, g: Double, b: Double, a: Double) { + let c = components?.map(Double.init) ?? [1] + switch c.count { + case 1: return (c[0], c[0], c[0], 1) + case 2: return (c[0], c[0], c[0], c[1]) + case 3: return (c[0], c[1], c[2], 1) + default: return (c[0], c[1], c[2], 1) + } } } diff --git a/Sources/Euclid+SIMD.swift b/Sources/Euclid+SIMD.swift index 5b6dcd8c..4378d554 100644 --- a/Sources/Euclid+SIMD.swift +++ b/Sources/Euclid+SIMD.swift @@ -33,47 +33,82 @@ import simd -public extension simd_double3 { - /// Creates a simd vector 3 from a Euclid `Vector`. - /// - Parameter vector: A Euclid vector. - init(_ vector: Vector) { - self.init(vector.x, vector.y, vector.z) +extension SIMD3: XYZConvertible where Scalar: FloatingPoint { + public var xyzComponents: (x: Double, y: Double, z: Double) { + switch self { + case let value as simd_double3: + return (value.x, value.y, value.z) + case let value as simd_float3: + return (Double(value.x), Double(value.y), Double(value.z)) + default: + preconditionFailure() + } } } -public extension simd_float3 { - /// Creates a simd float vector 3 from a Euclid `Vector`. - /// - Parameter vector: A Euclid vector. - init(_ vector: Vector) { - self.init(Float(vector.x), Float(vector.y), Float(vector.z)) +extension SIMD3: XYZRepresentable where Scalar: FloatingPoint { + @_disfavoredOverload + public init(x: Double, y: Double, z: Double) { + switch Self.self { + case let type as simd_double3.Type: + self = type.init(x, y, z) as! Self + case let type as simd_float3.Type: + self = type.init(Float(x), Float(y), Float(z)) as! Self + default: + preconditionFailure() + } } } -public extension simd_float2 { - /// Creates a simd float vector 2 from a Euclid `Vector`. - /// - Parameter vector: A Euclid vector. - init(_ vector: Vector) { - self.init(Float(vector.x), Float(vector.y)) +extension SIMD2: XYZConvertible where Scalar: FloatingPoint { + public var xyzComponents: (x: Double, y: Double, z: Double) { + switch self { + case let value as simd_double2: + return (value.x, value.y, 0) + case let value as simd_float2: + return (Double(value.x), Double(value.y), 0) + default: + preconditionFailure() + } } } -public extension Vector { - /// Creates a `Vector` from a simd vector 3. - /// - Parameter vector: A simd vector. - init(_ vector: simd_double3) { - self.init(vector.x, vector.y, vector.z) +extension SIMD2: XYZRepresentable where Scalar: FloatingPoint { + public init(x: Double, y: Double, z _: Double) { + switch Self.self { + case let type as simd_double2.Type: + self = type.init(x, y) as! Self + case let type as simd_float2.Type: + self = type.init(Float(x), Float(y)) as! Self + default: + preconditionFailure() + } } +} - /// Creates a `Vector` from a simd vector 3. - /// - Parameter vector: A simd vector. - init(_ vector: simd_float3) { - self.init(Double(vector.x), Double(vector.y), Double(vector.z)) +extension SIMD4: RGBAConvertible where Scalar: FloatingPoint { + public var rgbaComponents: (r: Double, g: Double, b: Double, a: Double) { + switch self { + case let value as simd_double4: + return (value.x, value.y, value.z, value.w) + case let value as simd_float4: + return (Double(value.x), Double(value.y), Double(value.z), Double(value.w)) + default: + preconditionFailure() + } } +} - /// Creates a `Vector` from a simd vector 2. - /// - Parameter vector: A simd vector. - init(_ vector: simd_float2) { - self.init(Double(vector.x), Double(vector.y)) +extension SIMD4: RGBARepresentable where Scalar: FloatingPoint { + public init(r: Double, g: Double, b: Double, a: Double) { + switch Self.self { + case let type as simd_double4.Type: + self = type.init(r, g, b, a) as! Self + case let type as simd_float4.Type: + self = type.init(Float(r), Float(g), Float(b), Float(a)) as! Self + default: + preconditionFailure() + } } } diff --git a/Sources/Euclid+SceneKit.swift b/Sources/Euclid+SceneKit.swift index 16484328..342d4656 100644 --- a/Sources/Euclid+SceneKit.swift +++ b/Sources/Euclid+SceneKit.swift @@ -85,11 +85,24 @@ public extension SCNVector3 { } } -public extension SCNVector4 { - /// Creates a 4D SceneKit vector from a color. - /// - Parameter c: The color to convert. - init(_ c: Color) { - self.init(c.r, c.g, c.b, c.a) +extension SCNVector3: XYZRepresentable { + public var xyzComponents: (x: Double, y: Double, z: Double) { + (Double(x), Double(y), Double(z)) + } + + @_disfavoredOverload + public init(x: Double, y: Double, z: Double) { + self.init(CGFloat(x), CGFloat(y), CGFloat(z)) + } +} + +extension SCNVector4: RGBARepresentable { + public var rgbaComponents: (r: Double, g: Double, b: Double, a: Double) { + (Double(x), Double(y), Double(z), Double(w)) + } + + public init(r: Double, g: Double, b: Double, a: Double) { + self.init(CGFloat(r), CGFloat(g), CGFloat(b), CGFloat(a)) } } @@ -495,14 +508,6 @@ private extension Data { } } -public extension Vector { - /// Creates a new vector from a SceneKit vector. - /// - Parameter v: The SceneKit `SCNVector3`. - init(_ v: SCNVector3) { - self.init(Double(v.x), Double(v.y), Double(v.z)) - } -} - public extension Rotation { /// Creates a rotation from a SceneKit quaternion. /// - Parameter q: The `SCNQuaternion` to convert. diff --git a/Sources/Euclid+UIKit.swift b/Sources/Euclid+UIKit.swift index 1689a07a..a3257ee1 100644 --- a/Sources/Euclid+UIKit.swift +++ b/Sources/Euclid+UIKit.swift @@ -50,11 +50,9 @@ public extension UIColor { } } -public extension Color { - /// Creates a color from a `UIColor`. - /// - Parameter uiColor: The `UIColor` to convert. - init(_ uiColor: UIColor) { - self.init(uiColor.cgColor) +extension UIColor: RGBAConvertible { + public var rgbaComponents: (r: Double, g: Double, b: Double, a: Double) { + cgColor.rgbaComponents } } diff --git a/Sources/Vector.swift b/Sources/Vector.swift index bb8bf298..0c235705 100644 --- a/Sources/Vector.swift +++ b/Sources/Vector.swift @@ -31,6 +31,37 @@ import Foundation +/// Protocol for types that can be converted to XYZ components. +public protocol XYZConvertible { + /// Get XYZ vector components. + var xyzComponents: (x: Double, y: Double, z: Double) { get } +} + +/// Protocol for types that can be represented by XYZ vector components. +public protocol XYZRepresentable: XYZConvertible { + /// Initialize with XYZ components. + /// - Parameters: + /// - x: The X component of the vector. + /// - y: The Y component of the vector. + /// - z: The Z component of the vector. + init(x: Double, y: Double, z: Double) +} + +public extension XYZRepresentable { + /// Initialize with some XYZConvertible value. + init(_ value: T) { + let components = value.xyzComponents + self.init(x: components.x, y: components.y, z: components.z) + } + + /// Initialize with any XYZConvertible value. + @_disfavoredOverload + init(_ value: XYZConvertible) { + let components = value.xyzComponents + self.init(x: components.x, y: components.y, z: components.z) + } +} + /// A distance or position in 3D space. /// /// > Note: Euclid doesn't have a 2D vector type. When working with primarily 2D shapes, such as @@ -55,6 +86,16 @@ public struct Vector: Hashable, Sendable, AdditiveArithmetic { } } +extension Vector: XYZRepresentable { + public var xyzComponents: (x: Double, y: Double, z: Double) { + (x, y, z) + } + + public init(x: Double = 0, y: Double = 0, z: Double = 0) { + self.init(x, y, z) + } +} + extension Vector: Comparable { /// Returns whether the leftmost vector has the lower value. /// This provides a stable order when sorting collections of vectors.