|
| 1 | +//===--- Polar.swift ------------------------------------------*- swift -*-===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift Numerics open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2019 - 2022 Apple Inc. and the Swift Numerics project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// |
| 10 | +//===----------------------------------------------------------------------===// |
| 11 | + |
| 12 | +// Norms and related quantities defined for Quaternion. |
| 13 | +// |
| 14 | +// The following API are provided by this extension: |
| 15 | +// |
| 16 | +// var magnitude: RealType // infinity norm |
| 17 | +// var length: RealType // Euclidean norm |
| 18 | +// var lengthSquared: RealType // Euclidean norm squared |
| 19 | +// |
| 20 | +// For detailed documentation, consult Norms.md or the inline documentation |
| 21 | +// for each operation. |
| 22 | +// |
| 23 | +// Implementation notes: |
| 24 | +// |
| 25 | +// `.magnitude` does not bind the Euclidean norm; it binds the infinity norm |
| 26 | +// instead. There are two reasons for this choice: |
| 27 | +// |
| 28 | +// - It's simply faster to compute in general, because it does not require |
| 29 | +// a square root. |
| 30 | +// |
| 31 | +// - There exist finite values `q` for which the Euclidean norm is not |
| 32 | +// representable (consider the quaternion with `r`, `x`, `y` and `z` all |
| 33 | +// equal to `RealType.greatestFiniteMagnitude`; the Euclidean norm is |
| 34 | +// `.sqrt(4) * .greatestFiniteMagnitude`, which overflows). |
| 35 | +// |
| 36 | +// The infinity norm is unique among the common vector norms in having |
| 37 | +// the property that every finite vector has a representable finite norm, |
| 38 | +// which makes it the obvious choice to bind `.magnitude`. |
| 39 | +extension Quaternion { |
| 40 | + /// The Euclidean norm (a.k.a. 2-norm, `sqrt(r*r + x*x + y*y + z*z)`). |
| 41 | + /// |
| 42 | + /// This value is highly prone to overflow or underflow. |
| 43 | + /// |
| 44 | + /// For most use cases, you can use the cheaper `.magnitude` |
| 45 | + /// property (which computes the ∞-norm) instead, which always produces |
| 46 | + /// a representable result. |
| 47 | + /// |
| 48 | + /// Edge cases: |
| 49 | + /// - |
| 50 | + /// If a quaternion is not finite, its `.length` is `infinity`. |
| 51 | + /// |
| 52 | + /// See also: |
| 53 | + /// - |
| 54 | + /// - `.magnitude` |
| 55 | + /// - `.lengthSquared` |
| 56 | + @_transparent |
| 57 | + public var length: RealType { |
| 58 | + let naive = lengthSquared |
| 59 | + guard naive.isNormal else { return carefulLength } |
| 60 | + return .sqrt(naive) |
| 61 | + } |
| 62 | + |
| 63 | + // Internal implementation detail of `length`, moving slow path off |
| 64 | + // of the inline function. |
| 65 | + @usableFromInline |
| 66 | + internal var carefulLength: RealType { |
| 67 | + guard isFinite else { return .infinity } |
| 68 | + guard !magnitude.isZero else { return .zero } |
| 69 | + // Unscale the quaternion, calculate its length and rescale the result |
| 70 | + return divided(by: magnitude).length * magnitude |
| 71 | + } |
| 72 | + |
| 73 | + /// The squared length `(r*r + x*x + y*y + z*z)`. |
| 74 | + /// |
| 75 | + /// This value is highly prone to overflow or underflow. |
| 76 | + /// |
| 77 | + /// For many cases, `.magnitude` can be used instead, which is similarly |
| 78 | + /// cheap to compute and always returns a representable value. |
| 79 | + /// |
| 80 | + /// This property is more efficient to compute than `length`. |
| 81 | + /// |
| 82 | + /// See also: |
| 83 | + /// - |
| 84 | + /// - `.length` |
| 85 | + /// - `.magnitude` |
| 86 | + @_transparent |
| 87 | + public var lengthSquared: RealType { |
| 88 | + (components * components).sum() |
| 89 | + } |
| 90 | + |
| 91 | + /// The [polar decomposition][wiki]. |
| 92 | + /// |
| 93 | + /// Returns the length of this quaternion, phase in radians of range *[0, π]* |
| 94 | + /// and the rotation axis as SIMD3 vector of unit length. |
| 95 | + /// |
| 96 | + /// Edge cases: |
| 97 | + /// - |
| 98 | + /// - If the quaternion is zero, length is `.zero` and angle and axis |
| 99 | + /// are `nan`. |
| 100 | + /// - If the quaternion is non-finite, length is `.infinity` and angle and |
| 101 | + /// axis are `nan`. |
| 102 | + /// - For any length other than `.zero` or `.infinity`, if angle is zero, axis |
| 103 | + /// is `nan`. |
| 104 | + /// |
| 105 | + /// See also: |
| 106 | + /// - |
| 107 | + /// - `.angle` |
| 108 | + /// - `.axis` |
| 109 | + /// - `.angleAxis` |
| 110 | + /// - `.rotationVector` |
| 111 | + /// - `init(length:angle:axis:)` |
| 112 | + /// - `init(length:phase:axis)` |
| 113 | + /// - `init(rotation:)` |
| 114 | + /// |
| 115 | + /// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition |
| 116 | + public var polar: (length: RealType, phase: RealType, axis: SIMD3<RealType>) { |
| 117 | + (length, halfAngle, axis) |
| 118 | + } |
| 119 | + |
| 120 | + /// Creates a quaternion specified with [polar coordinates][wiki]. |
| 121 | + /// |
| 122 | + /// This initializer reads given `length`, `phase` and `axis` values and |
| 123 | + /// creates a quaternion of equal rotation properties and specified *length* |
| 124 | + /// using the following equation: |
| 125 | + /// |
| 126 | + /// Q = (cos(phase), axis * sin(phase)) * length |
| 127 | + /// |
| 128 | + /// - Note: `axis` must be of unit length, or an assertion failure occurs. |
| 129 | + /// |
| 130 | + /// Edge cases: |
| 131 | + /// - |
| 132 | + /// - Negative lengths are interpreted as reflecting the point through the origin, i.e.: |
| 133 | + /// ``` |
| 134 | + /// Quaternion(length: -r, phase: θ, axis: axis) == -Quaternion(length: r, phase: θ, axis: axis) |
| 135 | + /// ``` |
| 136 | + /// - For any `θ` and any `axis`, even `.infinity` or `.nan`: |
| 137 | + /// ``` |
| 138 | + /// Quaternion(length: .zero, phase: θ, axis: axis) == .zero |
| 139 | + /// ``` |
| 140 | + /// - For any `θ` and any `axis`, even `.infinity` or `.nan`: |
| 141 | + /// ``` |
| 142 | + /// Quaternion(length: .infinity, phase: θ, axis: axis) == .infinity |
| 143 | + /// ``` |
| 144 | + /// - Otherwise, `θ` must be finite, or a precondition failure occurs. |
| 145 | + /// |
| 146 | + /// See also: |
| 147 | + /// - |
| 148 | + /// - `.angle` |
| 149 | + /// - `.axis` |
| 150 | + /// - `.angleAxis` |
| 151 | + /// - `.rotationVector` |
| 152 | + /// - `.polar` |
| 153 | + /// - `init(length:angle:axis:)` |
| 154 | + /// - `init(rotation:)` |
| 155 | + /// |
| 156 | + /// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition |
| 157 | + @inlinable |
| 158 | + public init(length: RealType, phase: RealType, axis: SIMD3<RealType>) { |
| 159 | + guard !length.isZero, length.isFinite else { |
| 160 | + self = Quaternion(length) |
| 161 | + return |
| 162 | + } |
| 163 | + |
| 164 | + // Length is finite and non-zero, therefore |
| 165 | + // 1. `phase` must be finite or a precondition failure needs to occur; as |
| 166 | + // this is not representable. |
| 167 | + // 2. `axis` must be of unit length or an assertion failure occurs; while |
| 168 | + // "wrong" by definition, it is representable. |
| 169 | + precondition( |
| 170 | + phase.isFinite, |
| 171 | + "Either phase must be finite, or length must be zero or infinite." |
| 172 | + ) |
| 173 | + assert( |
| 174 | + // TODO: Replace with `approximateEquality()` |
| 175 | + abs(.sqrt(axis.lengthSquared)-1) < max(.sqrt(axis.lengthSquared), 1)*RealType.ulpOfOne.squareRoot(), |
| 176 | + "Given axis must be of unit length." |
| 177 | + ) |
| 178 | + |
| 179 | + self = Quaternion(halfAngle: phase, unitAxis: axis).multiplied(by: length) |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +// MARK: - Operations for working with polar form |
| 184 | + |
| 185 | +extension Quaternion { |
| 186 | + /// The half rotation angle in radians within *[0, π]* range. |
| 187 | + /// |
| 188 | + /// Edge cases: |
| 189 | + /// - |
| 190 | + /// If the quaternion is zero or non-finite, halfAngle is `nan`. |
| 191 | + @usableFromInline @inline(__always) |
| 192 | + internal var halfAngle: RealType { |
| 193 | + guard isFinite else { return .nan } |
| 194 | + guard imaginary != .zero else { |
| 195 | + // A zero quaternion does not encode transformation properties. |
| 196 | + // If imaginary is zero, real must be non-zero or nan is returned. |
| 197 | + return real.isZero ? .nan : .zero |
| 198 | + } |
| 199 | + |
| 200 | + // If lengthSquared computes without over/underflow, everything is fine |
| 201 | + // and the result is correct. If not, we have to do the computation |
| 202 | + // carefully and unscale the quaternion first. |
| 203 | + let lenSq = imaginary.lengthSquared |
| 204 | + guard lenSq.isNormal else { return divided(by: magnitude).halfAngle } |
| 205 | + return .atan2(y: .sqrt(lenSq), x: real) |
| 206 | + } |
| 207 | + |
| 208 | + /// Creates a new quaternion from given half rotation angle about given |
| 209 | + /// rotation axis. |
| 210 | + /// |
| 211 | + /// The angle-axis values are transformed using the following equation: |
| 212 | + /// |
| 213 | + /// Q = (cos(halfAngle), unitAxis * sin(halfAngle)) |
| 214 | + /// |
| 215 | + /// - Parameters: |
| 216 | + /// - halfAngle: The half rotation angle |
| 217 | + /// - unitAxis: The rotation axis of unit length |
| 218 | + @usableFromInline @inline(__always) |
| 219 | + internal init(halfAngle: RealType, unitAxis: SIMD3<RealType>) { |
| 220 | + self.init(real: .cos(halfAngle), imaginary: unitAxis * .sin(halfAngle)) |
| 221 | + } |
| 222 | +} |
0 commit comments