Loading Meshes with Animation

The basic hierarchy of the mesh has been established. Now, all you need to do is write some code to actually use it. Before you do that, though, you will need to initialize your graphics device. You should use a method similar to the programmable pipeline examples earlier, so use the method found in Listing 13.4.

Listing 13.4 Initializing Your Graphics
 public bool InitializeGraphics() {     // Set our presentation parameters     PresentParameters presentParams = new PresentParameters();     presentParams.Windowed = true;     presentParams.SwapEffect = SwapEffect.Discard;     presentParams.AutoDepthStencilFormat = DepthFormat.D16;     presentParams.EnableAutoDepthStencil = true;     bool canDoHardwareSkinning = true;     // Does a hardware device support shaders?     Caps hardware = Manager.GetDeviceCaps(0, DeviceType.Hardware);     // We will need at least four blend matrices     if (hardware.MaxVertexBlendMatrices >= 4)     {         // Default to software processing         CreateFlags flags = CreateFlags.SoftwareVertexProcessing;         // Use hardware if it's available         if (hardware.DeviceCaps.SupportsHardwareTransformAndLight)             flags = CreateFlags.HardwareVertexProcessing;         // Use pure if it's available         if (hardware.DeviceCaps.SupportsPureDevice)             flags |= CreateFlags.PureDevice;         // Yes, Create our device         device = new Device(0, DeviceType.Hardware, this, flags, presentParams);     }     else     {         // No shader support         canDoHardwareSkinning = false;         // Create a reference device         device = new Device(0, DeviceType.Reference, this,             CreateFlags.SoftwareVertexProcessing, presentParams);     }     // Create the animation     CreateAnimation(@"..\..\tiny.x", presentParams);     // Hook the device reset event     device.DeviceReset += new EventHandler(OnDeviceReset);     OnDeviceReset(device, null);     return canDoHardwareSkinning; } 

Once again, you want to try to run in hardware first, and then fallback to the reference device if you must. The animation file used for this example will require at least four vertex blend matrices in order to render completely in hardware, so that's what the code will check for. Assuming it can, the best possible device will be created; otherwise, the reference device is created.

Once the device has been created, our animation should be loaded. The method that is being used hasn't been defined yet, but you will add that in just a moment. Before you do that, you should add the device reset event handler, as this method will have no other dependencies. Add the event handler from Listing 13.5 into your class.

Listing 13.5 Handling Device Reset
 private void OnDeviceReset(object sender, EventArgs e) {     Device dev = (Device)sender;     // Set the view matrix     Vector3 vEye = new Vector3( 0, 0, -1.8f * objectRadius );     Vector3 vUp = new Vector3( 0, 1, 0 );     dev.Transform.View = Matrix.LookAtLH(vEye, objectCenter, vUp);     // Setup the projection matrix     float aspectRatio = (float)dev.PresentationParameters.BackBufferWidth         / (float)dev.PresentationParameters.BackBufferHeight;     dev.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI / 4,         aspectRatio, objectRadius/64.0f, objectRadius*200.0f );     // Initialize our light     dev.Lights[0].Type = LightType.Directional;     dev.Lights[0].Direction = new Vector3(0.0f, 0.0f, 1.0f);     dev.Lights[0].Diffuse = Color.White;     dev.Lights[0].Commit();     dev.Lights[0].Enabled = true; } 

As you can see, the view matrix is set, placing the camera far enough away to see the entire object, and is looking at the center of the object. The projection matrix is created with a large enough far plane to see the entire model as well. There is also a single directional light created in order to see the model correctly.

Now you are ready to add the animation creation code:

 private void CreateAnimation(string file, PresentParameters presentParams) {     // Create our allocate hierarchy derived class     AllocateHierarchyDerived alloc = new AllocateHierarchyDerived(this);     // Load our file     rootFrame = Mesh.LoadHierarchyFromFile(file, MeshFlags.Managed,         device, alloc, null);     // Calculate the center and radius of a bounding sphere     objectRadius = Frame.CalculateBoundingSphere(rootFrame.FrameHierarchy,         out objectCenter);     // Setup the matrices for animation     SetupBoneMatrices((FrameDerived)rootFrame.FrameHierarchy);     // Start the timer     DXUtil.Timer(DirectXTimer.Start); } 

This method seems pretty simple, but in reality all it does is call other methods, so naturally it should seem pretty simple.

First, the derived allocate hierarchy class you created is instantiated. This is passed on to the LoadHierarchyFromFile method, along with the file name of the mesh you wish to load, any options, and the Direct3D device. When this method is called, you will notice that your CreateFrame and CreateMeshContainer overrides will be called numerous times, depending on the number of frames and mesh containers in your mesh. The AnimationRootFrame object that is returned will hold the root frame of the hierarchy tree, as well as the animation controller, which will be used to, well, control the animation for this mesh.

After the hierarchy has been created, you can calculate the bounding sphere of the entire frame by passing the root frame into this CalculateBoundingSphere method. This will return the radius of this sphere, as well as the center of the mesh, which has already been used to position the camera.

Finally, before the mesh is ready to be animated, you must set up the bone matrices. This will be the first method that will "walk" the frame hierarchy tree and will be the foundation for each of the others. Add the methods found in Listing 13.6.

Listing 13.6 Setting Up Bone Matrices
 private void SetupBoneMatrices(FrameDerived frame) {     if (frame.MeshContainer != null)     {         SetupBoneMatrices((MeshContainerDerived)frame.MeshContainer);     }     if (frame.FrameSibling != null)     {         SetupBoneMatrices((FrameDerived)frame.FrameSibling);     }     if (frame.FrameFirstChild != null)     {         SetupBoneMatrices((FrameDerived)frame.FrameFirstChild);     } } private void SetupBoneMatrices(MeshContainerDerived mesh) {     // Is there skin information?  If so, setup the matrices     if (mesh.SkinInformation != null)     {         int numBones = mesh.SkinInformation.NumberBones;         FrameDerived[] frameMatrices = new FrameDerived[numBones];         for(int i = 0; i< numBones; i++)         {             FrameDerived frame = (FrameDerived)Frame.Find(                 rootFrame.FrameHierarchy,                 mesh.SkinInformation.GetBoneName(i));             if (frame == null)                 throw new ArgumentException();             frameMatrices[i] = frame;         }         mesh.SetFrames(frameMatrices);     } } 

As you can see, walking the frame hierarchy isn't all that difficult. If you have a sibling, you simply call the method you are currently in once more with yourself. If you have children, you do the same. You don't need to call yourself with all of your children, just the first, since the first child will have each subsequent child listed as its sibling. In this case, we want to store each frame based on its bone name.

The overload that accepts the frame isn't overly interesting, since its only job is to walk the hierarchy tree and pass the mesh container along to the second overload. Here, an array of our frames is created, one for each bone in our mesh, assuming there is skeletal information in the first place. Each frame is retrieved by searching through the entire list looking for the one with the correct bone name. Once that is found, it is stored for later use.

USING THE DIRECTX TIMER

The animation system will require a relatively high precision timer. Rather than reinvent the wheel, this code will assume you are using the DirectX timer that ships in the SDK. You can add this file to your project by clicking Add Existing Item, navigating to the common folder, and choosing the dxutil.cs source file.

Now would be the perfect time to update the main method to ensure that your initialization code will be called. Use the main method found in Listing 13.7.

Listing 13.7 Main Entry Point
 static void Main() {     using (Form1 frm = new Form1())     {         // Show our form and initialize our graphics engine         frm.Show();         if (!frm.InitializeGraphics())         {             MessageBox.Show("Your card can not perform skeletal animation on " +                 "this file in hardware. This application will run in " +                 "reference mode instead.");         }         Application.Run(frm);     } } 


Managed DirectX 9 Graphics and Game Programming, Kick Start
Managed DirectX 9 Kick Start: Graphics and Game Programming
ISBN: B003D7JUW6
EAN: N/A
Year: 2002
Pages: 180
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net