From 1004551e13bd29352041ff435fbe232fd869a5ca Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 29 Jan 2024 01:37:12 +0000 Subject: [PATCH] Deprecate Quaternion --- Sources/Euclid+SIMD.swift | 19 +- Sources/Euclid+SceneKit.swift | 10 +- Sources/Quaternion.swift | 33 ++-- Sources/Rotation.swift | 333 ++++++++++++++++++++++++++-------- Sources/Transforms.swift | 7 +- Tests/BoundsTests.swift | 1 - Tests/CodingTests.swift | 3 + Tests/QuaternionTests.swift | 1 + Tests/TransformTests.swift | 8 +- 9 files changed, 305 insertions(+), 110 deletions(-) diff --git a/Sources/Euclid+SIMD.swift b/Sources/Euclid+SIMD.swift index 5b466ab8..5b6dcd8c 100644 --- a/Sources/Euclid+SIMD.swift +++ b/Sources/Euclid+SIMD.swift @@ -81,11 +81,12 @@ public extension simd_quatd { /// Creates a simd quaternion from a Euclid `Rotation`. /// - Parameter rotation: A Euclid rotation. init(_ rotation: Rotation) { - self.init(rotation.quaternion) + self = rotation.storage } /// Creates a simd quaternion from a Euclid `Quaternion`. /// - Parameter quaternion: A Euclid quaternion. + @available(*, deprecated) init(_ quaternion: Quaternion) { self = quaternion.storage } @@ -95,13 +96,14 @@ public extension simd_quatf { /// Creates a simd float quaternion from a Euclid `Rotation`. /// - Parameter rotation: A Euclid rotation. init(_ rotation: Rotation) { - self.init(rotation.quaternion) + self.init(vector: simd_float4(rotation.storage.vector)) } /// Creates a simd float quaternion from a Euclid `Quaternion`. - /// - Parameter q: A Euclid quaternion. - init(_ q: Quaternion) { - self.init(ix: Float(q.x), iy: Float(q.y), iz: Float(q.z), r: Float(q.w)) + /// - Parameter quaternion: A Euclid quaternion. + @available(*, deprecated) + init(_ quaternion: Quaternion) { + self.init(vector: simd_float4(quaternion.storage.vector)) } } @@ -109,21 +111,22 @@ public extension Rotation { /// Creates a `Rotation` from a simd quaternion. /// - Parameter quaternion: A simd quaternion. init(_ quaternion: simd_quatd) { - self.init(Quaternion(quaternion)) + self.init(storage: quaternion) } /// Creates a `Rotation` from a simd quaternion. /// - Parameter quaternion: A simd quaternion. init(_ quaternion: simd_quatf) { - self.init(Quaternion(quaternion)) + self.init(simd_quatd(vector: simd_double4(quaternion.vector))) } } +@available(*, deprecated) public extension Quaternion { /// Creates a `Quaternion` from a simd quaternion. /// - Parameter quaternion: A simd quaternion. init(_ quaternion: simd_quatd) { - self.storage = quaternion + self.init(storage: quaternion) } /// Creates a `Quaternion` from a simd quaternion. diff --git a/Sources/Euclid+SceneKit.swift b/Sources/Euclid+SceneKit.swift index 8179c08d..16484328 100644 --- a/Sources/Euclid+SceneKit.swift +++ b/Sources/Euclid+SceneKit.swift @@ -100,7 +100,7 @@ public extension SCNQuaternion { /// > Note: ``SCNQuaternion`` is actually just a typealias for ``SCNVector4`` so be /// careful to avoid type ambiguity when using this value. init(_ rotation: Rotation) { - self.init(rotation.quaternion) + self.init(rotation.x, rotation.y, rotation.z, rotation.w) } /// Creates a new SceneKit quaternion from a Euclid `Quaternion` @@ -108,6 +108,7 @@ public extension SCNQuaternion { /// /// > Note: ``SCNQuaternion`` is actually just a typealias for ``SCNVector4`` so be /// careful to avoid type ambiguity when using this value. + @available(*, deprecated) init(_ quaternion: Quaternion) { self.init(quaternion.x, quaternion.y, quaternion.z, quaternion.w) } @@ -504,12 +505,13 @@ public extension Vector { public extension Rotation { /// Creates a rotation from a SceneKit quaternion. - /// - Parameter quaternion: The `SCNQuaternion` to convert. - init(_ quaternion: SCNQuaternion) { - self.init(.init(quaternion)) + /// - Parameter q: The `SCNQuaternion` to convert. + init(_ q: SCNQuaternion) { + self.init(Double(q.x), Double(q.y), Double(q.z), Double(q.w)) } } +@available(*, deprecated) public extension Quaternion { /// Creates a Euclid `Quaternion` from a SceneKit quaternion. /// - Parameter q: The `SCNQuaternion` to convert. diff --git a/Sources/Quaternion.swift b/Sources/Quaternion.swift index 59252fb1..16dd2158 100644 --- a/Sources/Quaternion.swift +++ b/Sources/Quaternion.swift @@ -42,6 +42,7 @@ import simd /// /// In addition to being more compact than a 3x3 rotation matrix, quaternions also avoid a /// problem known as gymbal lock. +@available(*, deprecated, message: "Use Rotation instead") public struct Quaternion: Sendable { var storage: simd_quatd @@ -70,17 +71,19 @@ public struct Quaternion: Sendable { } } +@available(*, deprecated) extension Quaternion: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(storage.vector) } } +@available(*, deprecated) public extension Quaternion { /// Creates a quaternion from raw component values. init(_ x: Double, _ y: Double, _ z: Double, _ w: Double) { let vector = simd_normalize(simd_double4(x, y, z, w)) - self.init(simd_quatd(vector: vector)) + self.init(storage: simd_quatd(vector: vector)) } /// The axis of rotation. @@ -120,7 +123,7 @@ public extension Quaternion { if storage.vector == .zero { return self } - return .init(simd_normalize(storage)) + return .init(storage: simd_normalize(storage)) } /// Performs a spherical interpolation between two quaternions. @@ -129,17 +132,17 @@ public extension Quaternion { /// - t: The normalized extent of interpolation, from 0 to 1. /// - Returns: The interpolated quaternion. func slerp(_ q: Quaternion, _ t: Double) -> Quaternion { - .init(simd_slerp(storage, q.storage, t)) + .init(storage: simd_slerp(storage, q.storage, t)) } /// Returns the reverse quaternion rotation. static prefix func - (q: Quaternion) -> Quaternion { - .init(simd_inverse(q.storage)) + .init(storage: simd_inverse(q.storage)) } /// Returns the sum of two quaternion rotations. static func + (lhs: Quaternion, rhs: Quaternion) -> Quaternion { - .init(lhs.storage + rhs.storage) + .init(storage: lhs.storage + rhs.storage) } /// Adds the quaternion rotation on the right to the one on the left. @@ -149,7 +152,7 @@ public extension Quaternion { /// Returns the difference between two quaternion rotations,. static func - (lhs: Quaternion, rhs: Quaternion) -> Quaternion { - .init(lhs.storage - rhs.storage) + .init(storage: lhs.storage - rhs.storage) } /// Subtracts the quaternion rotation on the right from the one on the left. @@ -159,7 +162,7 @@ public extension Quaternion { /// Returns the product of two quaternions (i.e. the effect of rotating the left by the right). static func * (lhs: Quaternion, rhs: Quaternion) -> Quaternion { - .init(lhs.storage * rhs.storage) + .init(storage: lhs.storage * rhs.storage) } /// Multiplies the quaternion rotation on the left by the one on the right. @@ -169,7 +172,7 @@ public extension Quaternion { /// Returns a quaternion with its components multiplied by the specified value. static func * (lhs: Quaternion, rhs: Double) -> Quaternion { - .init(lhs.storage * rhs) + .init(storage: lhs.storage * rhs) } /// Multiplies the components of the quaternion by the specified value. @@ -179,7 +182,7 @@ public extension Quaternion { /// Returns a quaternion with its components divided by the specified value. static func / (lhs: Quaternion, rhs: Double) -> Quaternion { - .init(lhs.storage / rhs) + .init(storage: lhs.storage / rhs) } /// Divides the components of the vector by the specified value. @@ -188,15 +191,16 @@ public extension Quaternion { } } +@available(*, deprecated) extension Quaternion { init(unchecked x: Double, _ y: Double, _ z: Double, _ w: Double) { - self.init(simd_quatd(vector: simd_double4(x, y, z, w))) + self.init(storage: simd_quatd(vector: simd_double4(x, y, z, w))) assert(isNormalized || lengthSquared == 0) } init(unchecked axis: Vector, angle: Angle) { assert(axis.isNormalized) - self.init(simd_quatd( + self.init(storage: simd_quatd( angle: -angle.radians, axis: .init(axis.x, axis.y, axis.z) )) @@ -212,6 +216,7 @@ extension Quaternion { /// /// In addition to being more compact than a 3x3 rotation matrix, quaternions also avoid a /// problem known as gymbal lock. +@available(*, deprecated, message: "Use Rotation instead") public struct Quaternion: Hashable, Sendable { /// The quaternion component values. public var x, y, z, w: Double @@ -226,6 +231,7 @@ public struct Quaternion: Hashable, Sendable { } } +@available(*, deprecated) public extension Quaternion { /// The axis of rotation. var axis: Vector { @@ -354,6 +360,7 @@ public extension Quaternion { } } +@available(*, deprecated) extension Quaternion { init(unchecked x: Double, _ y: Double, _ z: Double, _ w: Double) { self.x = x @@ -373,6 +380,7 @@ extension Quaternion { #endif +@available(*, deprecated) extension Quaternion: Codable { private enum CodingKeys: CodingKey { case x, y, z, w @@ -409,6 +417,7 @@ extension Quaternion: Codable { } } +@available(*, deprecated) public extension Quaternion { /// The zero quaternion. static let zero = Quaternion(unchecked: 0, 0, 0, 0) @@ -503,6 +512,7 @@ public extension Quaternion { } } +@available(*, deprecated) extension Quaternion: UnkeyedCodable { func encode(to container: inout UnkeyedEncodingContainer) throws { try container.encode(x) @@ -520,6 +530,7 @@ extension Quaternion: UnkeyedCodable { } } +@available(*, deprecated) extension Quaternion { /// Approximate equality func isEqual(to other: Quaternion, withPrecision p: Double = epsilon) -> Bool { diff --git a/Sources/Rotation.swift b/Sources/Rotation.swift index 79b6fdd7..e5a9f605 100644 --- a/Sources/Rotation.swift +++ b/Sources/Rotation.swift @@ -31,17 +31,212 @@ import Foundation -/// A struct that represents an orientation or rotation in 3D space. +#if canImport(simd) + +import simd + +/// An orientation or rotation in 3D space. /// -/// Internally, a rotation is stored as a 3x3 matrix, but that's an implementation detail that may change in future. /// A rotation can be converted to and from an axis vector and angle, or a set of 3 Euler angles (pitch, yaw and roll). +public struct Rotation: Sendable { + var storage: simd_quatd +} + +extension Rotation: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(storage.vector) + } +} + +public extension Rotation { + /// The axis of rotation. + var axis: Vector { + guard abs(w - 1) > epsilon else { + // if angle close to zero, direction is not important + return .unitZ + } + return .init(-storage.axis) + } + + /// The angle of rotation. + var angle: Angle { + .radians(storage.angle) + } + + /// Performs a spherical linear interpolation between two rotations. + /// - Parameters: + /// - r: The rotation to interpolate towards. + /// - t: The normalized extent of interpolation, from 0 to 1. + /// - Returns: The interpolated rotation. + func slerp(_ r: Rotation, _ t: Double) -> Rotation { + .init(storage: simd_slerp(storage, r.storage, t)) + } + + /// Returns the inverse rotation. + static prefix func - (r: Rotation) -> Rotation { + .init(storage: r.storage.inverse) + } + + /// Combines two rotations to get the cumulative rotation. + static func * (lhs: Rotation, rhs: Rotation) -> Rotation { + .init(storage: lhs.storage * rhs.storage) + } + + /// Combines with the specified rotation. + static func *= (lhs: inout Rotation, rhs: Rotation) { + lhs.storage *= rhs.storage + } +} + +extension Rotation { + var x: Double { storage.vector.x } + var y: Double { storage.vector.y } + var z: Double { storage.vector.z } + var w: Double { storage.vector.w } + + init(_ x: Double, _ y: Double, _ z: Double, _ w: Double) { + let vector = simd_normalize(simd_double4(x, y, z, w)) + self.init(storage: simd_quatd(vector: vector)) + } + + init(unchecked x: Double, _ y: Double, _ z: Double, _ w: Double) { + self.init(storage: simd_quatd(vector: simd_double4(x, y, z, w))) + let lengthSquared = simd_dot(storage, storage) + assert(lengthSquared == 0 || abs(lengthSquared - 1) < epsilon) + } + + init(unchecked axis: Vector, angle: Angle) { + assert(axis.isNormalized) + self.init(storage: simd_quatd( + angle: -angle.radians, + axis: .init(axis.x, axis.y, axis.z) + )) + } +} + +#else + +/// An orientation or rotation in 3D space. +/// +/// A quaternion can be created from a from a ``Rotation`` matrix, or directly from an axis vector and +/// angle, or a from a set of 3 Euler angles (pitch, yaw and roll). +/// +/// In addition to being more compact than a 3x3 rotation matrix, quaternions also avoid a +/// problem known as gymbal lock. public struct Rotation: Hashable, Sendable { - var quaternion: Quaternion + /// The quaternion component values. + public var x, y, z, w: Double + + /// Creates a quaternion from raw component values. + public init(_ x: Double, _ y: Double, _ z: Double, _ w: Double) { + self.x = x + self.y = y + self.z = z + self.w = w + self = normalized() + } } +public extension Rotation { + /// The axis of rotation. + var axis: Vector { + let s = sqrt(1 - w * w) + guard s > epsilon else { + // if angle close to zero, direction is not important + return .unitZ + } + return Vector(x, y, z) / -s + } + + /// The angle of rotation. + var angle: Angle { + .radians(2 * acos(w)) + } + + /// Performs a spherical linear interpolation between two rotations. + /// - Parameters: + /// - r: The rotation to interpolate towards. + /// - t: The normalized extent of interpolation, from 0 to 1. + /// - Returns: The interpolated rotation. + func slerp(_ r: Rotation, _ t: Double) -> Rotation { + let dot = max(-1, min(1, self.dot(r))) + if abs(abs(dot) - 1) < epsilon { + return (self + (r - self) * t).normalized() + } + + let theta = acos(dot) * t + let t1 = self * cos(theta) + let t2 = (r - (self * dot)).normalized() * sin(theta) + return t1 + t2 + } + + /// Returns the inverse rotation. + static prefix func - (r: Rotation) -> Rotation { + .init(unchecked: r.x, r.y, r.z, -r.w) + } + + /// Combines two rotations to get the cumulative rotation. + static func * (lhs: Rotation, rhs: Rotation) -> Rotation { + .init( + unchecked: + lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y, + lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z, + lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x, + lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z + ) + } + + /// Combines with the specified rotation. + static func *= (lhs: inout Rotation, rhs: Rotation) { + lhs = lhs * rhs + } +} + +private extension Rotation { + func dot(_ r: Rotation) -> Double { + x * r.x + y * r.y + z * r.z + w * r.w + } + + func normalized() -> Rotation { + let lengthSquared = dot(self) + if lengthSquared == 0 || lengthSquared == 1 { + return self + } + return self / sqrt(lengthSquared) + } + + static func + (lhs: Rotation, rhs: Rotation) -> Rotation { + .init(unchecked: lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w) + } + + static func - (lhs: Rotation, rhs: Rotation) -> Rotation { + .init(unchecked: lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w) + } +} + +extension Rotation { + init(unchecked x: Double, _ y: Double, _ z: Double, _ w: Double) { + self.x = x + self.y = y + self.z = z + self.w = w + let lengthSquared = dot(self) + assert(lengthSquared == 0 || abs(lengthSquared - 1) < epsilon) + } + + init(unchecked axis: Vector, angle: Angle) { + assert(axis.isNormalized) + let r = -angle / 2 + let a = axis * sin(r) + self.init(unchecked: a.x, a.y, a.z, cos(r)) + } +} + +#endif + extension Rotation: Codable { private enum CodingKeys: CodingKey { - case axis, x, y, z, radians + case axis, x, y, z, w, radians } private struct Matrix { @@ -95,12 +290,12 @@ extension Rotation: Codable { let y = sqrt(max(0, 1 - r.m11 + r.m22 - r.m33)) / 2 let z = sqrt(max(0, 1 - r.m11 - r.m22 + r.m33)) / 2 let w = sqrt(max(0, 1 + r.m11 + r.m22 + r.m33)) / 2 - self.init(Quaternion( + self.init( x * (x * (r.m32 - r.m23) < 0 ? 1 : -1), y * (y * (r.m13 - r.m31) < 0 ? 1 : -1), z * (z * (r.m21 - r.m12) < 0 ? 1 : -1), w - )) + ) } /// Creates a new rotation by decoding from the given decoder. @@ -113,6 +308,10 @@ extension Rotation: Codable { if let x = try container.decodeIfPresent(Double.self, forKey: .x) { let y = try container.decode(Double.self, forKey: .y) let z = try container.decode(Double.self, forKey: .z) + if let w = try container.decodeIfPresent(Double.self, forKey: .w) { + self.init(x, y, z, w) + return + } axis = Vector(x, y, z) } self.init(unchecked: axis?.normalized() ?? .unitZ, angle: angle ?? .zero) @@ -152,46 +351,44 @@ extension Rotation: Codable { public extension Rotation { /// The identity rotation (i.e. no rotation). - static let identity = Rotation() + static let identity: Rotation = .init() + + /// Creates a new identity rotation. + init() { + self.init(unchecked: 0, 0, 0, 1) + } + + /// Creates a rotation from an axis and angle. + /// - Parameters: + /// - axis: A vector defining the axis of rotation. + /// - angle: The angle of rotation around the axis. + init?(axis: Vector, angle: Angle) { + let length = axis.length + guard length.isFinite, length > epsilon else { + return nil + } + self.init(unchecked: axis / length, angle: angle) + } /// Creates a rotation around the X axis. /// - Parameter rotation: The angle to rotate by. static func pitch(_ rotation: Angle) -> Rotation { - .init(.pitch(rotation)) + let r = -rotation.radians * 0.5 + return .init(unchecked: sin(r), 0, 0, cos(r)) } /// Creates a rotation around the Y axis. /// - Parameter rotation: The angle to rotate by. static func yaw(_ rotation: Angle) -> Rotation { - .init(.yaw(rotation)) + let r = -rotation.radians * 0.5 + return .init(unchecked: 0, sin(r), 0, cos(r)) } /// Creates a rotation around the Z axis. /// - Parameter rotation: The angle to rotate by. static func roll(_ rotation: Angle) -> Rotation { - .init(.roll(rotation)) - } - - /// Creates a new identity rotation. - init() { - self.init(.identity) - } - - /// Creates a rotation from an axis and angle. - /// - Parameters: - /// - axis: A vector defining the axis of rotation. - /// - end: The angle of rotation around the axis. - init?(axis: Vector, angle: Angle) { - guard let quaternion = Quaternion(axis: axis, angle: angle) else { - return nil - } - self.init(quaternion) - } - - /// Creates a rotation from a quaternion. - /// - Parameter quaternion: A quaternion defining a rotation. - init(_ quaternion: Quaternion) { - self.quaternion = quaternion + let r = -rotation.radians * 0.5 + return .init(unchecked: 0, 0, sin(r), cos(r)) } /// Creates a rotation from Euler angles applied in pitch/yaw/roll order. @@ -223,32 +420,22 @@ public extension Rotation { /// Rotation has no effect. var isIdentity: Bool { - quaternion.isIdentity - } - - /// The angle of rotation around the X-axis. - var pitch: Angle { - quaternion.pitch - } - - /// The angle of rotation around the Y-axis. - var yaw: Angle { - quaternion.yaw + abs(1 - w) < epsilon } /// The angle of rotation around the Z-axis. var roll: Angle { - quaternion.roll + -.atan2(y: 2 * (w * z + x * y), x: 1 - 2 * (y * y + z * z)) } - /// Axis of rotation - var axis: Vector { - quaternion.axis + /// The angle of rotation around the Y-axis. + var yaw: Angle { + -.asin(min(1, max(-1, 2 * (w * y - z * x)))) } - /// The angle of rotation. - var angle: Angle { - quaternion.angle + /// The angle of rotation around the X-axis. + var pitch: Angle { + -.atan2(y: 2 * (w * x + y * z), x: 1 - 2 * (x * x + y * y)) } /// A normalized direction vector pointing rightwards relative to the current rotation. @@ -266,30 +453,6 @@ public extension Rotation { Vector.unitZ.rotated(by: self) } - /// Performs a spherical linear interpolation between two rotations. - /// - Parameters: - /// - r: The rotation to interpolate towards. - /// - t: The normalized extent of interpolation, from 0 to 1. - /// - Returns: The interpolated rotation. - func slerp(_ r: Rotation, _ t: Double) -> Rotation { - .init(quaternion.slerp(r.quaternion, t)) - } - - /// Returns the reverse (aka transpose) rotation. - static prefix func - (rhs: Rotation) -> Rotation { - .init(-rhs.quaternion) - } - - /// Combines two rotations to get the cumulative rotation. - static func * (lhs: Rotation, rhs: Rotation) -> Rotation { - .init(lhs.quaternion * rhs.quaternion) - } - - /// Combines with the specified rotation. - static func *= (lhs: inout Rotation, rhs: Rotation) { - lhs.quaternion *= rhs.quaternion - } - /// Returns a rotation multiplied by the specified value. static func * (lhs: Rotation, rhs: Double) -> Rotation { .init(unchecked: lhs.axis, angle: lhs.angle * rhs) @@ -312,12 +475,24 @@ public extension Rotation { } extension Rotation { - init(unchecked axis: Vector, angle: Angle) { - self.init(.init(unchecked: axis, angle: angle)) - } - /// Approximate equality func isEqual(to other: Rotation, withPrecision p: Double = epsilon) -> Bool { - quaternion.isEqual(to: other.quaternion, withPrecision: p) + w.isEqual(to: other.w, withPrecision: p) && + x.isEqual(to: other.x, withPrecision: p) && + y.isEqual(to: other.y, withPrecision: p) && + z.isEqual(to: other.z, withPrecision: p) + } +} + +@available(*, deprecated) +extension Rotation { + var quaternion: Quaternion { + .init(x, y, z, w) + } + + /// Creates a rotation from a quaternion. + /// - Parameter quaternion: A quaternion defining a rotation. + public init(_ quaternion: Quaternion) { + self.init(quaternion.x, quaternion.y, quaternion.z, quaternion.w) } } diff --git a/Sources/Transforms.swift b/Sources/Transforms.swift index 9ef75c4f..0593e3a4 100644 --- a/Sources/Transforms.swift +++ b/Sources/Transforms.swift @@ -94,6 +94,7 @@ public extension Transformable { /// Returns a rotated copy of the value. /// - Parameter quaternion: A rotation to apply to the value. @_disfavoredOverload + @available(*, deprecated) func rotated(by quaternion: Quaternion) -> Self { rotated(by: Rotation(quaternion)) } @@ -101,6 +102,7 @@ public extension Transformable { /// Rotate the value in place. /// - Parameter quaternion: A rotation to apply to the value. @_disfavoredOverload + @available(*, deprecated) mutating func rotate(by quaternion: Quaternion) { self = rotated(by: quaternion) } @@ -420,11 +422,10 @@ extension Vector: Transformable { } public func rotated(by r: Rotation) -> Vector { - let q = r.quaternion - let qv = Vector(q.x, q.y, q.z) + let qv = Vector(r.x, r.y, r.z) let uv = qv.cross(self) let uuv = qv.cross(uv) - return self + (uv * 2 * q.w) + (uuv * 2) + return self + (uv * 2 * r.w) + (uuv * 2) } public func scaled(by v: Vector) -> Vector { diff --git a/Tests/BoundsTests.swift b/Tests/BoundsTests.swift index c57597ad..f8aa8991 100644 --- a/Tests/BoundsTests.swift +++ b/Tests/BoundsTests.swift @@ -124,7 +124,6 @@ class BoundsTests: XCTestCase { pitch: .radians(-0.5 * .pi) ) XCTAssert(Bounds.empty.rotated(by: rotation).isEmpty) - XCTAssert(Bounds.empty.rotated(by: Quaternion(rotation)).isEmpty) XCTAssert(Bounds.empty.transformed(by: .rotation(rotation)).isEmpty) } diff --git a/Tests/CodingTests.swift b/Tests/CodingTests.swift index f9759ad3..f1ad147e 100644 --- a/Tests/CodingTests.swift +++ b/Tests/CodingTests.swift @@ -954,7 +954,10 @@ class CodingTests: XCTestCase { let encoded = try encode(rotation) XCTAssert(try rotation.isEqual(to: decode(encoded))) } +} +@available(*, deprecated) +extension CodingTests { // MARK: Quaternion func testDecodingIdentityQuaternion() { diff --git a/Tests/QuaternionTests.swift b/Tests/QuaternionTests.swift index 32dc779f..a4060625 100644 --- a/Tests/QuaternionTests.swift +++ b/Tests/QuaternionTests.swift @@ -9,6 +9,7 @@ @testable import Euclid import XCTest +@available(*, deprecated) class QuaternionTests: XCTestCase { func testNormalizeZeroQuaternion() { let q = Quaternion.zero diff --git a/Tests/TransformTests.swift b/Tests/TransformTests.swift index 49fa980b..bf7c2b07 100644 --- a/Tests/TransformTests.swift +++ b/Tests/TransformTests.swift @@ -46,7 +46,7 @@ class TransformTests: XCTestCase { func testYawRotation() { let r = Rotation(yaw: .halfPi) XCTAssertEqual(r, .yaw(.halfPi)) - XCTAssert(r.isEqual(to: Rotation(Quaternion(yaw: .halfPi)))) + XCTAssert(r.isEqual(to: Rotation(yaw: .halfPi))) XCTAssertEqual(r.yaw, .halfPi) XCTAssertEqual(r.pitch, .zero) XCTAssertEqual(r.roll, .zero) @@ -74,6 +74,7 @@ class TransformTests: XCTestCase { XCTAssertEqual(pitch.radians, r.pitch.radians, accuracy: epsilon) } + @available(*, deprecated) func testRotationToQuaternion() { let roll = Angle.radians(2.31) let yaw = Angle.radians(0.2) @@ -92,14 +93,13 @@ class TransformTests: XCTestCase { -0.21565624395553415 ) XCTAssert(v.isNormalized) - let q = Quaternion( + let r = Rotation( 0.681812047958374, -0.0165534820407629, -0.028187578544020653, 0.7307965755462646 ) - XCTAssert(q.isNormalized) - let u = v.rotated(by: q) + let u = v.rotated(by: r) XCTAssert(u.isNormalized) }