# Relative quaternion rotation (delta rotation) produces unstable results

Use case: I have a currentRotation quaternion and a targetRotation quaternion and need to calculate the relative rotation between them - as in: what rotation do I need to apply to transform an object with currentRotation so that it has targetRotation.

Common solutions say that the delta rotation can be calculated by qDelta = qFrom.inverse() * qTo, which I use as well and works fine for most cases.

However, I am often running into issues for some specific rotations - Codepen Example here (I'm using three.js and coffeescript).

While a relative rotation around the z-axis works fine:

qFrom = (new THREE.Quaternion()).setFromAxisAngle new THREE.Vector3(0, 0, 1), 2 * (Math.PI / 6)
qTo = (new THREE.Quaternion()).setFromAxisAngle new THREE.Vector3(0, 0, 1), 5 * (Math.PI / 6)
qExp = (new THREE.Quaternion()).setFromAxisAngle new THREE.Vector3(0, 0, 1), 3 * (Math.PI / 6)
qDelta = calculateRelativeRotation(qFrom, qTo)
qResult = qDelta.clone().multiply(qFrom)


Sane case results (delta): expected x: 0.0°, y: 0.0°, z: 90.0°, got x: 0.0°, y: 0.0°, z: 90.0°

Sane case results (result): expected x: 0.0°, y: 0.0°, z: 150.0°, got x: 0.0°, y: 0.0°, z: 150.0°

... a more complicated rotation does not work out - the combination of qFrom and qDelta does not result in the expected qTo rotation:

eFrom = new THREE.Euler(0, 170 * dToR, 10 * dToR, 'XYZ')
eTo = new THREE.Euler(0, 170 * dToR, -10 * dToR, 'XYZ')
eExp = new THREE.Euler(0, 0, -20 * dToR, 'XYZ')


Problematic case results (delta): expected x: 0.0°, y: 0.0°, z: -20.0°, got x: 0.0°, y: 0.0°, z: -20.0°

Problematic case results (result): expected x: -180.0°, y: 10.0°, z: 170.0°, got x: 176.5°, y: 9.4°, z: -149.7°

expected result quaternion (qTo): {'_x':-0.086,'_y':0.992,'_z':-0.007,'_w':0.086} *

actual result quaternion: {'_x':0.257,'_y':0.962,'_z':-0.007,'_w':0.086}

Inverting the calculated delta rotations helps for some cases, but doesn't cover all cases.

What am I missing?

• TLDR: Quaternion math is weird. I don't know what the problem is (i.e. how to fix it, whatever "it" actually is) because quaternions aren't something I understand. I can tell you that quaternion multiplication is noncommutative (i.e. order matters) but that's about it. – Draco18s no longer trusts SE Jul 5 '17 at 17:38
• True that. The multiplication seems to be correct, as this issue only occurs for some special cases, and generally works fine. – AtiX Jul 6 '17 at 9:10
• I figured you hadn't messed up that badly or nothing would work. But I still don't know why it won't always work. – Draco18s no longer trusts SE Jul 6 '17 at 13:06

Thanks to Neal on Mathematics Stackexchange I found out that my code to calculate relative rotations was indeed in the wrong order (I took it from Unity Anwers which seems not to be the correct way to do it within three.js) - with the tricky part being that it worked out most times. So, the correct solution to calculate a relative rotation is

qDelta = qTo * qFrom.inverse()

• As long as you deal with unit length quaternions (which you have to for 3D orientations/rotations), the conjugate is the same as the inverse. The conjugate is faster, its basically just a copy with changing the sign for the axis-part. – Maik Semder Jul 12 '17 at 9:14
• If you're curious why the order flips, it's a matter of whether you apply the relative rotation in local or global space. If your question is "what relative rotation qRel do I need to apply to a child, so after its parent's rotation qRef is applied I get my desired result rotation qNet?" Then qNet = qRef * qRel = qRef * (qRef^-1 * qNet) ∴ qRel = qRef^-1 * qNet is correct. But if your question is "what relative rotation qRel do I need to apply on top of a starting rotation qRef" then qNet = qRel * qRef = (qNet * qRef^-1) ∴ qRel = qNet * qRef^-1. Both valid, just different cases. – DMGregory Jul 12 '17 at 10:01
• Oops, I left off a qRef in the second derivation above. The world space compounding case should be qNet = qRel * qRef = (qNet * qRef^-1) * qRef ∴ qRel = qNet * qRef^-1 – DMGregory Jul 12 '17 at 11:06