Skip to content

Commit 231d08a

Browse files
markuswntrstephentyrone
authored andcommitted
Add length parameter to angle/axis initializer
1 parent 7f65bec commit 231d08a

File tree

1 file changed

+54
-39
lines changed

1 file changed

+54
-39
lines changed

Sources/QuaternionModule/Transformation.swift

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ extension Quaternion {
2525
/// - `.angleAxis`
2626
/// - `.polar`
2727
/// - `.rotationVector`
28-
/// - `init(angle:axis:)`
29-
/// - `init(length:angle:axis)`
28+
/// - `init(length:angle:axis:)`
29+
/// - `init(length:phase:axis)`
3030
/// - `init(rotation:)`
3131
///
3232
/// [wiki]: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Recovering_the_axis-angle_representation
@@ -51,23 +51,23 @@ extension Quaternion {
5151
/// - `.angleAxis`
5252
/// - `.polar`
5353
/// - `.rotationVector`
54-
/// - `init(angle:axis:)`
55-
/// - `init(length:angle:axis)`
54+
/// - `init(length:angle:axis:)`
55+
/// - `init(length:phase:axis)`
5656
/// - `init(rotation:)`
5757
///
5858
/// [wiki]: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Recovering_the_axis-angle_representation
5959
@inlinable
6060
public var axis: SIMD3<RealType> {
61-
guard isFinite && imaginary != .zero && !real.isZero else {
61+
guard isFinite, imaginary != .zero, real != .zero else {
6262
return SIMD3(repeating: .nan)
6363
}
6464
return imaginary / .sqrt(imaginary.lengthSquared)
6565
}
6666

6767
/// The [Angle-Axis][wiki] representation.
6868
///
69-
/// Returns the rotation angle in radians within *[0, 2π]* and the rotation
70-
/// axis as SIMD3 vector of unit length.
69+
/// Returns the length of the quaternion, the rotation angle in radians
70+
/// within *[0, 2π]* and the rotation axis as SIMD3 vector of unit length.
7171
///
7272
/// Edge cases:
7373
/// -
@@ -80,13 +80,13 @@ extension Quaternion {
8080
/// - `.axis`
8181
/// - `.polar`
8282
/// - `.rotationVector`
83-
/// - `init(angle:axis:)`
84-
/// - `init(length:angle:axis)`
83+
/// - `init(length:angle:axis:)`
84+
/// - `init(length:phase:axis)`
8585
/// - `init(rotation:)`
8686
///
8787
/// [wiki]: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Recovering_the_axis-angle_representation
88-
public var angleAxis: (angle: RealType, axis: SIMD3<RealType>) {
89-
(angle, axis)
88+
public var angleAxis: (length: RealType, angle: RealType, axis: SIMD3<RealType>) {
89+
(length, angle, axis)
9090
}
9191

9292
/// The [rotation vector][rotvector].
@@ -109,8 +109,8 @@ extension Quaternion {
109109
/// - `.angle`
110110
/// - `.axis`
111111
/// - `.angleAxis`
112-
/// - `init(angle:axis:)`
113-
/// - `init(length:angle:axis)`
112+
/// - `init(length:angle:axis:)`
113+
/// - `init(length:phase:axis)`
114114
/// - `init(rotation:)`
115115
///
116116
/// [rotvector]: https://en.wikipedia.org/wiki/Axis–angle_representation#Rotation_vector
@@ -139,8 +139,8 @@ extension Quaternion {
139139
/// - `.axis`
140140
/// - `.angleAxis`
141141
/// - `.rotationVector`
142-
/// - `init(angle:axis:)`
143-
/// - `init(length:angle:axis)`
142+
/// - `init(length:angle:axis:)`
143+
/// - `init(length:phase:axis)`
144144
/// - `init(rotation:)`
145145
///
146146
/// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition
@@ -150,28 +150,31 @@ extension Quaternion {
150150

151151
/// Creates a unit quaternion specified with [Angle-Axis][wiki] values.
152152
///
153-
/// Angle-Axis is a representation of a 3D rotation using two different
154-
/// quantities: an angle describing the magnitude of rotation, and a vector
155-
/// of unit length indicating the axis direction to rotate along.
153+
/// Angle-Axis is a representation of a three-dimensional rotation using two
154+
/// different quantities: an angle describing the magnitude of rotation, and
155+
/// a vector of unit length indicating the axis direction to rotate along.
156+
/// The optional length parameter scales the quaternion after the conversion.
156157
///
157-
/// This initializer reads given `angle` and `axis` values and creates a
158-
/// quaternion of equal rotation properties using the following equation:
158+
/// This initializer reads given `length`, `angle` and `axis` values and
159+
/// creates a quaternion of equal rotation properties and of specified length
160+
/// using the following equation:
159161
///
160-
/// Q = (cos(angle/2), axis * sin(angle/2))
162+
/// Q = (cos(angle/2), axis * sin(angle/2)) * length
161163
///
162-
/// Given `axis` gets normalized if it is not of unit length.
164+
/// If `length` is not specified, it defaults to *1*; and the final
165+
/// quaternion is of unit length.
163166
///
164-
/// The final quaternion is of unit length.
167+
/// - Note: `axis` must be of unit length, or an assertion failure occurs.
165168
///
166169
/// Edge cases:
167170
/// -
168171
/// - For any `θ`, even `.infinity` or `.nan`:
169172
/// ```
170-
/// Quaternion(angle: θ, axis: .zero) == .zero
173+
/// Quaternion(length: .zero, angle: θ, axis: axis) == .zero
171174
/// ```
172175
/// - For any `θ`, even `.infinity` or `.nan`:
173176
/// ```
174-
/// Quaternion(angle: θ, axis: .infinity) == .ininfity
177+
/// Quaternion(length: .infinity, angle: θ, axis: axis) == .ininfity
175178
/// ```
176179
/// - Otherwise, `θ` must be finite, or a precondition failure occurs.
177180
///
@@ -183,24 +186,36 @@ extension Quaternion {
183186
/// - `.rotationVector`
184187
/// - `.polar`
185188
/// - `init(rotation:)`
186-
/// - `init(length:angle:axis)`
189+
/// - `init(length:phase:axis)`
187190
///
188-
/// - Parameter angle: The rotation angle about the rotation axis in radians
189-
/// - Parameter axis: The rotation axis
191+
/// - Parameter length: The length of the quaternion. Defaults to `1`.
192+
/// - Parameter angle: The rotation angle about the rotation axis in radians.
193+
/// - Parameter axis: The rotation axis. Must be of unit length.
190194
///
191195
/// [wiki]: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Recovering_the_axis-angle_representation
192196
@inlinable
193-
public init(angle: RealType, axis: SIMD3<RealType>) {
194-
let length: RealType = .sqrt(axis.lengthSquared)
195-
if angle.isFinite && length.isNormal {
196-
self = Quaternion(halfAngle: angle/2, unitAxis: axis/length)
197-
} else {
198-
precondition(
199-
length.isZero || length.isInfinite,
200-
"Either angle must be finite, or axis length must be zero or infinite."
201-
)
197+
public init(length: RealType = 1, angle: RealType, axis: SIMD3<RealType>) {
198+
guard !length.isZero, length.isFinite else {
202199
self = Quaternion(length)
200+
return
203201
}
202+
203+
// Length is finite and non-zero, therefore
204+
// 1. `angle` must be finite or a precondition failure needs to occur; as
205+
// this is not representable.
206+
// 2. `axis` must be of unit length or an assertion failure occurs; while
207+
// "wrong" by definition, it is representable.
208+
precondition(
209+
angle.isFinite,
210+
"Either angle must be finite, or length must be zero or infinite."
211+
)
212+
assert(
213+
// TODO: Replace with `approximateEquality()`
214+
abs(.sqrt(axis.lengthSquared)-1) < max(.sqrt(axis.lengthSquared), 1)*RealType.ulpOfOne.squareRoot(),
215+
"Given axis must be of unit length."
216+
)
217+
218+
self = Quaternion(halfAngle: angle/2,unitAxis: axis).multiplied(by: length)
204219
}
205220

206221
/// Creates a unit quaternion specified with given [rotation vector][wiki].
@@ -239,8 +254,8 @@ extension Quaternion {
239254
/// - `.angleAxis`
240255
/// - `.polar`
241256
/// - `.rotationVector`
242-
/// - `init(angle:axis:)`
243-
/// - `init(length:angle:axis)`
257+
/// - `init(length:angle:axis:)`
258+
/// - `init(length:phase:axis)`
244259
///
245260
/// - Parameter vector: The rotation vector.
246261
///

0 commit comments

Comments
 (0)