Skip to content

Commit d196252

Browse files
markuswntrstephentyrone
authored andcommitted
Add method to rotate a vector by a quaternion
1 parent d19d86f commit d196252

File tree

1 file changed

+55
-9
lines changed

1 file changed

+55
-9
lines changed

Sources/QuaternionModule/Transformation.swift

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,42 @@ extension Quaternion {
308308
self = Quaternion(length)
309309
}
310310
}
311+
312+
/// Rotates given vector by this quaternion.
313+
///
314+
/// As quaternions can be used to represent three-dimensional rotations, it is
315+
/// is possible to also rotate a three-dimensional vector by a quaternion. The
316+
/// rotation of an arbitrary vector by a quaternion is known as an action.
317+
///
318+
/// - Note: This method assumes this quaternion is of unit length.
319+
///
320+
/// Edge cases:
321+
/// -
322+
/// - If `vector` is `.infinity` in any of the lanes or all, the returning
323+
/// vector is `.infinity` in all lanes:
324+
/// ```
325+
/// Quaternion(rotation: .zero) == .zero
326+
/// ```
327+
///
328+
/// - Parameter vector: A vector to rotate by this quaternion
329+
/// - Returns: The vector rotated by this quaternion
330+
@inlinable
331+
public func act(on vector: SIMD3<RealType>) -> SIMD3<RealType> {
332+
guard vector.isFinite else { return SIMD3(repeating: .infinity) }
333+
334+
// The following expression have been split up so the type-checker
335+
// can resolve them in a reasonable time.
336+
let p1 = vector * (real*real - imaginary.lengthSquared)
337+
let p2 = 2 * imaginary * imaginary.dot(vector)
338+
let p3 = 2 * real * imaginary.cross(vector)
339+
let rotatedVector = p1 + p2 + p3
340+
if rotatedVector.isFinite { return rotatedVector }
341+
342+
// If the vector is no longer finite after it is rotated, scale it down,
343+
// rotate it again and then scale it back-up after the rotation operation
344+
let scale = max(abs(vector.max()), abs(vector.min()))
345+
return act(on: vector/scale) * scale
346+
}
311347
}
312348

313349
// MARK: - Transformation Helper
@@ -351,19 +387,29 @@ extension Quaternion {
351387
// and *(x,y,z)* axis representations internally to the module.
352388
extension SIMD3 where Scalar: FloatingPoint {
353389

354-
/// Returns the squared length of this SIMD3 instance.
390+
/// Returns the squared length of this instance.
355391
@usableFromInline @inline(__always)
356392
internal var lengthSquared: Scalar {
357-
(self * self).sum()
393+
dot(self)
394+
}
395+
396+
/// True if all values of this instance are finite
397+
@usableFromInline @inline(__always)
398+
internal var isFinite: Bool {
399+
x.isFinite && y.isFinite && z.isFinite
400+
}
401+
402+
/// Returns the scalar/dot product of this vector with `other`.
403+
@usableFromInline @inline(__always)
404+
internal func dot(_ other: SIMD3<Scalar>) -> Scalar {
405+
(self * other).sum()
358406
}
359407

360-
/// Returns the vector/cross product of this quaternion with `other`.
408+
/// Returns the vector/cross product of this vector with `other`.
361409
@usableFromInline @inline(__always)
362-
internal func vectorProduct(with other: SIMD3<Scalar>) -> SIMD3<Scalar> {
363-
let selfYZW = self[SIMD3<Int>(1,2,0)]
364-
let otherYZX = other[SIMD3<Int>(1,2,0)]
365-
let selfZXY = self[SIMD3<Int>(2,0,1)]
366-
let otherZXY = other[SIMD3<Int>(2,0,1)]
367-
return (selfYZW * otherZXY) - (selfZXY * otherYZX)
410+
internal func cross(_ other: SIMD3<Scalar>) -> SIMD3<Scalar> {
411+
let yzx = SIMD3<Int>(1,2,0)
412+
let zxy = SIMD3<Int>(2,0,1)
413+
return (self[yzx] * other[zxy]) - (self[zxy] * other[yzx])
368414
}
369415
}

0 commit comments

Comments
 (0)