Skeletal Animation System

Started by
23 comments, last by Danicco 10 years, 1 month ago

A keyframe can consist of only a rotation part if all keyframes are evenly seperated. Right?

Advertisement

Technically there is no reason why a keyframe cannot be a pair, for example.

There is no technical reason, but your mentioned primary 2 constraints proved to me in the distant past too constraining for any practical use.
I originally thought it would be easier to handle interpolation of vectors as their own type but I found out I was just making more pain for myself than necessary. A single track system with only one pairing type is not only easier to implement, it works how the underlying modeling software works which causes fewer animation bugs/restrictions/future work-arounds. It leaves you with only one type of data in your animation file.

Advancing 3 tracks instead of 1 is going to give you slight overhead but in practice you won’t be animating all 3 vector components at once that often, so updating and interpolating 2 tracks (XY for example) separately roughly evens out compared to updating 1 track but interpolating all 3 components (XYZ), with 1 (Z) being unnecessarily updated.


A keyframe can consist of only a rotation part if all keyframes are evenly seperated. Right?

How evenly they are separated should not be a factor.
A keyframe can technically have whatever you want. It could be a 3-component vector indicating radians along each axis, or a 4-component quaternion, or the upper 3×3 part of a transform matrix.

It’s just that the more you deviate from how your authoring software works the more likely you are to have improper animation playback, in addition to multiple implementations for track interpolation, multiple types of data in your file format, etc.

It is generally not worth using anything but a single float and (up to) 3 separate tracks, as counter-intuitive as that sounds.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid


They should inherit from the same base class that gives the rest of your scene “scene graph” capabilities. A gun should be attachable to any joint as easily as it should to any game object or part of a game object.

I'm trying to avoid inheritance and virtuals so I did this on composition, but I don't know if this will be a problem in the future, so far, it's been manageable:


class SceneObject
{
    public:
        Orientation currentOrientation;
        Orientation previousOrientation

        SceneObject* parentObject; //SceneGraph

        void UpdateMatrix(float& interpolation);
        Matrix4x4& GetMatrix();
        //etc, various functions required to "work" the calculations required for the scene positioning
};

class Scene
{
    public:
    private:
        vector<Model*> _models;
        vector<Image*> _images; //separated by type, I found it easier to sort and apply commands before drawing

        vector<SceneObject*> _objects; //but before drawing, I update all objects' matrices
};

class Model
{
    public:
        //etc
        void Draw(Camera* camera);
    private:
        SceneObject _sceneObject;
};

class Image
{
    public:
    private:
        SceneObject _sceneObject;
};

And I'm kinda obsessed about performance, so I'm really trying to make the engine do only the minimum required work, so I'm trying to figure if I really really need to have the skeleton in the "scene".

All objects in the scene have their matrices calculated, interpolated, checked for hierarchy (I have some trouble with this too, will describe below) etc

If a skeleton is used only to move a model's vertex data, I don't think I need to keep track of each bone's position in the scene. So I'm trying to figure if it'll be worth it or not.

I'd implement a Bone object that can be held in the Scene, but I just wouldn't put ALL skeleton bones and update all of them for all skeletons. I'd leave it up to the game to know if the "HandBone" is required as an object, then he gets a new instance from the skeleton, attach it to a model, link to the Bone, and it joins the Scene and is an object able to hold children/parents.


IMHO: Technically there is no reason why a keyframe cannot be a pair, for example. The constraints that are added by doing so are
,placement>

I'm thinking in something a bit different, to avoid calculating the track's next keyframe and interpolate, since I'm using "Frames" as time unit for the animations, I'm thinking in doing something like a "Motion" class.

When the skeleton/animation is loaded, I'd check all bones, all keyframes, and I'd create a Motion class with a value begin/end, like:


class Motion
{
    public:
        int frameStart, frameEnd;
        Vector3 position;
        Vector3 rotation;
        int boneIndex;

        Motion* nextMotion;
}

Then when my animation has a track playing, I wouldn't deal with figuring keyframes and such, I'd pick the current Motion, sum the values to the Bone's position if the "frame" time is between the motion. I'd probably be able to do something like this:


