Skip to content

Commit 5ac7546

Browse files
Merge pull request #180 from markuswntr/quaternion/patch-2
Fix potential infinite loop in quaternion act
2 parents 446c15d + f60066e commit 5ac7546

File tree

2 files changed

+21
-12
lines changed

2 files changed

+21
-12
lines changed

Sources/QuaternionModule/Transformation.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -387,15 +387,21 @@ extension Quaternion {
387387
// The following expression have been split up so the type-checker
388388
// can resolve them in a reasonable time.
389389
let p1 = vector * (real*real - imaginary.lengthSquared)
390-
let p2 = 2 * imaginary * imaginary.dot(vector)
391-
let p3 = 2 * real * imaginary.cross(vector)
392-
let rotatedVector = p1 + p2 + p3
390+
let p2 = imaginary * imaginary.dot(vector)
391+
let p3 = imaginary.cross(vector) * real
392+
let rotatedVector = p1 + (p2 + p3) * 2
393393

394394
// If the rotation computes without over/underflow, everything is fine
395395
// and the result is correct. If not, we have to do the computation
396396
// carefully and first unscale the vector, rotate it again and then
397397
// rescale the vector
398-
if rotatedVector.isNormal { return rotatedVector }
398+
if
399+
(rotatedVector.x.isNormal || rotatedVector.x.isZero) &&
400+
(rotatedVector.y.isNormal || rotatedVector.y.isZero) &&
401+
(rotatedVector.z.isNormal || rotatedVector.z.isZero)
402+
{
403+
return rotatedVector
404+
}
399405
let scale = max(abs(vector.max()), abs(vector.min()))
400406
return act(on: vector/scale) * scale
401407
}
@@ -459,12 +465,6 @@ extension SIMD3 where Scalar: FloatingPoint {
459465
x.isFinite && y.isFinite && z.isFinite
460466
}
461467

462-
/// True if all values of this instance are finite
463-
@usableFromInline @inline(__always)
464-
internal var isNormal: Bool {
465-
x.isNormal && y.isNormal && z.isNormal
466-
}
467-
468468
/// Returns the squared length of this instance.
469469
@usableFromInline @inline(__always)
470470
internal var lengthSquared: Scalar {

Tests/QuaternionTests/TransformationTests.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ final class TransformationTests: XCTestCase {
3030
XCTAssertEqual(Quaternion<T>(angle: .pi, axis: -xAxis).angle, .pi)
3131
XCTAssertEqual(Quaternion<T>(angle: .pi, axis: -xAxis).axis, -xAxis)
3232
// Negative angle, negative axis
33-
XCTAssertEqual(Quaternion<T>.init(angle: -.pi, axis: -xAxis).angle, .pi)
34-
XCTAssertEqual(Quaternion<T>.init(angle: -.pi, axis: -xAxis).axis, xAxis)
33+
XCTAssertEqual(Quaternion<T>(angle: -.pi, axis: -xAxis).angle, .pi)
34+
XCTAssertEqual(Quaternion<T>(angle: -.pi, axis: -xAxis).axis, xAxis)
3535
}
3636

3737
func testAngleAxis() {
@@ -221,6 +221,15 @@ final class TransformationTests: XCTestCase {
221221
let vector = SIMD3<T>(1,1,1)
222222
let xAxis = SIMD3<T>(1,0,0)
223223

224+
let singleAxis = Quaternion<T>(angle: .pi/2, axis: SIMD3(0, 1, 0))
225+
XCTAssertTrue(singleAxis.act(on: xAxis).x.isApproximatelyEqual(
226+
to: .zero, absoluteTolerance: T.ulpOfOne.squareRoot()
227+
))
228+
XCTAssertTrue(singleAxis.act(on: xAxis).y.isApproximatelyEqual(
229+
to: .zero, absoluteTolerance: T.ulpOfOne.squareRoot()
230+
))
231+
XCTAssertEqual(singleAxis.act(on: xAxis).z, -1)
232+
224233
let piHalf = Quaternion<T>(angle: .pi/2, axis: xAxis)
225234
XCTAssertTrue(closeEnough(piHalf.act(on: vector).x, 1, ulps: 0))
226235
XCTAssertTrue(closeEnough(piHalf.act(on: vector).y, -1, ulps: 1))

0 commit comments

Comments
 (0)