@@ -58,10 +58,14 @@ extension Quaternion {
58
58
/// [wiki]: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Recovering_the_axis-angle_representation
59
59
@inlinable
60
60
public var axis : SIMD3 < RealType > {
61
- guard isFinite, imaginary != . zero, real != . zero else {
62
- return SIMD3 ( repeating: . nan)
63
- }
64
- return imaginary / . sqrt( imaginary. lengthSquared)
61
+ guard isFinite, imaginary != . zero else { return SIMD3 ( repeating: . nan) }
62
+
63
+ // If lengthSquared computes without over/underflow, everything is fine
64
+ // and the result is correct. If not, we have to do the computation
65
+ // carefully and unscale the quaternion first.
66
+ let lenSq = imaginary. lengthSquared
67
+ guard lenSq. isNormal else { return divided ( by: magnitude) . axis }
68
+ return imaginary / . sqrt( lenSq)
65
69
}
66
70
67
71
/// The [Angle-Axis][wiki] representation.
@@ -267,7 +271,7 @@ extension Quaternion {
267
271
@inlinable
268
272
public init ( rotation vector: SIMD3 < RealType > ) {
269
273
let angle : RealType = . sqrt( vector. lengthSquared)
270
- if !angle. isZero && angle. isFinite {
274
+ if !angle. isZero, angle. isFinite {
271
275
self = Quaternion ( halfAngle: angle/ 2 , unitAxis: vector/ angle)
272
276
} else {
273
277
self = Quaternion ( angle)
@@ -404,8 +408,19 @@ extension Quaternion {
404
408
/// If the quaternion is zero or non-finite, halfAngle is `nan`.
405
409
@usableFromInline @inline ( __always)
406
410
internal var halfAngle : RealType {
407
- guard !isZero && isFinite else { return . nan }
408
- return . atan2( y: . sqrt( imaginary. lengthSquared) , x: real)
411
+ guard isFinite else { return . nan }
412
+ guard imaginary != . zero else {
413
+ // A zero quaternion does not encode transformation properties.
414
+ // If imaginary is zero, real must be non-zero or nan is returned.
415
+ return real. isZero ? . nan : . zero
416
+ }
417
+
418
+ // If lengthSquared computes without over/underflow, everything is fine
419
+ // and the result is correct. If not, we have to do the computation
420
+ // carefully and unscale the quaternion first.
421
+ let lenSq = imaginary. lengthSquared
422
+ guard lenSq. isNormal else { return divided ( by: magnitude) . halfAngle }
423
+ return . atan2( y: . sqrt( lenSq) , x: real)
409
424
}
410
425
411
426
/// Creates a new quaternion from given half rotation angle about given
@@ -430,18 +445,18 @@ extension Quaternion {
430
445
// and *(x,y,z)* axis representations internally to the module.
431
446
extension SIMD3 where Scalar: FloatingPoint {
432
447
433
- /// Returns the squared length of this instance.
434
- @usableFromInline @inline ( __always)
435
- internal var lengthSquared : Scalar {
436
- dot ( self )
437
- }
438
-
439
448
/// True if all values of this instance are finite
440
449
@usableFromInline @inline ( __always)
441
450
internal var isFinite : Bool {
442
451
x. isFinite && y. isFinite && z. isFinite
443
452
}
444
453
454
+ /// Returns the squared length of this instance.
455
+ @usableFromInline @inline ( __always)
456
+ internal var lengthSquared : Scalar {
457
+ dot ( self )
458
+ }
459
+
445
460
/// Returns the scalar/dot product of this vector with `other`.
446
461
@usableFromInline @inline ( __always)
447
462
internal func dot( _ other: SIMD3 < Scalar > ) -> Scalar {
0 commit comments