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 Graphicspublic 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 Resetprivate 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 Matricesprivate 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.
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 Pointstatic 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); } } |