# Combining two independent rotations relative to the world frame

I am implementing a mouse rotation which works by accumulating the X-Y delta between frames to yaw and pitch rotation angles. The problem is that I wish rotations to be independent of each other. But as you can see from the picture:

I wish it behaved like this instead:

In the second picture, instead of dragging, I am releasing the mouse button and performing a second drag movement. So the local Y-axis is not rotated as in the first picture.

This is the code I am using:

Vector2 delta = current - lastPointerPosition;
totalYaw += delta.X;
totalPitch += delta.Y;
var r = Quaternion.RotationAxis(Vector3.UnitY, totalYaw / 100f);
var s = Quaternion.RotationAxis(Vector3.UnitX, totalPitch / 100f);
var qOrientation = s*r*qStart;


Please ignore for the moment the crude division by 100, I wanted to get the rotation right before optimising that part. That code causes the behaviour in the first picture.

I tried to rotate the UnitX axis used in the second quaternion by doing var q = r*new Quaternion(Vector3.UnitX, 0)*Quaternion.Invert(r); and then using q.Axis in place of UnitX in s.

I have looked all over the web but so far none of the solutions seemed to work for me. What am I doing wrong?

EDIT every time the mouse moves, I am updating lastPointerPosition with the current one (after updating the rotation matrix); when the mouse pointer is released, totalYaw/totalPitch are restored to 0.

• So you want whats happening in the second image, but without having to let go of the mouse button? Mar 13, 2015 at 11:36
• Yes that's correct. Most model-viewers behave like that I think Mar 13, 2015 at 11:39
• So lastPointerPosition only changes when you press the mouse button again? Mar 13, 2015 at 11:52

Instead of accumulating into totalYaw and totalPitch, accumulate into a quaternion:

Vector2 delta = current - lastPointerPosition;
var yaw = delta.X;
var pitch = delta.Y;
var r = Quaternion.RotationAxis(Vector3.UnitY, yaw / 100f);
var s = Quaternion.RotationAxis(Vector3.UnitX, pitch / 100f);
qTotalRotation = s*r*qTotalRotation;
var qOrientation = qTotalRotation*qStart;


(qTotalRotation is initialized to the identity quaternion.)

Originally, you computed the total mouse position delta (relabeled as totalYaw, totalPitch) and applied this to the original quaternion. Crucially, the delta quaternion (s*r) is applied to the same qStart quaternion, so it is always acting in the same space, with the same coordinate axes.

In this second formulation, each mouse movement results in a new quaternion representing just that small motion being created and accumulated. If the mouse has moved twice, then:

qOrientation = qTotalRotation * qStart
qOrientation = (s_2*r_2) * (s_1*r_1) * qStart


That is, each of the later s_i*r_i are applied after the previous rotations, so they act in a space with rotated axes, not the original space.

Another way to look at this which might be helpful is to note that in the orignal formulation, the path the cursor takes from start to finish does not matter: it is only the delta that changes the orientation. In the second one, however, due to the non-commutativity of quaternions, if the first motion is horizontal and the second is vertical, this will be different from if the first is vertical and the second horizontal. In particular, the second acts as if the mouse button is released and then pressed immediately on each motion.

• Just testes this method and it still produces the same rotation (as in gif 1) for me. Nov 21, 2016 at 2:19
• @Sam check the order of each quaternion multiplication, since there is no commutativity May 10, 2018 at 21:55
• Literally 2 years later, lol May 17, 2018 at 15:31

The problem lies in lastPointerPosition.

In your first image lastPointerPosition never changes there for both rotations are applied to the same qOrientation. The mouse gets released and applies to the cube.

In the second image lastPointerPosition changes. You do the first rotation, let go of the mouse button and apply the rotation. You click again and lastPointerPosition changes, you now apply the rotation on top of the existing one, instead of combining them.

The solution would be to do the rotations on a per axis basis i.e have a lastPointerPositionX and lastPointerPositionY. So basically you do this:

Check Pointer X
If changed do rotation
based on delta
apply rotation

Check Pointer Y
If changed do rotation
based on delta
apply rotation

• No actually I am updating lastPointerPosition every time the mouse moves, in another event. When the mouse pointer is released, totalYaw/pitch get restored to 0. Mar 13, 2015 at 12:01