Creating the Frame Hierarchy

Most meshes (even those not created with animation built in) have some type of hierarchy; an arm attached to the chest, a finger attached to the arm. In the Direct3D extensions library, you can load these hierarchies and maintain them yourself.

In order to store this information, there are two abstract classes that will be used to maintain the hierarchy: Frame and MeshContainer. Each Frame can contain zero to many sibling frames, as well as a potential child frame. Each frame can also contain zero to many mesh containers.

Before we get into all that, though, a new project will need to be created. Go ahead and get it created with the default options, the device variable declared, and the window style set, just like you've done with all of the other Direct3D applications.

Declare the following variables in your application:

 private AnimationRootFrame rootFrame; private Vector3 objectCenter; private float objectRadius; private float elapsedTime; 

The AnimationRootFrame structure will hold the root frame of the hierarchy tree, as well as the animation controller class. The next two variables will hold the center of the loaded mesh, with the other storing the radius of the bounding sphere that can encase the full mesh. These will be used to position the camera so that the entire model will be seen. The last variable will store the elapsed time of the application, which will be used to update the animation.

Since each animated mesh is different, Direct3D will not force a particular way to load the hierarchy on you. Instead, you will need to derive a new class from the AllocateHierarchy object. Add the following class to your application:

 public class AllocateHierarchyDerived : AllocateHierarchy {     Form1 app = null;     /// <summary>     /// Create new instance of this class     /// </summary>     /// <param name="parent">Parent of this class</param>     public AllocateHierarchyDerived(Form1 parent)     {         app = parent;     } } 

So far, the class doesn't do anything. The constructor stores an instance of the main form so that it can be used to call methods later, but right now the application won't even compile. There are two methods that must be implemented in this class before it can be compiled, since they are marked abstract. The two methods are used to create the frames and mesh containers that will be used to store the hierarchy. Since both of these classes are marked abstract as well, they cannot be created, so you will need to add new derived versions of the classes to your application. There will be extra information stored for each class to help our rendering later as well. Add the following class to your application:

 public class FrameDerived : Frame {     // Store the combined transformation matrix     private Matrix combined = Matrix.Identity;     public Matrix CombinedTransformationMatrix     {         get { return combined; } set { combined = value; }     } } 

In addition to the normal data a frame can store, each frame in this application will also store the combined transformation matrix of this frame and all of its parents. This will be useful when setting the world matrices later. Now you will need to include the derived mesh container class found in Listing 13.1.

Listing 13.1 The MeshContainer Class
 public class MeshContainerDerived : MeshContainer {     private Texture[] meshTextures = null;     private int numAttr = 0;     private int numInfl = 0;     private BoneCombination[] bones;     private FrameDerived[] frameMatrices;     private Matrix[] offsetMatrices;     // Public properties     public Texture[] GetTextures() { return meshTextures; }     public void SetTextures(Texture[] textures) { meshTextures = textures; }     public BoneCombination[] GetBones() { return bones; }     public void SetBones(BoneCombination[] b) { bones = b; }     public FrameDerived[] GetFrames() { return frameMatrices; }     public void SetFrames(FrameDerived[] frames) { frameMatrices = frames; }     public Matrix[] GetOffsetMatrices() { return offsetMatrices; }     public void SetOffsetMatrices(Matrix[] matrices){offsetMatrices = matrices; }     public int NumberAttributes {get{return numAttr;}set{numAttr = value;}}     public int NumberInfluences {get{return numInfl;}set{numInfl = value;}} } 

Each mesh container will hold a little more information, including the bone combination table, the number of attributes, the number of influences, and offset matrices. Each of these will be used while rendering our animated mesh.

With all of the derived classes implemented, the abstract methods currently missing can be added. First, add the CreateFrame overload to your allocate hierarchy derived class:

 public override Frame CreateFrame(string name) {     FrameDerived frame = new FrameDerived();     frame.Name = name;     frame.TransformationMatrix = Matrix.Identity;     frame.CombinedTransformationMatrix = Matrix.Identity;     return frame; } 

For each frame that needs creating, this code will store the name of the frame, as well set the transformation and combined transformation matrices to the identity matrix. Behind the scenes, the frames siblings, children, and mesh containers will be filled in by the runtime, so you need not worry about that.

There's a lot more work that needs to go on for creating a mesh container. Add the override found in Listing 13.2 to your application.

Listing 13.2 Creating a New MeshContainer
 public override MeshContainer CreateMeshContainer(string name,     MeshData meshData, ExtendedMaterial[] materials,     EffectInstance effectInstances, GraphicsStream adjacency,     SkinInformation skinInfo) {     // We only handle meshes here     if (meshData.Mesh == null)         throw new ArgumentException();     // We must have a vertex format mesh     if (meshData.Mesh.VertexFormat == VertexFormats.None)         throw new ArgumentException();     MeshContainerDerived mesh = new MeshContainerDerived();     mesh.Name = name;     int numFaces = meshData.Mesh.NumberFaces;     Device dev = meshData.Mesh.Device;     // Make sure there are normals     if ((meshData.Mesh.VertexFormat & VertexFormats.Normal) == 0)     {         // Clone the mesh         Mesh tempMesh = meshData.Mesh.Clone(meshData.Mesh.Options.Value,             meshData.Mesh.VertexFormat | VertexFormats.Normal, dev);         meshData.Mesh = tempMesh;         meshData.Mesh.ComputeNormals();     }     // Store the materials     mesh.SetMaterials(materials);     mesh.SetAdjacency(adjacency);     Texture[] meshTextures = new Texture[materials.Length];     // Create any textures     for (int i = 0; i < materials.Length; i++)     {         if (materials[i].TextureFilename != null)         {             meshTextures[i] = TextureLoader.FromFile(dev, @"..\..\" +                 materials[i].TextureFilename);         }     }     mesh.SetTextures(meshTextures);     mesh.MeshData = meshData;     // If there is skinning info, save any required data     if (skinInfo != null)     {         mesh.SkinInformation = skinInfo;         int numBones = skinInfo.NumberBones;         Matrix[] offsetMatrices = new Matrix[numBones];         for (int i = 0; i < numBones; i++)             offsetMatrices[i] = skinInfo.GetBoneOffsetMatrix(i);         mesh.SetOffsetMatrices(offsetMatrices);         app.GenerateSkinnedMesh(mesh);     }     return mesh; } 

This looks quite a bit more complicated than it really is. The first thing that needs to be done is to verify that this mesh is something the application will support. Obviously, if there is no mesh included in the mesh data structure, there will be a problem, so an exception is thrown. If the mesh that is included isn't using a valid vertex format, another exception is thrown.

Assuming that the mesh data itself is valid, a new mesh container is created. The name is set, and the number of faces the mesh contains is stored, along with the device. The code could also use the app variable to access the windows forms device, but that's marked as private.

With code that should look familiar, the mesh is checked for normal data. If it does not exist, it is added to the mesh. Our animated mesh will be lit and will require this normal data.

Next, you store the materials and adjacency information that was passed into this method into the mesh container for later use. Since the materials will include texture information, a new array of textures is created. The correct texture is then loaded into each member of the texture array, and that array is stored in the mesh container.

Finally, the code checks if there is any skeletal information in the mesh container. Assuming there is, the number of bones is stored, and an array of offset matrices (one for each bone) is created. We store the bone offset matrices in this array, and make a call to our (so far) non-existent GenerateSkinnedMesh method from our form. With everything complete, you can now return this mesh container.

Although in reality, everything isn't complete. The application still won't compile since the method that is called while creating the mesh container does not exist. Add the GenerateSkinnedMesh method from Listing 13.3 into your main class.

Listing 13.3 Generating Skinned Meshes
 public void GenerateSkinnedMesh(MeshContainerDerived mesh) {     if (mesh.SkinInformation == null)         throw new ArgumentException();     int numInfl = 0;     BoneCombination[] bones;     // Use ConvertToBlendedMesh to generate a drawable mesh     MeshData m = mesh.MeshData;     m.Mesh = mesh.SkinInformation.ConvertToBlendedMesh(m.Mesh, MeshFlags.Managed         | MeshFlags.OptimizeVertexCache, mesh.GetAdjacencyStream(), out numInfl,         out bones);     // Store this info     mesh.NumberInfluences = numInfl;     mesh.SetBones(bones);     // Get the number of attributes     mesh.NumberAttributes = bones.Length;     mesh.MeshData = m; } 

This method should never be called if there isn't any skeletal information, so if there isn't, an exception is thrown. We store the mesh data temporarily and then use ConvertToBlendedMesh to generate a new mesh with per-vertex blend weights and a bone combination table. This method is the root of what allows the mesh to be animated. Finally, we store the number of influences in this mesh, the bone combination table, and the number of attributes.



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