//for each track, loop through the motions
for(unsigned int i = 0; i < motion.size(); i++)
{
    Bone* bone = skeleton->GetBone(motion->boneIndex); //Getting the bone ref
    bone->position += motion->position;
    bone->rotation += motion->rotation;
}

(By the way, this only if each model HAS it's own skeleton, I know it conflicts with my idea up above of keeping only one instance, but it's just that I'm trying a bunch of things)

This also provides blending since I can create new motions based on two keyframes, it's just that I don't have to calculate each one during play, except for blends.

There's so many possibilities and I don't know which one to follow...

(I know about premature optimization, but that's how I'd like to do this... I'm not in a hurry to code a game, and I'm using this engine to learn a lot about programming, so I'm doing step-by-step, slowly, understanding the concepts and implementing whatever I figure it's best)

And thanks for the replies, found so many things I've overlooked and so much more to think about and implement!

If I understand correctly, you design seems to tightly couple a number of distinct concepts that may lead to you duplicating a lot of data due to the denormalised schema. I think it's probably quite important to separate out the concepts of a Skeleton, an Animation and the current PlayState of an animation to factor out shared/reusable data.

The idea would be that Skeletons and Animations don't actually change once they're loaded, i.e. a playing animation (or an animating skeleton) won't 'know' that they're being played/animated and they don't keep track of that themselves. This would be the responsibility of the PlayState to keep track of that kind of thing.

This would mean that a single Skeleton can be shared amongst many animations and entities, animations can be shared amongst many entities and each entity can just keep its own PlayState that records the current position in the timeline. So, for example, you could have 50 humans using the "Human" Skeleton, each one of those guys can be playing their own individual animations or some may be playing the same animation but in any case *all* of those entities can be at any point in time in their playback.

You could extend that design to achieve various effects like blending. For example a PlayState could be configured with a mask to block out the waist-to-feet portion of an animation which frees up the legs for another PlayState to animate. If masks are weighted (rather than boolean) then they could be used to feather the two animations together smoothly around the waist.

In case it isn't clear:

Skeleton - Models the hierarchy of bones, as well as their initial physical properties (bone length, initial transform, name, anything else). It can probably achieve that in an efficient 'flat' format without need for a recursive Bone class, merely some std::vectors for the various properties e.g. std::vector<double> boneLengths;

Animation - A series of keyframes per bone. You might make this as a container of Tracks (where a Track is a series of keyframes), one Track per bone.

PlayState - Pairs a Skeleton with an Animation and maintains the current point-in-time of the playing animation. Obviously it could also cache the current & next keyframes for efficient interpolation.

The final calculated state of an animating skeleton could be spat out as a new Skeleton object with its bones in the correct pose for that frame. Or, if you don't need the hierarchy, then as a new concept: The Pose. Which just models the final transformed state of each bone. You might even refactor Skeleton to be a pairing of a Hierarchy with the skeleton's initial bind Pose.


I'm thinking in something a bit different, to avoid calculating the track's next keyframe and interpolate, since I'm using "Frames" as time unit for the animations, I'm thinking in doing something like a "Motion" class.
When the skeleton/animation is loaded, I'd check all bones, all keyframes, and I'd create a Motion class with a value begin/end, like:
class Motion
{
public:
int frameStart, frameEnd;
Vector3 position;
Vector3 rotation;
int boneIndex;

Motion* nextMotion;
}
Then when my animation has a track playing, I wouldn't deal with figuring keyframes and such, I'd pick the current Motion, sum the values to the Bone's position if the "frame" time is between the motion. I'd probably be able to do something like this:
//for each track, loop through the motions
for(unsigned int i = 0; i < motion.size(); i++)
{
Bone* bone = skeleton->GetBone(motion->boneIndex); //Getting the bone ref
bone->position += motion->position;
bone->rotation += motion->rotation;
}
(By the way, this only if each model HAS it's own skeleton, I know it conflicts with my idea up above of keeping only one instance, but it's just that I'm trying a bunch of things)

Using frames and avoiding interpolation will most probably show you choppy animations, since the timing of your game loop is required to be absolutely stable. Moreover, it probably causes many more keyframes than are needed. And it hinders you from blending cyclic animations in general.

For the latter argument it must be clear that the playback speed is not totally defined by the animation itself. Let's say we have a WALK animation with 10 frames per cycle, as well as a RUN animation with 5 frames per cycle. Obviously, the RUN animation completes its cycle twice as fast as the WALK animation, and hence the skeleton is moved with double speed. We now want the skeleton to change movement from WALK to RUN in a smooth transition of 2 seconds, or else we want it to run constantly at a speed in-between. Either way, let's say the current speed is 6 frames per second. So you need to accelerate the playback speed of WALK by 10/6 and to decelerate those of RUN by 5/6. If you don't do so, then the one animation shows the phase where the right foot is on ground, while the other animation shows it in air, and this also weaves over time. Together the animation is terrible to look at. Notice that the playback speeds are adapted in a way that easily causes in-between hits w.r.t. keyframes.

Dealing with real keyframes at "arbitrary" time moments is not so bad, since the current pair of keyframes can be memorized in the playback speed object. So yo just need to look forward, e.g. whether the current time delta has exceeded the later of the both keyframes.


If I understand correctly, you design seems to tightly couple a number of distinct concepts that may lead to you duplicating a lot of data due to the denormalised schema. I think it's probably quite important to separate out the concepts of a Skeleton, an Animation and the current PlayState of an animation to factor out shared/reusable data.

...

Seconding.


If I understand correctly, you design seems to tightly couple a number of distinct concepts that may lead to you duplicating a lot of data due to the denormalised schema. I think it's probably quite important to separate out the concepts of a Skeleton, an Animation and the current PlayState of an animation to factor out shared/reusable data.

Oh that's what I was thinking in, but I wasn't really getting the idea behind it and I was mixing what Animation and PlayState is, thanks for clarifying that for me.


Using frames and avoiding interpolation will most probably show you choppy animations, since the timing of your game loop is required to be absolutely stable. Moreover, it probably causes many more keyframes than are needed. And it hinders you from blending cyclic animations in general.

In the example I forgot about interpolation, but I didn't meant I wouldn't do it, I'm using a double as "frameTimer" and I'd multiply the value by that value (which should be between 0~1). Each update increases the "frameTimer" by a value depending on my "updates per second" timer.


Dealing with real keyframes at "arbitrary" time moments is not so bad, since the current pair of keyframes can be memorized in the playback speed. So yo just need to look forward, e.g. whether the current time delta has exceeded the later of the both keyframes.

Now it seems much more clearer how I can implement this, I wasn't really sure about the "PlayState" class and was trying to fit the Animation (dealing with it as if it wasn't a resource) with it and the code was getting just messy and confusing.

I'm only unsure if I should make the skeleton / bones as a scene object as well, or if I should leave them as only a reference to all "human models" in the scene for example.

I think it might not be so bad calculating and updating all bones' matrices even if I'm not really using them in the scene, and I think I might be able to use them for physics/collisions easier if they're all in the scene (I haven't even started physics so I'm not sure, I'm only trying to plan ahead how I'd do it).

Then implementing a "special case" when a game requires a bone to hold a children in the scene is looking bad since if I'm going to use them for anything else, it wouldn't be the "special" case anymore.

Again, many thanks for all the replies, I got a much clear vision of how I should deal with animations now.

I structure this like:

Skeleton - defines the joint positions in bind pose

KeyFrame - defines the transforms for a single pose from the animation

Animation - stores a list of keyframes

Animation has a interpolatedKeyFrame(float t) method that returns the keyframe for that point

Skeleton has a palette(const KeyFrame &k) method that returns a matrix palette based on the bind pose and the input keyframe transforms

My equivalent to the PlayState that dmatter mentions in this system is just the float t input to Animation::interpolatedKeyFrame. This float is the only thing that changes once everything is loaded and multiple objects can share the same Skeleton and Animation without issues.

Blending based on fixed update, variable frame rate is handled by just storing a previous t and interpolating.


Animation a;
Skeleton s;

float t;
float previousT;

void render(float blend)
{
    KeyFrame k = a.interpolatedKeyFrame(interpolate(previousT, t, blend));

    shader.setPalette(s.palette(k));
    renderMesh();
}

I structure this like:

Skeleton - defines the joint positions in bind pose
KeyFrame - defines the transforms for a single pose from the animation
Animation - stores a list of keyframes

I would suggest the following:

Skeleton: The hierarchy of joints that creates the bind pose.
AnimatedSkeleton: An instance of a Skeleton used by model instances for their animations.
KeyFrame: A single {Time, Value} pair. Can act on an AnimatedSkeleton or anything else; just give it a reference to the value it modifies and another to a dirty flag.
Track: A list of KeyFrames, sorted by time with no duplicates.
Animation: A list of Tracks.
(Not separating Animation and AnimationInstance for brevity.)


The main problem with the vast majority of what has been discussed so far in this thread is that everyone is tightly coupling animations and skeletons, like how Aardvajk’s KeyFrame works specifically on Skeleton’s.

Keep in mind that everything else in your scene can be animated as well. Color values, light values, on/off switches, camera field-of-view, aperture, etc.

The generic solution is easier, more efficient, and far more flexible.
The flow should be:
  • A Track is a list of KeyFrames which are just values paired with times (as I said before, these really should be just single float values).
  • A TrackInstance has a pointer to a Track, a current time, and a reference to a value it modifies.
    • A TrackInstance can thus be applied to any value on anything, updating that value through its reference to it.
  • When a TrackInstance is updated by a certain number of microseconds, it figures out between which 2 KeyFrames it is (using a pointer to its reference Track, of which there is only a single instance), uses interpolation to get the current value, and applies that value to the reference (whatever single value this TrackInstance is supposed to animate).
  • The TrackInstance is sent the microsecond update value via the Animation, which is itself passed a time update value by which to advance.
  • When the Animation has finished updating all its tracks it uses an interface to tell whatever object to which it is attached that it has been updated by the Animation. Most objects do nothing because most animations are color values, object parameters, etc., that don’t need any post-processing after an animation has run. Note that this does not need to be implemented via an interface (as in don’t worry about virtual function calls) but it makes it easy to conceptualize.
  • Very few objects will need to recompile some information after an animation finishes. SkeletonInstances need to rebuild their matrices.
    • In a proper implementation, a model will have a scene graph by default with parts that have their own local and world transforms and an AnimatedSkeleton will simply update each respective object’s local transform after the animation modifies the data inside the AnimatedSkeleton. The AnimatedSkeleton joints will have in non-matrix form a position, rotation, and scale to which its Tracks will attach. When any of these is modified the local matrix of the object to which each joint is attached is minimally rebuilt (if only positions changed, just update the transform part of the object’s matrix).
I strongly suggest this organization and flow, as it is the industry-standard way, matches authoring tools such as Maya, and is the most flexible solution. A track can attach via reference (or pointer) to any value in the scene (although a track will always interpolate with a single float, template parameters can allow it to cast its float to anything before assigning to the reference), which means write-once-use-anywhere. It’s obnoxious to write an animation system specifically for skeletons and then another for light parameters, camera parameters, etc.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Interesting stuff, Spiro, glad you posted that. I was about to start work on some separate stuff for interpolating single values but you've made me think that could be incorporated into the animation system quite well if, as you say, it is decoupled from the skeleton.

Thanks.


(as I said before, these really should be just single float values)

There are of course some tradeoffs being made by that choice. It'll work fine for basic types like vectors and euler angles but not as well for types with less separable components such as quaternions and matrices (and they also have to stay normalised). You could perform an operation like NLerp on them, but not for example a SLerp. I guess you could have specialised tracks if complex interpolation was important (ScalarTrack, QuaternionTrack, etc).

This topic is closed to new replies.

Advertisement