Skip to content

Commit

Permalink
Reduce complexity of smallestAngularDifference function
Browse files Browse the repository at this point in the history
  • Loading branch information
ericyd committed Feb 6, 2024
1 parent b9968ae commit 686f2cb
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 31 deletions.
39 changes: 11 additions & 28 deletions lib/math.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,43 +39,26 @@ export function isWithinError(target, error, value) {
}

/**
* For angles in a known range, e.g. [-PI, PI],
* returns the angle (and direction) of the smallest angular difference between them.
* The result will always assume traveling from `angle1` to `angle2`.
* TODO@v1: this function is wild... write more tests and figure out how to simplify
* Returns the angle (directionally-aware) of the smallest angular difference between them.
* The result will always assume traveling from `angle1` to `angle2`;
* that is, if angle1 is anti-clockwise of angle2, the result will be positive (traveling clockwise to reach angle2),
* and if angle1 is clockwise of angle2, the result will be negative (traveling anti-clockwise to reach angle2).
* @param {Radians} angle1
* @param {Radians} angle2
* @returns {Radians}
*/
export function smallestAngularDifference(angle1, angle2) {
// put both angles in -PI,PI range
while (angle1 < -Math.PI) { angle1 += Math.PI * 2 }
while (angle2 < -Math.PI) { angle2 += Math.PI * 2 }
while (angle1 > Math.PI) { angle1 -= Math.PI * 2 }
while (angle2 > Math.PI) { angle2 -= Math.PI * 2 }
let diff = angle2 - angle1

let adjustedAngle1 = angle1
while (adjustedAngle1 < angle2) {
adjustedAngle1 += Math.PI * 2
// the smallest angular rotation should always be in range [-π, π]
while (diff > Math.PI) {
diff -= Math.PI * 2
}

let adjustedAngle2 = angle2
while (adjustedAngle2 < angle1) {
adjustedAngle2 += Math.PI * 2
while (diff < -Math.PI) {
diff += Math.PI * 2
}

const min = Math.min(adjustedAngle2 - adjustedAngle1, adjustedAngle1 - adjustedAngle2)
let directionalMin = adjustedAngle1 > adjustedAngle2 ? min : min * -1

// the absolute value of the smallest angular rotation should always be < Math.PI
while (directionalMin > Math.PI) {
directionalMin -= Math.PI * 2
}
while (directionalMin < -Math.PI) {
directionalMin += Math.PI * 2
}
// account for situations where the two values are the same "effective" angle
return isWithin(Math.PI - 0.001, Math.PI + 0.001, directionalMin) ? 0 : directionalMin
return diff
}

/**
Expand Down
7 changes: 4 additions & 3 deletions lib/math.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@ describe('smallestAngularDifference', () => {
{ angle1: 0, angle2: 0, expected: 0 },
// "9-o-clock" to "12-o-clock" = quarter turn clockwise
{ angle1: -Math.PI / 2, angle2: Math.PI, expected: -Math.PI / 2},
// outside of [-π, π] gets corrected
{ angle1: -Math.PI * 9 / 2, angle2: Math.PI * 5, expected: -Math.PI / 2},
// "12-o-clock" to "9-o-clock" = quarter turn anti-clockwise
// argument order matters!!!
{ angle1: Math.PI, angle2: -Math.PI / 2, expected: Math.PI / 2},
// outside of [-π, π] gets corrected
{ angle1: Math.PI * 3, angle2: -Math.PI / 2, expected: Math.PI / 2},
{ angle1: Math.PI * 5, angle2: -Math.PI * 9 / 2, expected: Math.PI / 2},
// same angle, different sign = 0
{ angle1: -Math.PI, angle2: Math.PI, expected: 0},
{ angle1: -Math.PI * 9, angle2: Math.PI * 9, expected: 0},
Expand All @@ -40,9 +37,13 @@ describe('smallestAngularDifference', () => {
const approximateTests = [
{ angle1: -Math.PI * 0.99, angle2: Math.PI * 0.99, expected: -Math.PI * 0.02 },
{ angle1: Math.PI*2 - 0.05, angle2: 0.05, expected: 0.1},
{ angle1: -0.05, angle2: 0.05, expected: 0.1},
{ angle1: Math.PI/2 - 0.05, angle2: Math.PI/2 + 0.05, expected: 0.1},
{ angle1: Math.PI - 0.05, angle2: -Math.PI + 0.05, expected: 0.1},
{ angle1: 100.5, angle2: 102.75, expected: 2.25},
// outside of [-π, π] gets corrected
{ angle1: Math.PI * 5, angle2: -Math.PI * 9 / 2, expected: Math.PI / 2},
{ angle1: -Math.PI * 9 / 2, angle2: Math.PI * 5, expected: -Math.PI / 2},
]

for (const { angle1, angle2, expected } of approximateTests) {
Expand Down

0 comments on commit 686f2cb

Please sign in to comment.