2d transformations hierarchy (like with bones) with matrix

I’m sure this was asked before but I didn’t find a decent answer for it.

I want to make a scene graph with bone-like sprites that rotate and transform in hierarchy. For example, take a look at this picture:

Lets say every bone is a sprite - if the left elbow rotates, it should effect the rotation and position of the left hand bone. and if left hand happens to hold something, that something should rotate and move accordingly.

Now to achieve that I tried the following approach:

  1. Every sprite can hold a list of child sprites.
  2. Every sprite have a transformation matrix of its own (local) and before drawing I multiply it with its parent world matrix (which includes its parent matrix and so forth until root).
  3. Because its impossible to apply transformation matrix on a single draw call I decompose the final world (eg local + parent) matrix into rotation, scale, and position and use it to draw the individual sprites.

With position and scale it works perfectly. But once I try to add rotation, all hell break loose.

Before I show the problems, let’s see some code. Here I calculate the final matrix of a sprite:

                // calculate local transformations
                _transformations = Matrix.Identity;
                _transformations = _transformations * Matrix.CreateRotationZ(Rotation);
                _transformations.Translation = new Vector3(Position.X, Position.Y, 0);
                _transformations.Scale = (FlipX ? new Vector3(-1, 1, 1) : Vector3.One) * Scale;

                // calculate final transformations
                _finalTransformations = _parent != null ? _transformations * _parent._finalTransformations : _transformations;

Pretty simple right? As you can guess '_finalTransformations ’ is what I eventually use when drawing each sprite. The draw call looks something like this:

    // calculate origin point
    Vector2 origin = new Vector2(_srcRect.Width * Origin.X, _srcRect.Height * Origin.Y);

    // get world position
    Vector3 worldPos = WorldTransformations.Translation;
    Vector2 position = new Vector2(worldPos.X, worldPos.Y);

    // get world scale
    Vector3 worldScale = WorldTransformations.Scale;
    Vector2 scale = new Vector2(worldScale.X, worldScale.Y);

    // take desired size into consideration
    if (_desiredSize.X != 0)
    {
        scale.X *= (float)_desiredSize.X / Texture.Width;
        scale.Y *= (float)_desiredSize.Y / Texture.Height;
    }

    // draw the sprite
    spriteBatch.Draw(
        Texture, 
        rotation: Rotation, 
        position: position,
        scale: scale,
        origin: origin, 
        color: color, 
        layerDepth: zindex);

Now what goes wrong? take a look at this sprite:

The sprite above is made from 7 parts: two legs, body, two hands, head and hair. in addition, there’s a gun as a child of the hands, and the hands can rotate based on aim direction (in the pic above there’s no problem because rotation is 0). Scaling and positioning works.

Now lets see what happens once I rotate the hands:

As you can see the gun turns smaller and smaller, rotation affect scaling. And if I let the hands rotate even more, its becoming worse:

Why didn’t it follow the hand? Since I gave the gun offset from the hand origin (which is the shoulder) I expected rotation to affect its local position and work.

Anyway I hope the problem is clear. Any noticeable problem in my code? or maybe someone done it before and willing to share?

Thanks!

Ok answering self for future seekers…

