Thursday, May 10, 2018

c# - Weird behavior with XNA SpriteBatch.Draw origin


I have a texture with 256x256 pixels.


Consider this piece of code:



    protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();
spriteBatch.Draw(texture, new Rectangle(0, 0, 100, 100), null, Color.White);
spriteBatch.End();

base.Draw(gameTime);
}


If I run this, the texture appears (predictably) at the top-left corner of the window with the correct size (100x100).


If I do this instead (XNA 4.0-only):


spriteBatch.Draw(texture, new Rectangle(0, 0, 100, 100), null, Color.White, 0, new Vector2(0, 0), SpriteEffects.None, 0);

Everything is still as expected. However, as soon as I change the origin parameter, the position gets weird.


    protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);


spriteBatch.Begin();
spriteBatch.Draw(texture, new Rectangle(0, 0, 100, 100), null, Color.White, 0, new Vector2(128, 128), SpriteEffects.None, 0);
spriteBatch.End();

base.Draw(gameTime);
}

In this last example, only 1/4 of the texture is visible.


In other words, the origin parameter not only changes the origin of the rotation, but also the destination rectangle. In other words, the Top/Left coordinates of the rectangle are not the Top/Left coordinates of the drawn sprite, but rather the coordinates of the Origin.X/Origin.Y texture pixel.


If the target position was just a Vector2, I'd understand (and fully support) this behavior, but with a destination rectangle it just seems odd, especially since the documentation does not seem to mention this fact (although to be honest, the documentation barely says anything at all).



A StackOverflow answer even suggests that a destination rectangle with rotation doesn't make much sense, but XNA offers it anyway.


What is the reason for this behavior?



Answer



Though your question about Draw arguments is understandable, I do think it is by design. Also,



the Top/Left coordinates of the rectangle are not the Top/Left coordinates of the drawn sprite, but rather the coordinates of the Origin.X/Origin.Y texture pixel.



This is an incorrect statement. Your origin has become the top-left visible pixel, which is governed by screen space, not by misdesign design of Spritebatch. The actual top-left pixel is simply off-screen.


For clarity, I put all your draws together into one screenshot, and I added some more with rotation. The black lines are gridlines at the destinationRectangle X and Y coordinates.


spriteBatch.Draw(texture, new Rectangle(100, 50, 100, 100), null, Color.White);

spriteBatch.Draw(texture, new Rectangle(100, 225, 100, 100), null, Color.White, 0, new Vector2(0, 0), SpriteEffects.None, 0);
spriteBatch.Draw(texture, new Rectangle(100, 400, 100, 100), null, Color.White, 0, new Vector2(128, 128), SpriteEffects.None, 0);

spriteBatch.Draw(texture, new Rectangle(400, 50, 100, 100), null, Color.White, MathHelper.PiOver4, Vector2.Zero, SpriteEffects.None, 0);
spriteBatch.Draw(texture, new Rectangle(400, 225, 100, 100), null, Color.White, MathHelper.PiOver4, new Vector2(0, 0), SpriteEffects.None, 0);
spriteBatch.Draw(texture, new Rectangle(400, 400, 100, 100), null, Color.White, MathHelper.PiOver4, new Vector2(128, 128), SpriteEffects.None, 0);


As you can see, your texture coordinate has not been changed. The destination rectangle has been moved (prior to rotating).


Does that make sense?



I think so.


To answer your question, yes, the origin affects both the rotation center and the draw destination. But that alteration is completely logical. A preemptive summary: a destination rectangle does not make sense for a rotated quad, and SpriteBatch.Draw()'s arguments make as much sense as is possible.


Consider the process of drawing this sprite. Using your rectangle with position and size, Spritebatch generates a quad that it will toss into its "batch". That quad needs to be rotated about some point (there is no draw overload that includes rotation but does not include origin) For that idea to make sense, some vertices of the quad need to be rotated from behind or above that origin. But you didn't specify a position, you only specified a destination rectangle. That means that the top-left corner vertex (based on the X and Y of your rectangle) must be moved away from the rotation point, and that move is -1 * origin. This happens internally, without any extra work by you. Yes, it changes the "idea" of a destination rectangle. But the height and width don't make any sense after rotation either (they've both changed).


Compare that to the alternative. For every rectangle that you want to draw, in order to determine its position, you have to manually rotate the top-leftmost corner relative to your origin, then transform your rectangle's X and Y, then pass that new rectangle into the actual draw call. You do all all that work simply to make the destinationRectangle's position match the screenspace position. In the meantime, the Rectangle's height and width are still wrong, because the rotation changed them, too. And some of your rectangle is farther left than your position value, so it was all for naught anyway.


Hopefully that makes sense. I am sure that most of your question came from the pixels that you thought had vanished (but were really just off-screen). If not, I'll be happy to argue about it more. :)


P.S. I am currently trying to roll some 2D physics, and I determined that SpriteBatch would cause more problems than it would solve exactly because of this kind of internal design decision. So I elected to generate quad vertices manually, to avoid my top-left point being transformed without my permission. But again, my trade-off for doing that is that I have to do much, much more work manually.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...