Before you finish this chapter, you implement the camera class. During the Blockers game, you handled all the camera movements (mainly the view transform) on your own. Although it is a completely viable option, it can be a little annoying. Having an abstracted camera class is more natural. Create a new code file (call it camera.cs) in your project, and use the code from Listing 13.11 for the initial implementation. Listing 13.11. Initial Camera Classusing System; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Tankers { public class Camera { // Attributes for view matrix private Vector3 eyeVector; private Vector3 lookAtVector; private Vector3 upVector; // Matrix for the view transform private Matrix viewMatrixTransform; // Attributes for projection matrix private float fieldView; private float aspectRatio; private float nearPlane; private float farPlane; // Matrix for the projection transform private Matrix projectionMatrixTransform; } } You probably recognize many of these variables from Blockers when you were setting up the camera for that game. These values map directly to the associated items in the projection and view matrices that you can create. You provide read-only properties for these as well, but there's no need for that information to appear in this book because you can see the code on the included CD for the implementation. Plus, defining read-only properties has been done numerous times, even in this chapter. You only have the properties as read-only so you cannot set them, which makes it pretty difficult to have the camera update any of these variables. Instead, you add two methods (Listing 13.12) to the class to set each of the required variables at the same time. Listing 13.12. Setting Camera Propertiespublic void SetViewParameters( Vector3 vEyePt, Vector3 vLookatPt, Vector3 vUpVec ) { // Set attributes for the view matrix eyeVector = vEyePt; lookAtVector = vLookatPt; upVector = vUpVec; viewMatrixTransform = Matrix.LookAtLH( eyeVector, lookAtVector, upVector ); UpdateViewFrustum(); } public void SetProjParameters( float fFOV, float fAspect, float fNearPlane, float fFarPlane ) { // Set attributes for the projection matrix fieldView = fFOV; aspectRatio = fAspect; nearPlane = fNearPlane; farPlane = fFarPlane; projectionMatrixTransform = Matrix.PerspectiveFovLH( fFOV, fAspect, fNearPlane, fFarPlane ); } For the most part, these methods don't do much more than store the variables passed in and then update the associated matrix, but in reality, they don't need to do anything more. When the view matrix is updated, a method you haven't defined yet is called to update the view frustum. I discussed the view frustum a few chapters ago, and if you remember, it's essentially the trapezoid area where the camera can see. The idea here is that you don't want to waste time by drawing things the camera obviously cannot see. You create an internal view frustum that in reality is a series of planes that define the boundaries of the frustum, and then you check whether an object is inside that frustum before rendering it. This practice is a quick and efficient way of rendering only objects the camera can see. The implementation of the method appears in Listing 13.13. Listing 13.13. Creating the Frustum Planespublic void UpdateViewFrustum() { // First get the inverse viewproj matrix Matrix mat = viewMatrixTransform * projectionMatrixTransform; mat.Invert(); // Get the 8 corners of the view frustum frustumPoints[0] = new Vector3(-1.0f, -1.0f, 0.0f); // xyz frustumPoints[1] = new Vector3( 1.0f, -1.0f, 0.0f); // Xyz frustumPoints[2] = new Vector3(-1.0f, 1.0f, 0.0f); // xYz frustumPoints[3] = new Vector3( 1.0f, 1.0f, 0.0f); // XYz frustumPoints[4] = new Vector3(-1.0f, -1.0f, 1.0f); // xyZ frustumPoints[5] = new Vector3( 1.0f, -1.0f, 1.0f); // XyZ frustumPoints[6] = new Vector3(-1.0f, 1.0f, 1.0f); // xYZ frustumPoints[7] = new Vector3( 1.0f, 1.0f, 1.0f); // XYZ for( int i = 0; i < frustumPoints.Length; i++ ) frustumPoints[i] = Vector3.TransformCoordinate(frustumPoints[i], mat); // Now calculate the planes frustumPlanes[0] = Plane.FromPoints(frustumPoints[0], frustumPoints[1], frustumPoints[2] ); // Near frustumPlanes[1] = Plane.FromPoints(frustumPoints[6], frustumPoints[7], frustumPoints[5] ); // Far frustumPlanes[2] = Plane.FromPoints(frustumPoints[2], frustumPoints[6], frustumPoints[4] ); // Left frustumPlanes[3] = Plane.FromPoints(frustumPoints[7], frustumPoints[3], frustumPoints[5]); // Right frustumPlanes[4] = Plane.FromPoints(frustumPoints[2], frustumPoints[3], frustumPoints[6] ); // Top frustumPlanes[5] = Plane.FromPoints(frustumPoints[1], frustumPoints[0], frustumPoints[4]); // Bottom } To calculate the trapezoid where the frustum will reside, you first get the view and projection matrices and invert them. You then manually set the frustum points as if they were a cube with a length of 1 on each side. You do this because right after, you transform each of the coordinates by the inverted view and projection matrix, which "transforms" them into the trapezoid shape that is the view frustum. You then create the six planes that make up each side of the frustum. You've probably noticed that you never defined the points' and planes' variables being used here, so you want to do that as well: // View frustum data private Vector3[] frustumPoints = new Vector3[8]; private Plane[] frustumPlanes = new Plane[6]; With your set of planes defined now, how do you actually know whether an object lies within the frustum and should be rendered? There's a simple mathematical formula you can use to see whether a point is outside of a plane. (See Listing 13.14.) Listing 13.14. Checking Whether an Object Is in Frustumpublic bool ObjectInFrustum(IMoveableObject obj) { // Check to see if this point is in the plane foreach(Plane p in frustumPlanes) { if (p.A * obj.Position.X + p.B * obj.Position.Y + p.C * obj.Position.Z + p.D <= (-obj.Radius)) { // The object is not in the view frustum, do not draw it return false; } } // By default, the object is in the frustum return true; } Here you see that by default, the object is assumed to be within the frustum (and thus drawn); however, if it can be guaranteed that it is not, it is skipped. Did you notice the type of object passed into this method? This interface isn't defined anywhere but will be used for objects you want to render, particularly those that can be culled. You'll find the definition of this interface in Listing 13.15, and starting next chapter, you'll be using it. The code on the included CD has this interface defined in the game engine source file, but you may put it wherever you find it convenient. Construction Cue
Listing 13.15. The IMoveableObjectpublic interface IMoveableObject { float Radius { get; } // The radius of the moveable object Vector3 Position { get; } // The current position of the moveable object Vector3 Direction { get; } // The direction the moveable object // is facing or moving void Update(float elapsedTime, Level currentLevel); // Update the moveable object void Draw(GameEngine engine, Camera c); // Render the moveable object } Unfortunately, you can't compile this code because you haven't defined the level class yet. That's coming up right about now. |