Hi,

I have a cube in a ViewPort3D in a WPF application (C#), and now I am trying to enable the user to 'orbit' the camera around this cube. The camera should always point toward the center of the cube, but it can fly around the cube when the users drags the mouse.

For an example of the type of rotation I need, see this:
http://pv3d.org/2008/11/19/dragging-...-camera-orbit/


I've searched on google on how to perform camera rotations in WPF, and I found this article;
http://www.codeproject.com/KB/WPF/Wpf3DPrimer.aspx
While this seems to rotate the object, rather than the camera, I made it rotate the camera instead by simply applying the transformations to the camera and negating the axis vector.

The important bit is in the MouseMove event:
csharp Code:
  1. private void Grid_MouseMove(object sender, MouseEventArgs e) {
  2.     if(!mDown) return;
  3.     Point pos = Mouse.GetPosition(viewport);
  4.     Point actualPos = new Point(
  5.             pos.X - viewport.ActualWidth / 2,
  6.             viewport.ActualHeight / 2 - pos.Y);
  7.     double dx = actualPos.X - mLastPos.X;
  8.     double dy = actualPos.Y - mLastPos.Y;
  9.     double mouseAngle = 0;
  10.  
  11.     if(dx != 0 && dy != 0) {
  12.         mouseAngle = Math.Asin(Math.Abs(dy) /
  13.             Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)));
  14.         if(dx < 0 && dy > 0) mouseAngle += Math.PI / 2;
  15.         else if(dx < 0 && dy < 0) mouseAngle += Math.PI;
  16.         else if(dx > 0 && dy < 0) mouseAngle += Math.PI * 1.5;
  17.     }
  18.     else if(dx == 0 && dy != 0) {
  19.             mouseAngle = Math.Sign(dy) > 0 ? Math.PI / 2 : Math.PI * 1.5;
  20.     }
  21.     else if(dx != 0 && dy == 0) {
  22.             mouseAngle = Math.Sign(dx) > 0 ? 0 : Math.PI;
  23.     }
  24.  
  25.     double axisAngle = mouseAngle + Math.PI / 2;
  26.  
  27.     Vector3D axis = new Vector3D(
  28.             Math.Cos(axisAngle) * 4,
  29.             Math.Sin(axisAngle) * 4, 0);
  30.  
  31.     double rotation = 0.02 *
  32.             Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));
  33.  
  34.     Transform3DGroup group = mGeometry.Transform as Transform3DGroup;
  35.        QuaternionRotation3D r =
  36.             new QuaternionRotation3D(
  37.             new Quaternion(axis, rotation * 180 / Math.PI));
  38.     group.Children.Add(new RotateTransform3D(r));
  39.  
  40.     mLastPos = actualPos;
  41. }

dx and dy are simply the amount by which the mouse has moved between now and the previous MouseMove event, and from this an angle is calculated. Then a rotation axis is calculated which is finally used to create a quaternion, and WPF takes care of the rest (just pass in the quaternion and the rotation is done).

Anyway, this example gives the camera too much freedom, as you can also roll it around the axis pointing from camera to object, which should not be possible in my case.



So, my question is quite simple really... How do I stop it rolling?


So far I tried to copy the code from the flash example, where he simply stores the pitch and yaw values and adds dy and dx to them. Then he uses them in an 'orbit' method that would supposedly do the rotation which I'm trying to do...

Well, I thought, if I have the pitch and yaw (and the roll would be fixed at 0 I guess?) then I should be able to create a quaternion from those and let WPF handle the rest. So that's what I tried, using this formula from Wikipedia:

csharp Code:
  1. yaw += dx;
  2.             pitch += dy;
  3.  
  4.             yaw %= 360;
  5.             pitch %= 360;
  6.  
  7.             pitch = pitch > 0 ? pitch : 0.000001;
  8.             pitch = pitch < 90 ? pitch : 89.999999;
  9.  
  10.             var phi = roll / 2D;   // roll = 0
  11.             var theta = pitch / 2D;
  12.             var psi = yaw / 2D;
  13.  
  14.             var q = new Quaternion
  15.                                {
  16.                                    X = cos(phi) * cos(theta) * cos(psi) + sin(phi) * sin(theta) * sin(psi),
  17.                                    Y = sin(phi) * cos(theta) * cos(psi) - cos(phi) * sin(theta) * sin(psi),
  18.                                    Z = cos(phi) * sin(theta) * cos(psi) + sin(phi) * cos(theta) * sin(psi),
  19.                                    W = cos(phi) * cos(theta) * sin(psi) - sin(phi) * sin(theta) * cos(psi)
  20.                                };
  21.  
  22.             Transform3DGroup group = camera.Transform as Transform3DGroup;
  23.             QuaternionRotation3D r = new QuaternionRotation3D(q);
  24.             group.Children.Add(new RotateTransform3D(r));
cos and sin are simply functions that take a number of degrees as argument, convert them to radians and then simply use Math.Cos and Math.Sin, nothing fancy. Also, I know the formula uses phi/2, theta/2, etc, but to save some writing (and a tiny bit of computing time I guess) I divide by 2 before I use them.

First note that I'm not sure whether the order (X,Y,Z,W) is correct, maybe it should be (W,X,Y,Z), but the result is the same in both cases: chaos.

It seems whenever I move my mouse by just 1 pixel, the result is that the camera rotates about 38 billion times. Well, give or take a few times. It just seems to warp from one position to another. I tried dividing dx and dy by some factor to see if that would slow it down a bit, but that doesn't help.


I'm a bit at a loss here... Can I use the first attempt (from the CodeProject article) and modify that somehow to stop it from rolling, or is my own attempt the right way and is there simply a small error or something? I cannot figure it out....

Thanks for any help!