![]() |
![]() |
MultiAnimation Sample
Path
User's Guide
Sample Overview MultiAnimation shows how an application can render 3D animation utilizing D3DX's animation support. D3DX has APIs that handles the loading of the animatable mesh, as well as the blending of multiple animations. The animation controller supports animation tracks for this purpose, and allows transitioning from one animation to another smoothly. The sample is divided into two parts: the animation class library and the application. The animation class library is a general-purpose library that sits between the application and D3DX. It encapsulates the details of loading the mesh from an .X file and manipulating its frame hierarchy to prepare it for rendering, as well as rendering the animated mesh using a vertex shader and a matrix palette for indexed skinning. It is designed to be reusable and customizable.
The application portion contains code specific only to this sample. It
uses the animation class library to create instances of Tiny, which are animated
according to the action they perform. Each instance can be controlled by either
the user or the sample. This portion also handles collision detection among
instances of Tiny, out of bound detection, and behavior management for instances
that are controlled by the sample.
In this sample, Tiny can be controlled by either the user or the application (default). When Tiny is controlled by the application, the following happens: we first find a random location on the floor and determine a move speed (run or walk). Then, we turn and move Tiny from the current position to the new destination. After Tiny arrives to the new destination, there is a brief period of idle time. After that has passed, we pick another location for Tiny to move to, and the whole process repeats. There can be more than one instance of Tiny. Collision detection is done for the instances, so they can block each other's movement. The CTiny class has a CAnimInstance member, a class defined by the animation library. CTiny uses this member and its animation controller to perform the animation tasks necessary. In this sample, Tiny has three sets of animation: Loiter, Walk, and Run. The animation controller of Tiny supports two animation tracks. Most of the time, one of these two tracks is active with an animation set. When Tiny's action calls for a change of animation (for instance, going from loitering to walking, or stopping from running), we enable the second track with the new animation set to play and set a transition period to complete the transition from the first track to the second in. Then, as we advance time, the animation controller will generate the correct frame matrices that reflect the transition smoothly by interpolating between the two tracks. After the transition period passes, the first track is disabled, and the second track plays the new animation we see Tiny in. CTiny also takes advantage of the callback system to play the footstep sound at appropriate instances in the animation. At initialization time, CTiny sets up a structure, CallbackDataTiny, containing the data to pass to the callback handler. The structure has three members. m_dwFoot indicates which foot is triggering the sound; m_pvCameraPos points to the camera position when the callback is made; m_pvTinyPos points to the world space coordinates of Tiny. This data is used to determine which sound to play (left or right), and how loud it should be. The sample defines a class called CBHandlerTiny, derived from ID3DXAnimationCallbackHandler, that contains the callback handler function to be called by the animation controller. In the callback handler function, HandleCallback(), we retrieve the data passed to us as a CallbackDataTiny structure. We then play a DirectSound buffer with the appropriate volume based on the values in the CallbackDataTiny structure. In FrameMove(), the sample iterates through the array of CTiny instances and calls Animate() on each one of them. This updates the behaviors of all instances, changing them if needed. The rendering code of the application is relatively simple. It first sets up the view and projection matrices based on the camera position and orientation, then it renders the mesh object representing the floor. Next, it goes through the array of Tiny instances and calls AdvanceTime() and Draw() on each one of them. AdvanceTime() makes the animation controller update the frame hierarchy's matrices, then Draw() renders the instance using the updated matrices. Finally, the code renders the informational text as necessary.
The Animation Class Library This class is the heart of the library. Its function is to encapsulate the mesh hierarchy loaded from a .X file. It can also create instances of the animating mesh, of the type CAnimInstance, that share the mesh hierarchy from the .X file. These instances are associated with the CMultiAnim object that creates them. In its initialization method, CMultiAnim::Setup(), CMultiAnim determines the maximum size of the matrix palette based on the version of the vertex shader present. It then loads the mesh hierarchy from the given .X file, and creates an effect object from the given .fx file that contains the vertex shader with which the mesh is rendered. The frames of the mesh hierarchy and the animation controller are created in this process. The frames consist of a tree structure representing the hierarchy of the bones, as well as the mesh objects. This structure is shared by all associated instances. The animation controller is associated with the mesh hierarchy frames. The animation controller that CMultiAnim holds is not used for animation. Instead, when a new animation instance is created, the animation controller is cloned, and the new animation controller is owned by the new instance. The application uses an instance's own animation controller to control its animation. Because each instance has its own copy of animation controller, it can animate independent of all other instances. When the AdvanceTime() method of an animation controller in an animation instance is called, the animation controller will update the transformation matrix for each frame in the mesh hierarchy. The frames are then used in the rendering of the instance.
CAnimInstance
When an animation instance is rendered, it uses the effect object of its associated CMultiAnim to set the rendering parameters, including the vertex shader. The vertex shader function is responsible for skinning the mesh, transforming the position to screen space, and computing the color. The skinning portion is done by the function VS_Skin. This function is located in the vertex shader file Skin.vsh. The file contains an array of float4x3 named amPalette, which represents the matrix palette for skinning meshes, and the VS_Skin skinning function. VS_Skin is designed to be invoked by another vertex shader function, so an application can call it in its own vertex shader function to handle the skinning process. VS_Skin takes the object space position and normal, up to three blending weights (the last blending weight is computed by subtracting the sum of all other weights from 1), and up to four indices for the matrix palette. Then it transforms the position and normal up to four times with the corresponding matrices in the matrix palette and blending weights, and adds the result together to form the world space position and normal.
CMultiAnimAllocateHierarchy CreateMeshContainer() does a little more work than simply allocating and copying. It has to:
This structure is derived from D3DXFRAME. It represents a single frame, or bone, in a mesh hierarchy. A frame may contain siblings or children. The entire mesh hierarchy is structured similar to a tree. The application can add members to a structure derived from D3DXFRAME as necessary. In this sample, no additional members are needed.
MultiAnimMC
Pitfalls and Alternatives Some of the things that this sample does can be done differently, with different trade-offs. Because all instances of Tiny share the same frame hierarchy matrices, the sample must advance time on a specific instance and render it before moving on to another instance. Generally, this does not present a problem. However, if an application wishes to update the matrices for all of the instances first then render them, it can make new animation controllers point to a different set of matrices when cloning. This way, each animation controller has its own set of matrices to work with, and overwriting will not occur, at the expense of more memory usage. It's also worth noting that the matrix palette in the skinning vertex shader is an array of matrices, and naturally takes up a significant number of constant registers. An application may experience the problem of insufficient constant registers for other use. The application can work with a smaller sized matrix palette by setting the MATRIX_PALETTE_SIZE_DEFAULT shader #define to a smaller value when creating the effect object in CMultiAnim. The drawback of this approach is that the mesh may contain more subsets that need to be drawn separately. Note that this sample specifically demonstrates rendering animated meshes. It does not handle static meshes, although the code can be extended to do that. Another task that the sample only does partially is the complete preservation of animation controllers' states when the Direct3D device is released and re-created. This generally happens when the application switches from HAL to REF device, or vice versa, or from one 3D device to the other on a dual-monitor system. In MultiAnimation, when the Direct3D device is released, all animation controllers are also released. The animation controllers are then re-created when the sample initializes the new Direct3D device. This presents a problem, because animation states stored in animation controllers are lost. The sample loses the knowledge of what animation each track is playing, what tracks are enabled, and speed and weight of each track before the old device is released. To remedy this issue, an application should, before releasing Direct3D objects in its cleanup function (DeleteDeviceObjects for MultiAnimation), retrieve the animation controllers' states and save them in a buffer. Then, after it re-creates the animation controllers that it needs (in InitDeviceObjects), it should restore the animation controllers' states from the buffer. For each animation controller, the states that should be saved are:
|