Instead of using matrices I dropped them completely and added the following Transformation class based on the code from here http://www.catalinzima.com/2011/06/2d-skeletal-animations/

    /// <summary>
    /// Renderable transformations - position, scale, rotation.
    /// </summary>
    public class Transformation
    {
        /// <summary>
        /// Transformation position.
        /// </summary>
        public Vector2 Position = Vector2.Zero;

        /// <summary>
        /// Transformation scale.
        /// </summary>
        public Vector2 Scale = Vector2.One;

        /// <summary>
        /// Transformation rotation.
        /// </summary>
        public float Rotation = 0;

        // identity transformation object.
        private static readonly Transformation _identity = new Transformation();

        /// <summary>
        /// Get the identity transformations.
        /// </summary>
        public static Transformation Identity { get { return _identity; } }

        /// <summary>
        /// Merge two transformations into one.
        /// </summary>
        /// <param name="a">First transformation.</param>
        /// <param name="b">Second transformation.</param>
        /// <returns>Merged transformation.</returns>
        public static Transformation Compose(Transformation a, Transformation b)
        {
            Transformation result = new Transformation();
            Vector2 transformedPosition = a.TransformVector(b.Position);
            result.Position = transformedPosition;
            result.Rotation = a.Rotation + b.Rotation;
            result.Scale = a.Scale * b.Scale;
            return result;
        }

        /// <summary>
        /// Lerp between two transformation states.
        /// </summary>
        /// <param name="key1">Transformations from.</param>
        /// <param name="key2">Transformations to.</param>
        /// <param name="amount">How much to lerp.</param>
        /// <param name="result">Out transformations.</param>
        public static void Lerp(ref Transformation key1, ref Transformation key2, float amount, ref Transformation result)
        {
            result.Position = Vector2.Lerp(key1.Position, key2.Position, amount);
            result.Scale = Vector2.Lerp(key1.Scale, key2.Scale, amount);
            result.Rotation = MathHelper.Lerp(key1.Rotation, key2.Rotation, amount);
        }

        /// <summary>
        /// Transform a vector.
        /// </summary>
        /// <param name="point">Vector to transform.</param>
        /// <returns>Transformed vector.</returns>
        public Vector2 TransformVector(Vector2 point)
        {
            Vector2 result = Vector2.Transform(point, Matrix.CreateRotationZ(Rotation));
            result *= Scale;
            result += Position;
            return result;
        }
    }

Then I use it like this:

// update world transformations
_worldTrans = _parent != null ? Transformation.Compose(_parent._worldTrans, _trans) : _trans;

and drawing:

            // draw the sprite
            spriteBatch.Draw(
                Texture, 
                rotation: WorldTransformations.Rotation, 
                position: WorldTransformations.Position,
                scale: WorldTransformations.Scale,
                origin: origin, 
                color: color, 
                layerDepth: zindex);

And that’s it, things seems to be working now.

1 Like

Glad you found a solution. It can be tricky to get right, but it’s great once you get it going. I used 2D skinned animation for TY the Tasmanian Tiger 4, and the maths was indeed a challenge.

Where you interpolating the matrices linearly? You cannot do that if you have rotation in them. If you handle rotation seperately like you do in your solution you can of course. If you want to interpolate rotation matrices you have to slerp. That’s easy to do on quaternions, hence MG has a function Slerp on the Quaternion struct. XNA docs: XNA Game Studio 4.0 Refresh | Microsoft Learn
But I’ve previously run into this and made a Transform class just like you :stuck_out_tongue:

Wow man that game looks really good! Very polished… I know its super off-topic but what did you use for animating? Spine 2d? I’m considering buying the full version but I wonder if there are better alternatives (currently leaning for spine mainly because the JSON export).

Thanks for the extra info! :slight_smile: And good to know you ended up doing a similar solution, it reinforce my confidence in this decision.

It was an in-house tool written for that project. I wrote custom content pipeline importers and processors for it.

I can recommend DragonBones which is a free alternative to Spine. DB is pretty good for a free tool. I haven’t used Spine myself, but my guess is that it’s a bit more polished, so if you don’t care about the price tag it’s probably a better option. Also IK was not very good in DragonBones when I looked at it about a year ago.

I started working on a runtime then, but I didn’t finish it and it produces a lot of garbage. You can mesh your textures in DB to deform them and they’ve recently added the option to assign weights to vertices of such a texture mesh for interpolating between bones during an animation. I don’t think I explained that very clearly, so here’s a video that show the functionality if you’re interested: https://www.youtube.com/watch?v=xUfosRr9fY4
That is not implemented in my runtime.

DB exports in json too and the stuff I wrote could easily be extracted into a Content Importer (with a few change because of the DB format changes in the past year).

I want to get back to it sometime to get things properly implemented and provide a very decent, completely free alternative to Spriter/Spine. But probably won’t get to it soon.

Link: http://dragonbones.com/en/animation.html

Interesting. If its generic enough and not commercial you should consider releasing it, I bet a lot of people would appreciate that. :innocent:

Cool I will check it out! It looks quite good and it export to JSON which I like. Spine 2d isn’t that expensive but since its for a hobby project with 0 budget I prefer free alternatives.

If you do let me know. :slight_smile:

1 Like

I don’t work for that company any more and unfortunately they’re not the type of company to open source or release any tools such as that.