SpriteBatch draw calls with rotation using position and rotation values from a parent sprite

I have a UI system which consist of a tree of individual elements starting at a Root element, and rendering down through the tree for as many elements that exist. The transform of a parent affects all its children, so that position / scale / etc is relative to the parent instead of absolute screen space values.

Here is the Render function for an individual element:

public void Render( SpriteBatch spriteBatch, Matrix parentTransform, float parentAlpha )
{
	float alpha = parentAlpha * this.Alpha;

	Matrix transform = Matrix.CreateTranslation( X, Y, Depth ) * Matrix.CreateRotationZ( MathHelper.ToRadians( ZRotation ) );
	transform *= parentTransform;
	Vector3 pos = transform.Translation;

	// Fixes some odd behavior with how SpriteBatch handles rendering rectangles with origin values
	Rectangle elementRect = new Rectangle( (int)pos.X, (int)pos.Y, (int)Width, (int)Height );
	Rectangle destRect = new Rectangle( elementRect.X + elementRect.Width / 2, elementRect.Y + elementRect.Height / 2, elementRect.Width, elementRect.Height );
	Vector2 originOffset = new Vector2( _Image.Width / 2, _Image.Height / 2 );

	spriteBatch.Draw( _Image, destRect, new Rectangle( 0, 0, _Image.Width, _Image.Height ), ColorMod * alpha, MathHelper.ToRadians( ZRotation ), originOffset, SpriteEffects.None, pos.Z );

	for ( int childNum = 0; childNum < _Children.Count; ++childNum )
	{
		_Children[childNum].Render( spriteBatch, transform, alpha );
	}
}

Things are already working well for the translation, but as you can probably see there is an issue with the rotation value due to the fact that it is affecting the position of the Rectangle as being applied as the rotation value in the actual Draw call.

I’m wondering if anyone has a better way of handling this? The simple solution would be to just pass a distinct rotation value down the tree instead of applying it into the transform Matrix, but the issue there lies with the fact that I’ll run into this exact same issue when I go to add Scale as something that affects down the tree. In addition, at some point I’m going to optimize these by moving these calculations out of render and into the Update function, and it would be nice to be able to keep track of just the one transform matrix instead of a running position, rotation, and scale value.

I suspect the answer will be just to abandon SpriteBatch and just render everything as textured quads. Then there won’t be any issue with this Matrix approach as I just apply the running transform matrix to the verts. However, I wanted to see if anyone had any other answers before I do the refactor.

Matrix transform = Matrix.CreateTranslation( X, Y, Depth ) * Matrix.CreateRotationZ( MathHelper.ToRadians( ZRotation ) );

Changing it to this below, I can’t remember if this would matter, but I believe you have to rotate before creating translation. Since it’s technically Matrix.Identity * translation * rotation. First you move the position, then rotate with that position instead of with a center point. So changing it to rotate first, then translate. But not sure if that’ll work.

Matrix transform = Matrix.CreateRotationZ( MathHelper.ToRadians( ZRotation ) * Matrix.CreateTranslation( X, Y, Depth ) );
1 Like

I can’t believe I did that and completely missed the fact I was doing my matrices in the wrong order. I spent so much time staring at this and I was WAY overthinking what the actual problem was.

Thanks so much for catching that - you’re 100% correct that was the issue. This is continued proof of how essential a second set of eyes is when coding.

I still have to update the code a bit to ensure I’m using the correct rotation value passed to the Draw call (will post up what I end up using) but the incorrect multiplication order does indeed fix the offset issue I was seeing. Cheers!

1 Like

FYI, you can compare what you are doing to do MG.Ex’s Transform2 class.

1 Like

Posting this up just in case anyone is ever looking at the thread for a similar solution.

I ended up abandoning SpriteBatch and just rendering everything as quads using DrawUserIndexedPrimitives. It ended up making things far more simple. Now the rotation / translation of any element is relative to the transform of its parent. And it is a simple matter to add scale information in as well since it would just multiply into the transform matrix I’m using.

public void DrawUserIndexedVertexRectangle( BasicEffect effect, GraphicsDevice device, Rectangle r, Matrix parentTransform )
{
	effect.Texture = _Image;

	float halfWidth = Width / 2;
	float halfHeight = Height / 2;
	Color color = this.ColorMod * Alpha;

	_QuadVerts[0].Position = new Vector3( -halfWidth, -halfHeight, 0f );
	_QuadVerts[1].Position = new Vector3( -halfWidth, halfHeight, 0f );
	_QuadVerts[2].Position = new Vector3( halfWidth, halfHeight, 0f );
	_QuadVerts[3].Position = new Vector3( halfWidth, -halfHeight, 0f );

	Matrix transform = parentTransform;
	transform *= (Matrix.CreateRotationZ( MathHelper.ToRadians( ZRotation ) ) * Matrix.CreateTranslation( X + halfWidth, Y + halfHeight, 0.0f ));
	for ( int i = 0; i < 4; ++i )
	{
		_QuadVerts[i].Position = Vector3.Transform( _QuadVerts[i].Position, transform );
		_QuadVerts[i].Color = color;
	}

	foreach ( EffectPass pass in effect.CurrentTechnique.Passes )
	{
		pass.Apply();
		device.DrawUserIndexedPrimitives( PrimitiveType.TriangleList, _QuadVerts, 0, 4, _QuadIndices, 0, 2 );
	}
}`

Notes

  • _QuadVerts is an array of VertexPositionColorTexture stores as a member variable in the UI Element class
  • A better optimization is to do this in the Update function (and only when dirty), but I kept it here temporarily to make it easyer to read
  • The effect is a pretty simple BasicEffect set up with an identity world and view matrix. The Projection is a standard OrthographicOffCenter set to the viewport.
1 Like

FYI, if you are doing only 2D transforms you might see better performance results using .NET Core’s Matrix3x2 or MG.Ex’s Matrix2 to transform a Vector2.

If you (or anyone else) runs into difficulty’s again with this at a later date post up here and I will share some of my code. I went through all this a while back when implementing a scene graph and found lots of oddities (which I largely have forgotten and didn’t take note of!)

But it mainly related to non uniform scaling and rotation. I’m a bit fussy to have things “right”, and it probably would rarely be an issue in practice so maybe no one cares, but with unity and actually any established render engine I have ever used they support this correctly with skew. I’d be happy to share the base display node class which has much of this stuff in it.

If I remember correctly I had to do a custom matrix decompose method which I can also share.