@@ -49,7 +49,7 @@ extension Quaternion {
4949 /// - If a quaternion is not finite, its `.length` is `infinity`.
5050 ///
5151 /// See also `.magnitude`, `.lengthSquared`, `.polar` and
52- /// `init(length:,phase:, axis:)`.
52+ /// `init(length:halfAngle: axis:)`.
5353 @_transparent
5454 public var length : RealType {
5555 let naive = lengthSquared
@@ -70,131 +70,130 @@ extension Quaternion {
7070 /// The squared length `(r*r + x*x + y*y + z*z)`.
7171 ///
7272 /// This value is highly prone to overflow or underflow.
73- ///
73+ ///
7474 /// For many cases, `.magnitude` can be used instead, which is similarly
7575 /// cheap to compute and always returns a representable value.
7676 ///
7777 /// This property is more efficient to compute than `length`.
7878 ///
7979 /// See also `.magnitude`, `.length`, `.polar` and
80- /// `init(length:,phase:, axis:)`.
80+ /// `init(length:halfAngle: axis:)`.
8181 @_transparent
8282 public var lengthSquared : RealType {
8383 ( components * components) . sum ( )
8484 }
8585
86+ /// The half rotation angle in radians within *[0, π]* range.
87+ ///
88+ /// Edge cases:
89+ /// - If the quaternion is zero or non-finite, halfAngle is `nan`.
90+ @inlinable
91+ public var halfAngle : RealType {
92+ guard isFinite else { return . nan }
93+ // A zero quaternion does not encode transformation properties.
94+ // If imaginary is zero, real must be non-zero or nan is returned.
95+ guard !isReal else { return isPure ? . nan : . zero }
96+ // If lengthSquared computes without over/underflow, everything is fine
97+ // and the result is correct. If not, we have to do the computation
98+ // carefully and unscale the quaternion first.
99+ let lenSq = imaginary. lengthSquared
100+ guard lenSq. isNormal else { return divided ( by: magnitude) . halfAngle }
101+ return . atan2( y: . sqrt( lenSq) , x: real)
102+ }
103+
86104 /// The [polar decomposition][wiki].
87105 ///
88- /// Returns the length of this quaternion, phase in radians of range *[0, π]*
89- /// and the rotation axis as SIMD3 vector of unit length.
106+ /// Returns the length of this quaternion, halfAngle in radians of range
107+ /// *[0, π]* and the rotation axis as SIMD3 vector of unit length.
90108 ///
91109 /// Edge cases:
92- /// - If the quaternion is zero, length is `.zero` and angle and axis
110+ /// - If the quaternion is zero, length is `.zero` and halfAngle and axis
93111 /// are `nan`.
94- /// - If the quaternion is non-finite, length is `.infinity` and angle and
112+ /// - If the quaternion is non-finite, length is `.infinity` and halfAngle and
95113 /// axis are `nan`.
96- /// - For any length other than `.zero` or `.infinity`, if angle is zero, axis
97- /// is `nan`.
114+ /// - For any length other than `.zero` or `.infinity`, if halfAngle is zero,
115+ /// axis is `nan`.
98116 ///
99117 /// See also `.magnitude`, `.length`, `.lengthSquared` and
100- /// `init(length:,phase:, axis:)`.
118+ /// `init(length:halfAngle: axis:)`.
101119 ///
102120 /// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition
103- public var polar : ( length: RealType , phase: RealType , axis: SIMD3 < RealType > ) {
121+ public var polar : (
122+ length: RealType ,
123+ halfAngle: RealType ,
124+ axis: SIMD3 < RealType >
125+ ) {
104126 ( length, halfAngle, axis)
105127 }
106128
129+ /// Creates a new quaternion from given half rotation angle about given
130+ /// rotation axis.
131+ ///
132+ /// The angle-axis values are transformed using the following equation:
133+ ///
134+ /// Q = (cos(halfAngle), unitAxis * sin(halfAngle))
135+ ///
136+ /// - Parameters:
137+ /// - halfAngle: The half rotation angle
138+ /// - unitAxis: The rotation axis of unit length
139+ @usableFromInline @inline ( __always)
140+ internal init ( halfAngle: RealType , unitAxis: SIMD3 < RealType > ) {
141+ self . init ( real: . cos( halfAngle) , imaginary: unitAxis * . sin( halfAngle) )
142+ }
143+
107144 /// Creates a quaternion specified with [polar coordinates][wiki].
108145 ///
109- /// This initializer reads given `length`, `phase ` and `axis` values and
146+ /// This initializer reads given `length`, `halfAngle ` and `axis` values and
110147 /// creates a quaternion of equal rotation properties and specified *length*
111148 /// using the following equation:
112149 ///
113- /// Q = (cos(phase), axis * sin(phase)) * length
114- ///
115- /// - Note: `axis` must be of unit length, or an assertion failure occurs.
150+ /// Q = (cos(halfAngle), axis * sin(halfAngle)) * length
116151 ///
117152 /// Edge cases:
118153 /// - Negative lengths are interpreted as reflecting the point through the
119154 /// origin, i.e.:
120155 /// ```
121- /// Quaternion(length: -r, phase : θ, axis: axis) == -Quaternion(length: r, phase : θ, axis: axis)
156+ /// Quaternion(length: -r, halfAngle : θ, axis: axis) == -Quaternion(length: r, halfAngle : θ, axis: axis)
122157 /// ```
123158 /// - For any `θ` and any `axis`, even `.infinity` or `.nan`:
124159 /// ```
125- /// Quaternion(length: .zero, phase : θ, axis: axis) == .zero
160+ /// Quaternion(length: .zero, halfAngle : θ, axis: axis) == .zero
126161 /// ```
127162 /// - For any `θ` and any `axis`, even `.infinity` or `.nan`:
128163 /// ```
129- /// Quaternion(length: .infinity, phase : θ, axis: axis) == .infinity
164+ /// Quaternion(length: .infinity, halfAngle : θ, axis: axis) == .infinity
130165 /// ```
131- /// - Otherwise, `θ` must be finite, or a precondition failure occurs.
166+ /// - Otherwise, `θ` must be finite, or a precondition failure occurs and
167+ /// `axis` must be of unit length, or an assertion failure occurs.
132168 ///
133169 /// See also `.magnitude`, `.length`, `.lengthSquared` and `.polar`.
134170 ///
135171 /// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition
136172 @inlinable
137- public init ( length: RealType , phase : RealType , axis: SIMD3 < RealType > ) {
173+ public init ( length: RealType , halfAngle : RealType , axis: SIMD3 < RealType > ) {
138174 guard !length. isZero, length. isFinite else {
139175 self = Quaternion ( length)
140176 return
141177 }
142178
143179 // Length is finite and non-zero, therefore
144- // 1. `phase ` must be finite or a precondition failure needs to occur; as
145- // this is not representable.
180+ // 1. `halfAngle ` must be finite or a precondition failure needs to occur;
181+ // as this is not representable.
146182 // 2. `axis` must be of unit length or an assertion failure occurs; while
147183 // "wrong" by definition, it is representable.
148184 precondition (
149- phase . isFinite,
150- " Either phase must be finite, or length must be zero or infinite. "
185+ halfAngle . isFinite,
186+ " Either halfAngle must be finite, or length must be zero or infinite. "
151187 )
152188 assert (
153189 // TODO: Replace with `approximateEquality()`
154190 abs ( . sqrt( axis. lengthSquared) - 1 ) < max ( . sqrt( axis. lengthSquared) , 1 ) * RealType. ulpOfOne. squareRoot ( ) ,
155191 " Given axis must be of unit length. "
156192 )
157193
158- self = Quaternion ( halfAngle: phase, unitAxis: axis) . multiplied ( by: length)
159- }
160- }
161-
162- // MARK: - Operations for working with polar form
163-
164- extension Quaternion {
165- /// The half rotation angle in radians within *[0, π]* range.
166- ///
167- /// Edge cases:
168- /// - If the quaternion is zero or non-finite, halfAngle is `nan`.
169- @usableFromInline @inline ( __always)
170- internal var halfAngle : RealType {
171- guard isFinite else { return . nan }
172- guard imaginary != . zero else {
173- // A zero quaternion does not encode transformation properties.
174- // If imaginary is zero, real must be non-zero or nan is returned.
175- return real. isZero ? . nan : . zero
176- }
177-
178- // If lengthSquared computes without over/underflow, everything is fine
179- // and the result is correct. If not, we have to do the computation
180- // carefully and unscale the quaternion first.
181- let lenSq = imaginary. lengthSquared
182- guard lenSq. isNormal else { return divided ( by: magnitude) . halfAngle }
183- return . atan2( y: . sqrt( lenSq) , x: real)
184- }
185-
186- /// Creates a new quaternion from given half rotation angle about given
187- /// rotation axis.
188- ///
189- /// The angle-axis values are transformed using the following equation:
190- ///
191- /// Q = (cos(halfAngle), unitAxis * sin(halfAngle))
192- ///
193- /// - Parameters:
194- /// - halfAngle: The half rotation angle
195- /// - unitAxis: The rotation axis of unit length
196- @usableFromInline @inline ( __always)
197- internal init ( halfAngle: RealType , unitAxis: SIMD3 < RealType > ) {
198- self . init ( real: . cos( halfAngle) , imaginary: unitAxis * . sin( halfAngle) )
194+ self = Quaternion (
195+ halfAngle: halfAngle,
196+ unitAxis: axis
197+ ) . multiplied ( by: length)
199198 }
200199}
0 commit comments