Results 1 to 6 of 6

Thread: [WPF] 3D orbiting camera (pitch/yaw rotation only)

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    [WPF] 3D orbiting camera (pitch/yaw rotation only)

    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!

  2. #2
    Only Slightly Obsessive jemidiah's Avatar
    Join Date
    Apr 2002
    Posts
    2,431

    Re: [WPF] 3D orbiting camera (pitch/yaw rotation only)

    To answer your question briefly, use
    Code:
    TotalDx += dx; TotalDy += dy;
    
    double theta = TotalDx / 3;
    double phi = TotalDy / 3;
    Vector3D thetaAxis = new Vector3D(0, 1, 0);
    Vector3D phiAxis = new Vector3D(-1, 0, 0);
    
    Transform3DGroup group = mGeometry.Transform as Transform3DGroup;
    group.Children.Clear();
    QuaternionRotation3D r;
    r = new QuaternionRotation3D(new Quaternion(thetaAxis, theta));
    group.Children.Add(new RotateTransform3D(r));
    r = new QuaternionRotation3D(new Quaternion(phiAxis, phi));
    group.Children.Add(new RotateTransform3D(r));
    I'll reply with a longer answer shortly.
    The time you enjoy wasting is not wasted time.
    Bertrand Russell

    <- Remember to rate posts you find helpful.

  3. #3
    Only Slightly Obsessive jemidiah's Avatar
    Join Date
    Apr 2002
    Posts
    2,431

    Re: [WPF] 3D orbiting camera (pitch/yaw rotation only)

    That CodeProject article is incorrect in the key point "... if we apply two transforms in sequence, the latter overrides the first." That project in fact relies on the opposite, since its rotations are incremental. The article's method is wildly inefficient--it applies 1 3D transformation per MouseMove event. If the user moves around a fair amount, that project will apply hundreds or thousands of rotations per frame. I suppose there's a chance the composite transformation is cached by the Transform3DGroup class, but I wouldn't count on it.

    Another problem with that article is that it uses a bunch of ugly logic to calculate mouseAngle, when the atan2 function is designed to handle just that. I've never used 3D in WPF, so I can't offer my own "expert" opinion, but these issues aren't encouraging.


    Your Euler angle rotation routine doesn't work in part because you're adding a new full rotation to the overall transformation on each MouseMove event. After a few events fire, you're basically performing many full rotations, which cause them to be incredibly erratic. Your code results in a "sane" rotation if you replace "yaw += dx" with "yaw = dx", and similarly with dy. However, it doesn't result in the rotation you're after. For one, the W component of a quaternion should be the q0 component from the Wikipedia article. The remaining three components can be interpreted as a constant multiple of an (x, y, z) 3D vector, hence the notation of calling those components x, y, and z. I don't believe .NET's coordinate axes and the transformation you listed are precisely compatible. You should be able to use Euler angles in a manner similar to the one you used to do what you want, but I haven't investigated further because my method above is so much simpler. You would probably need to reorder the transformations or permute the axes to get it to work.

    The code I posted uses two "full" rotations instead of one incremental rotation per MouseMove event. In order, it first rotates about the +y axis an angle theta, proportional to total mouse movement in the screen's x-direction. This rotates the original -x axis about the +y axis. The next transformation rotates about the newly-rotated -x axis an angle phi, proportional to total mouse movement in the screen's y-direction. This almost precisely mimics spherical coordinates, using the math notation of theta for "longitude", phi for "latitude".


    To be clear, the code I posted is used in the Grid_MouseMove event of the CodeProject article's project. It should replace the code between the lines "double dx ..." and "mLastPos ...".
    The time you enjoy wasting is not wasted time.
    Bertrand Russell

    <- Remember to rate posts you find helpful.

  4. #4

  5. #5

  6. #6
    Only Slightly Obsessive jemidiah's Avatar
    Join Date
    Apr 2002
    Posts
    2,431

    Re: [WPF] 3D orbiting camera (pitch/yaw rotation only)

    Good point about clamping phi.

    I forgot to mention that a major reason my modification of your quaternion code (the one resulting in a "sane" rotation) doesn't give the desired result is because it uses a series of incremental roll/pitch/yaw rotations instead of a single roll/pitch/yaw rotation. Most likely the flash example you looked at stores global pitch and yaw angles and applies them in a single transformation, perhaps using the relevant matrix. If you Clear() the transformation collection and fix up your quaternion rotation code (getting the W, X, Y, Z order straight and making sure the right angles are getting applied in the right order about the right axes), this amounts to the same method. But again, my method is simpler when you already have access to an axis-angle rotation routine, so none of this matters much. (I'm just a stickler for completeness, which is why I mention it at all.)
    The time you enjoy wasting is not wasted time.
    Bertrand Russell

    <- Remember to rate posts you find helpful.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width