The World’s Leading Microsoft .NET Magazine
   
 
The .NET Addict's Blog

My Top Tags

                                                           

My RSS Feeds








I heart FeedBurner

Latest Diggs - Programming

Computers Blogs - Blog Top Sites

Site Hits

Total: 4,882,895
since: 19 Jan 2005

Camera Rotation and Movement in Windows Presentation Foundation

posted Sun 05 Mar 06

Rotating the camera in a 3D scene in WPF is equivalent to "looking" up, down, left, or right. If you're in a first-person shooter or several of the Massively Multiplayer online games, one way you can look around is by holding down the right mouse button and panning around. Its a quick and easy method to looking around the virtual environment.

One other method is using keyboard input to look around. For example, if you hit the left arrow, your character/avatar/camera should turn to the left. If you hit the right arrow, your character/avatar/camera should turn to the right. You can't just do simple addition on one axis (e.g. add or subtract one to the X value of the LookDirection vector on the camera).

The camera in WPF (and in Direct3D, for those of you keeping score...) is an invisible 3D object. In front of the camera is what is called the near plane, and beyond that is the far plane. The far plane is wider and taller than the near plane. This creates a box with one large end and one small end. 3D scenes are then rendered by having the 3D objects contained within this box (field of view, or frustum) be converted into 2D raw image data by applying foreshortening rules, occlusion (hiding one object behind another), and other rules such as excluding objects beyond the far plane. There's a lot more that goes into rendering a scene such as applying brushes, figuring out which piece of a model is facing the camera and which piece is facing away from the camera by using what are called 'Normals' (I'll probably cover that in another blog entry).

Anyway, to 'spin' a camera in place, you can apply a transform to the camera. One type of transformation that is common is to rotate a 3D model (a 3D model in this case is a collection of Geometry, which includes Position points, Vertex locations, Triangle indices, and Texture co-ordinates) about an Axis. You have to be really careful when rotating a model about an axis, because every rotation needs a center point. This actually caused me no end of headaches because the code I had written for previous versions of Avalon (WPF) didn't require me to explicitly set the center point of the rotation, the code was inferring it through some magical process to which I wasn't privy.

Let's use an example. Let's say you're building an immersive video game, and the avatar is standing still at co-ordinates 3,3,0 (X:3, Y (up):3, Z (into/out of the camera):0). If you rotate this avatar about the Y axis (the Up axis in this case), this should allow him to spin in place. The problem is that if the center of the rotation is the origin (the default), he will actually move in a circle around the origin instead of spinning in place. You must set the centerpoint of the rotation or you may end up with really unpredictable results.

Now to get down to business. I've included the code for the Keydown event handler for my WPF window. This window has an ImageBrush'd background and a bunch of programmatically generated cubes in it.  The cubes get progessively lower on the Y axis and progressively further away on the Z axis. This will give you a good look at how the view changes when you rotate the camera.

 


 

private void Window_Keydown(object sender, System.Windows.Input.KeyEventArgs e) {

  if (e.Key == System.Windows.Input.Key.Left)

 {

    RotateTransform3D cameraSpin = new RotateTransform3D(

       new AxisAngleRotation3D(new Vector3D(0, 1, 0), 10));

    cameraSpin.CenterX = perspCamera.Position.X;

    cameraSpin.CenterY = perspCamera.Position.Y;

   cameraSpin.CenterZ = perspCamera.Position.Z;

   (perspCamera.Transform as MatrixTransform3D).Matrix *= cameraSpin.Value;

 }

 if (e.Key == System.Windows.Input.Key.Right)

 {

    RotateTransform3D cameraSpin = new RotateTransform3D(

      new AxisAngleRotation3D(new Vector3D(0, 1, 0), -10));

    cameraSpin.CenterX = perspCamera.Position.X;

    cameraSpin.CenterY = perspCamera.Position.Y;

    cameraSpin.CenterZ = perspCamera.Position.Z;

    (perspCamera.Transform as MatrixTransform3D).Matrix *= cameraSpin.Value;

 }

 if (e.Key == System.Windows.Input.Key.PageDown)

 {

     RotateTransform3D cameraTilt = new RotateTransform3D(

       new AxisAngleRotation3D(new Vector3D(1, 0, 0), -10));

    cameraTilt.CenterZ = perspCamera.Position.Z;

    cameraTilt.CenterX = perspCamera.Position.X;

    cameraTilt.CenterY = perspCamera.Position.Y;

    (perspCamera.Transform as MatrixTransform3D).Matrix *= cameraTilt.Value;

 }

 if (e.Key == System.Windows.Input.Key.PageUp)

 {

    RotateTransform3D cameraTilt = new RotateTransform3D(

      new AxisAngleRotation3D(new Vector3D(1, 0, 0), 10));

      cameraTilt.CenterZ = perspCamera.Position.Z;

    cameraTilt.CenterX = perspCamera.Position.X;

    cameraTilt.CenterY = perspCamera.Position.Y;

    (perspCamera.Transform as MatrixTransform3D).Matrix *= cameraTilt.Value;

  }

}

 


