|
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.

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.

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
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.
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.
I try to execute your code sample. But at this line :
(perspCamera.Transform as MatrixTransform3D).Matrix *= cameraTilt.Value;
Hi.
Great article but what this terrible yellow code !!
Are this a secret code !!
That cover with a lemon juice ?
Sorry about the formatting.. I didn't/don't have control over the display
template.
To you experiencing problems because of the MatrixTransform3D object being
in a read-only state:
<PerspectiveCamera.Transform>
<MatrixTransform3D>
</MatrixTransform3D>
</PerspectiveCamera.Transform>