Skip to content

Commit

Permalink
Add XYZRepresentable and RGBARepresentable protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklockwood committed Aug 7, 2024
1 parent 63bbbcd commit af5bbad
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 73 deletions.
4 changes: 2 additions & 2 deletions Euclid.docc/Extensions/Color.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 3 additions & 5 deletions Euclid.docc/Extensions/Vector.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 35 additions & 0 deletions Sources/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
Expand Down Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions Sources/Euclid+AppKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
39 changes: 24 additions & 15 deletions Sources/Euclid+CoreGraphics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
91 changes: 63 additions & 28 deletions Sources/Euclid+SIMD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}

Expand Down
31 changes: 18 additions & 13 deletions Sources/Euclid+SceneKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down Expand Up @@ -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.
Expand Down
8 changes: 3 additions & 5 deletions Sources/Euclid+UIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
41 changes: 41 additions & 0 deletions Sources/Vector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: XYZConvertible>(_ 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
Expand All @@ -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.
Expand Down

0 comments on commit af5bbad

Please sign in to comment.