The key to this transformation is the use of matrix multiplication. Matrix multiplication, before I learned how to apply it to video games and 3D graphics, was one of those cruel inventions created by my physics professors to have my cold, sweaty hand reaching for my graphing calculator in a tear-stricken panic. Now that I know how useful , nay, completely unable to live without matrix multiplication is - I'm a big fan. For example, I create an instance of the Axis angle rotation (3D) class and set the centerpoint of the rotation to the current position of the camera. (Whenever the centerpoint of rotation is the position of the object being rotated, the axis will go through the object and create a spin.. you can use this same thing to make 'planets' by creating a 45-degree-from-Y axis rotation that goes through the middle of a sphere mesh)

 

 

 

 You then apply the matrix contained in the rotation object's Value property to the camera by doing some matrix multiplication.

The first screenshot contained below shows you my default 3D scene. The camera is facing straight into the scene (facing in the -Z direction, or a LookDirection of 0,0,-1. Remember that Unit Vectors are used to specify a direction.

Unrotated 3D Scene

This next screenshot shows the scene after I've used the arrow keys and the PageDown key a few times to look down and to the left. The camera itself has maintained its original position (which is good, because there are problems with using Transforms to change the camera's position that arose for me in previous CTPs such as bogus hit test results) and the matrix multiplication has transformed the LookDirection of the camera.

3D scene with a rotation-transformed camera view

If you're like me, you're thinking "that's great, but how do I apply this to a video game?". Well, if you can turn left and right, the only other thing you need to be able to do in order to 'move' through a scene is to move forward and backward. Moving forward and backward is a lot easier than you might think. Thinking back to the physics class about Vectors, you might remember that if you add a unit vector to a position, the object will 'move' in the direction of the unit vector.

So, to move forward:

perspCamera.Position += perspCamera.LookDirection;

And to move backward:

perspCamera.Position -= perspCamera.LookDirection;

The miracle that is WPF takes care of all the hard work for you. For example, LookDirection might have a transform applied to it, but when you do the addition as shown above, the transform is taken into account and the camera will move in the direction it is currently looking, not in the direction it was configured at the beginning of the scene.

I hope you found this handy, because I'm going to start spamming you all with more WPF samples. The next sample I want to show you involves 3D hit-testing and data visualization, so stay tuned!

- The .NET Addict

links: digg this    del.icio.us    technorati    reddit

AddThis Social Bookmark Button




1. Giorgio left...
Mon 10 Apr 06 1:59 pm :: http://www.sintesi80.it/

Hi Kevin, I'm Giorgio Sardo, MS Student Ambassador from Italy. I think you're doing a great job with wpf: I've just started to code with it since a few days. I think your samples are really helpfull and userfull. Could you please send me the source code of your project? I really liked the one where you use camera rotation and picking.

Thank you for your support, keep blogging on this stuff please.

Giorgio


2. Ryan left...
Fri 02 Jun 06 11:46 am

I was just wondering if you have tried this code with the latest WinFX release. The rotation works but the translation is not behaving as expected.

perspCamera.Position += perspCamera.LookDirection;

I would expect the camera to move directly towards, or away from the center of the screen. This is producing a 'sliding' effect if the camera has been rotated.

Incidentally, I tried using MatrixCamera and doing all of the view matrix calculations myself and I ended up with the same effect.


3. MrHoSo left...
Fri 02 Feb 07 6:12 pm

I try to execute your code sample. But at this line : (perspCamera.Transform as MatrixTransform3D).Matrix *= cameraTilt.Value;

returns this error:

Cannot set a property on object 'System.Windows.Media.Media3D.MatrixTransform3D' because it is in a read-only state.

Does anybody have an idea ?

Regards


4. Guy left...
Tue 23 Oct 07 1:24 pm

Hi. Great article but what this terrible yellow code !! Are this a secret code !! That cover with a lemon juice ?

Please make it visibile.


5. Kevin Hoffman left...
Tue 23 Oct 07 1:27 pm

Sorry about the formatting.. I didn't/don't have control over the display template.


6. Jeff left...
Wed 20 Aug 08 2:59 pm

Error message:

Additional information: Cannot set a property on object 'System.Windows.Media.Media3D.MatrixTransform3D' because it is in a read-only state.


7. Jesper Borgstrup left...
Wed 15 Oct 08 2:43 pm

To you experiencing problems because of the MatrixTransform3D object being in a read-only state:

It seems like your PerspectiveCamera doesn't have a MatrixTransform3D. Just create an empty one - i.e. in XAML:

<PerspectiveCamera ...>

  • <PerspectiveCamera.Transform>

    • <MatrixTransform3D>

    • </MatrixTransform3D>

  • </PerspectiveCamera.Transform>

</PerspectiveCamera>


Tag Related Posts