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.

  • \$\begingroup\$ So you want whats happening in the second image, but without having to let go of the mouse button? \$\endgroup\$ – Soapy Mar 13 '15 at 11:36
  • \$\begingroup\$ Yes that's correct. Most model-viewers behave like that I think \$\endgroup\$ – TheWanderer Mar 13 '15 at 11:39
  • \$\begingroup\$ So lastPointerPosition only changes when you press the mouse button again? \$\endgroup\$ – Soapy Mar 13 '15 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.

| improve this answer | |
  • \$\begingroup\$ Just testes this method and it still produces the same rotation (as in gif 1) for me. \$\endgroup\$ – Alchemist Nov 21 '16 at 2:19
  • \$\begingroup\$ @Sam check the order of each quaternion multiplication, since there is no commutativity \$\endgroup\$ – d3m4nz3 May 10 '18 at 21:55
  • \$\begingroup\$ Literally 2 years later, lol \$\endgroup\$ – Alchemist May 17 '18 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
| improve this answer | |
  • \$\begingroup\$ 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. \$\endgroup\$ – TheWanderer Mar 13 '15 at 12:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.