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:
- Every sprite can hold a list of child sprites.
- 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).
- 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!