DX11 Animation System


Context

Given a framework, we needed to create a system to manage object and component hierarchy as well to handle animation that could be rendered using a given hierarchy.

Tasks

  • Implement a plane without animation but with a movable rotor and turret.
  • The plane needs to be able to shot from the turret and the bullet will have inertia.
  • Implement a robot with 3 different animations (Attack, Idle, Death)
    • Animations need to blend between them.
    • Death animation will trigger when a robot is shot.
    • Attack animation will trigger if the plane flies nearly.
    • Idle will trigger after a few seconds of death or if the plane flies out of range.

Project

First Aproach

This project required a number of classes that will handle all the behavior of the project. The first integrated was all the code needed for that.

Classes Implemented

Mesh System

  • MeshBase

Container class which contains the information of the loaded mesh. Will be unique for each different mesh.

  • MeshSystem

Contains the array of the different MeshBases. Is the class called to request for a mesh and returns an existent MeshBase or generates a new one with the new mesh to load. That evades duplicated information.

  • Boundings

A bounding box is generated with the information of each MeshBase.

Animations

  • AnimationTransform

Contains the information about the hierarchy of an object for each keyframe. One of this class is generated for each bone. Has a getter function that returns the new hierarchy transform for a given time. The transform will be a merge of the 2 keyframes that the given time is between.

class AnimationTransform {
public:

	// Pair <KeyFrameTime, Value/s>
	std::vector<std::pair<float, float>>RotationX;
	std::vector<std::pair<float, float>>RotationY;
	std::vector<std::pair<float, float>>RotationZ;
	std::vector<std::pair<float, float[3]>>Translation;
	std::vector<std::pair<float, float[3]>>Scale;

	
	Transform GetLerped(float time); 

private:
	// Internal functions
	std::vector<XMVECTOR> GetCurrentQuatRotation(float time);
	std::vector<float> GetCurrentRotation(float time);
	std::vector<float> GetCurrentTranslation(float time);
	std::vector<float> GetCurrentScale(float time);

};
  • AnimationBase

Container class which has all the information of an animation. Saves an AnimationTransform for each bone. Will be unique for each loaded animation.

  • AnimationSystem

Contains the array of loaded AnimBases. Class called to request an animation. Will return the existent AnimationBase or generates a new one with the new animation to load. That evades duplicated information.

  • AnimationObject

Object created for each instance of an AnimationBase being reproduced. Contains a pointer to the animation being played and information about how to reproduce it, such as being reproduced forward or backward and how to be reproduced (Once, Loop or PingPong).

class AnimationObject
{
public:
  AnimationObject();
  ~AnimationObject();

  bool Load(const char* animationName);
  Transform GetBoneTransform(const char* boneName);

  void Play();
  void Stop();
  void Pause();
  void Resume();
  void Update(float DeltaTime);

  AnimationBase* animation_ = nullptr;

  AnimType type_ = AT_Once;
  float animTime_=0.0f;
  bool isPlaying_=false;
  bool forward_=true;
private:

};
  • AnimationBlender

That is the actual class used for all the objects that use animations. Contains at the most 2 AnimationObject playing at the same time and is used to switch from one to another. That blends the animation being reproduced given a time to do it. Each AnimationObject used is stored to evade the creation of a new one if needed.

class AnimationBlender
{
public:
    AnimationBlender();
    ~AnimationBlender();

    void Update(float DeltaTime);

    Transform GetBoneTransform(const char* boneName);

    //Forces the playing animation to a given one
    bool SetAnimation(std::string animation);

    //Animation to blend to and the blend duration
    bool BlendTo(std::string animation, float time = 1.0f);

    //Forces the blending to a given percentage
    void SetBlendValue(float blendPercentage);

    AnimationObject* animationPlaying = nullptr;
    AnimationObject* animationBlending = nullptr;

    float blendTime_ = 0.0f;
    float blendDuration_ = 1.0f;

    //Blend Percentage (time/duration)
    float blendValue = 0.0f;

    bool blending_ = false;

private:
    std::map<std::string,AnimationObject*> animations_;
    AnimationObject* GetAnimation(std::string name);
};

Objects

A base class which from all the Object type classes will inherit. Contains the basic information as well as the main functionality for a 3D object.

  • Transform
  • Component Vector
    • Root Component
  • Draw Mode (For Debug purposes)
    • Normal
    • Bounding
    • Wireframe

An Object can have a vector of components which will be updated with the object itself. Those components must inherit from the base class Component. A Templated class is provided to ensure that.

   template <typename T>
    T* AddComponent() {
        static_assert(std::is_base_of<Component, T>::value, "T must inherit from Component");
        T* newComponent = new T();
        if (newComponent) {
            components_.push_back(newComponent);
            ((Component*)newComponent)->objectOwner_ = this;
            return newComponent;
        }
        return nullptr;
    }

std::vector::push_back() would create an assert if T incompatible, but a custom assert is more helpfull

Component

A base component class containing most of the basic information and functionality. Different component classes will inherit from that base.

  • Parent Component
  • Vector of child Components
  • Pointer to the owner Object
  • Local Transform

MeshComponent

An inherited class that contains 2 extra variables.

  • MeshBase
  • BoundingBox

Only the draw function has been overridden.

class MeshComponent : public Component
{
public:
    MeshComponent();
    ~MeshComponent();

    bool SetMesh(const char* route);

    virtual void Draw() override;
    void DrawBounding();

    MeshBase* mesh_ = nullptr;
    BoundingBox bounding_;

};

SkeletalMeshComponent

Even if it is not a complete SkeletalMesh, that class contains an std::map pairing each bone name with an individual MeshComponent. The components will replicate the hierarchy of the animation requested for the AnimationBlender. It uses Root as the parent component. Only the draw function has been overridden.

 class SkeletalMeshComponent : public Component
{
public:
    SkeletalMeshComponent();
    ~SkeletalMeshComponent();

    bool AddBone(const char* BoneName, const char* Route, Transform& transform, const char* Parent = "");

    virtual void Draw() override;
    void DrawBoundings();
    //Bone-Mesh Structure
    std::map<std::string, MeshComponent*> skeleton_; 
    AnimationBlender* animBlender_=nullptr;

};

Updated: