Offset when rotating object sprites more than once

Started by TheManInBoots, Sat 29/02/2020 14:54:28

Previous topic - Next topic

TheManInBoots

Edit: Actually I noticed that the offsets only happens when I rotate by certain degrees, so when I rotate 90,180,270 degrees there is no offset.

So I think I still need to experiment a little with this, since the reason does not appear to be rotating multiple times, but by what degree one rotates.

Spoiler


This was the original post

I am rotating object sprites, using dynamic sprites.

The first rotation works fine. But when I rotate it a second time, there is a little offset, it's about 33 pixels too far to the right and up.

The script I used:

In the script body:
Code: ags
DynamicSprite* shower;


Then in room after fade in:
Code: ags

shower=DynamicSprite.CreateFromExistingSprite(723, true);
shower.Rotate(90);
oindicator.Graphic =shower.Graphic;
Wait(20);

shower=DynamicSprite.CreateFromExistingSprite(723, true);
shower.Rotate(47);
oindicator.Graphic =shower.Graphic;
Wait(20);

shower=DynamicSprite.CreateFromExistingSprite(723, true);
shower.Rotate(154);
oindicator.Graphic =shower.Graphic;
Wait(20);

shower=DynamicSprite.CreateFromExistingSprite(723, true);
shower.Rotate(238);
oindicator.Graphic =shower.Graphic;
Wait(20);


So the first time it rotates normally the way I want it, too, and from the second time on there is this offset there.
I can fix it by simply moving the object 33 pixels left and down (-33 pixels on x-coordinate,  33 pixels on y-coordinate) after the first rotation, but I would like to understand why this happens in the first place, so that if I have more complicated things to rotate, I can calculate and asses exactly how big the offset is going to be, and not just fix it by eye sight and estimating the offset.
Does anyone have an idea why this happens?
It's exactly the same when I draw overlays instead of object sprites...
[close]
Thanks

Khris

This should clarify it:


70 x 100    70 x 100     100x118

TheManInBoots

#2
Got it!
Thanks!!

Khris

Btw, you can calculate the offset based on the sprite's original and rotated dimensions.

Code: ags
  int ox = (Game.SpriteWidth[sprite_slot] - Game.SpriteWidth[rotated_sprite.Graphic]) / 2;


Same for y.
Now add that to the x/y coordinate when you draw and the sprite will remain centered.

If you need to rotate the sprite around a different pivot point, just increase the canvas size of the original sprite so the supposed pivot is in the center.

TheManInBoots

#4
Oh thank god you mentioned those expressions.
It's exactly what I was looking for.
I was about to do a deep dive into trigonometry to calculate it.

Nice, so I used it and wrote after each rotation:

Code: ags

oindicator.X = 438+FloatToInt(IntToFloat(Game.SpriteWidth[723] - Game.SpriteWidth[shower.Graphic]) / 2.00);
oindicator.Y = 362-FloatToInt(IntToFloat(Game.SpriteHeight[723] - Game.SpriteHeight[shower.Graphic])/2.00);


and it's the most fluent and perfect rotation together with the floats.

Thanks

Snarky

There is no point converting an integer (the difference between the spritewidths) to float, dividing by another integer (2.00), and then converting back to int. Literally the only effect of that is to make your code run slower (as well as making it less readable and easier to make a mistake with).

The only times you want to do the intermediate calculations in float is if you're doing some operation with at least one non-integer number (multiplying or dividing by something with decimal parts, or adding or subtracting two numbers with decimals in them), or if you need special control over the rounding (which doesn't apply here since dividing by 2 can only give .5 as the fractional part). So just do it like Khris showed.

TheManInBoots

#6
Well when I looped through the rotation, it was all jerky and shaky (moving around by 2,3 pixels) because of the rounding when using ints.
Adding the floats made it perfect!
So I do the way it works and looks perfectly smooth!!!
I don't care that you don't like it.
It works.
You sound very upset

Snarky

Quote from: TheManInBoots on Sun 01/03/2020 12:18:20
Well when I looped through the rotation, it was all jerky and shaky (moving around by 2,3 pixels) because of the rounding when using ints.
Adding the floats made it perfect!

No, that was not the cause of the problem, and not the thing that fixed it. What you're doing is Cargo Cult Programming.

Your attitude when people try to explain that you're doing something wrong leaves much to be desired.

TheManInBoots

#8
Wow.
Thanks for the insult. We're done here.

Khris

The difference between sprite dimensions is either even or odd. Even if it is odd, the result will be the same because by default, FloatToInt rounds down, which amounts to cutting off the .5, exactly what happens when you divide an odd integer by 2.

So you could add eRoundNearest or eRoundUp to the conversion, but with the code you posted, I don't see how that can have any effect on the jerkiness.

Can you confirm that using floats actually does have an effect please?

Snarky

Quote from: Khris on Sun 01/03/2020 17:08:35
The difference between sprite dimensions is either even or odd.

… And in fact, it's always going to be even, because symmetry ensures that if the rotation requires x pixels of additional padding on the left, it's also going to require x pixels of padding to the right (and same with the top and bottom). So the difference is always even and dividing by 2 will always give an integer result.

TheManInBoots

#11
Here is the exact code I use to demonstrate this:

I loop the rotation once with ints, once with floats:

Code: ags

// room script file

DynamicSprite* shower;
int angle=1;

function room_AfterFadeIn()
{
//first loop with ints
while(angle<360)
{ shower=DynamicSprite.CreateFromExistingSprite(723, true);
shower.Rotate(angle);
oindicator.Graphic =shower.Graphic;
oindicator.X = 438 (Game.SpriteWidth[723] - Game.SpriteWidth[shower.Graphic]) / 2;
oindicator.Y = 362-(Game.SpriteHeight[723] - Game.SpriteHeight[shower.Graphic])/2;
Wait(1);
angle  ;}  

angle=1;  

//second loop with floats
while(angle<360)
{ shower=DynamicSprite.CreateFromExistingSprite(723, true);
shower.Rotate(angle);
oindicator.Graphic =shower.Graphic;
oindicator.X = 438 FloatToInt(IntToFloat(Game.SpriteWidth[723] - Game.SpriteWidth[shower.Graphic]) / 2.00);
oindicator.Y = 362-FloatToInt(IntToFloat(Game.SpriteHeight[723] - Game.SpriteHeight[shower.Graphic])/2.00);
Wait(1);
angle  ;}
}



And here is the exact result with that script.
https://streamable.com/laijv

ints rotation are until 00:20, then starts the float rotation.
I'm out of this thread for now however.
I don't just get insulted like that.
I don't want to talk about this anymore.

Thanks

Khris

Snarky: that depends on the algorithm, the size of the rotated sprite is calculated using trig and can very well be even or odd, depending on the angle. I tested it and the difference has odd values, too.

I also found the cause of the confusion: I'm calculating a negative offset, and since -2.5 for instance by default gets rounded down to -3 instead of -2, there's a noticeable difference between the two ways because when the difference between the two sizes is odd, the integer version results in for instance -2 while the float version rounds down to -3.

It does not matter one bit for the jerkiness in general though; I'm rotating two Roger sprites next to each other using both versions, and the rounding errors make both of them jerk violently, just not at the same time.

I guess for a square sprite it's possible that the occasional difference by one pixel exactly compensates for the rounding errors, which is probably what we see in the example movie.

In conclusion: it was not really a fix, just a happy accident.

Snarky

I guess I owe you an apology, TheManInBoots. You were right insofar as the problem was related to integer rounding of this expression, and I was wrong to call your solution Cargo Cult Programming.

Quote from: Khris on Sun 01/03/2020 17:37:13
I also found the cause of the confusion: I'm calculating a negative offset, and since -2.5 for instance by default gets rounded down to -3 instead of -2, there's a noticeable difference between the two ways because when the difference between the two sizes is odd, the integer version results in for instance -2 while the float version rounds down to -3.

Ouch! That's a nasty gotcha. I can't help but feel that the default FloatToInt() behavior should match how integer divisions are truncated, and round negative numbers towards zero.

Quote from: Khris on Sun 01/03/2020 17:37:13
Snarky: that depends on the algorithm, the size of the rotated sprite is calculated using trig and can very well be even or odd, depending on the angle. I tested it and the difference has odd values, too.

That's very strange, Khris. I've done it a number of time and it has always done so for me, or seemed to, at least. I wonder if there are certain properties of the sprites I've used that ensure they behave.

It seems to me that if the difference is an odd value (the padding around the original sprite and its center, the pivot, is uneven on each side), it's not going to be possible to align the sprite pivot exactly, so positional jittering is inevitable. Though I suppose it depends on how the rotated sprite is positioned on the canvas. If it simply crops off one edge of the canvas due to rounding, for example…

Quote from: Khris on Sun 01/03/2020 17:37:13
It does not matter one bit for the jerkiness in general though; I'm rotating two Roger sprites next to each other using both versions, and the rounding errors make both of them jerk violently, just not at the same time.

Right, that's what I would expect.

Quote from: Khris on Sun 01/03/2020 17:37:13
I guess for a square sprite it's possible that the occasional difference by one pixel exactly compensates for the rounding errors, which is probably what we see in the example movie.

In conclusion: it was not really a fix, just a happy accident.

Hmm… It can hardly be a total coincidence, though. Depending on exactly how the rotation algorithm is implemented (how it rounds positions and sizes), I can sort of see how this would happen, but what I can't figure out is why different sprites would end up with different displacements of the pivot point from the center of the canvas. "It's square" doesn't sound like a satisfying explanation to me.

I wonder if a more likely factor is whether the original sprite had an odd or even size along a given dimension. Additional testing might be required.

Crimson Wizard

#14
Quote from: Snarky on Mon 02/03/2020 21:30:23
Quote from: Khris on Sun 01/03/2020 17:37:13
I also found the cause of the confusion: I'm calculating a negative offset, and since -2.5 for instance by default gets rounded down to -3 instead of -2, there's a noticeable difference between the two ways because when the difference between the two sizes is odd, the integer version results in for instance -2 while the float version rounds down to -3.

Ouch! That's a nasty gotcha. I can't help but feel that the default FloatToInt() behavior should match how integer divisions are truncated, and round negative numbers towards zero.

I was very curious too why there could be a difference, and this is indeed counter-intuitive, at least for me.

At first I thought there's some bug in the engine, but no, it very explicitly deals with positive and negative floats separately, so counter-intuitive or not, it is a part of original design.

https://github.com/adventuregamestudio/ags/blob/master/Engine/ac/math.cpp#L20

So, when it sais "round down", it is the absolute "down", not "zero".

But this also means that it lacks another mode, that would match C-style float to int cast.


TheManInBoots

#15
Of course the jerkiness is related to the rounding, just not in the way you guys think.

The real problem is that with sprites where height and width are different, the rounding happens in opposite ways.
Let's take the Roger sprite that Khris mentioned for example.
When Roger is turned around 90 by degrees, the height of the turned dynamic sprite is lower than the height of the original sprite, so the difference of the formula we use
heightSprite-heightdynamicsprite is positive, and therefore will cause normal down rounding.
However the WIDTH of the dynamic sprite is larger than the original sprite, so the difference
widthSprite-widthdynamicsprite is NEGATIVE, and therefor will cause what we perceive in the image as up round (what Khris explains, that it rounds from -2,5 to -3 instead of -2). It's exactly that difference in rounding of the x and y coordinate at the same moment (one rounds 'up', the other down) that causes the jerkiness.

So when you want to rotate a sprite with different height than width (and not just a square) you simply need to equalize the rounding by very simply writing:

Code: ags
oindicator.X = 438 FloatToInt(IntToFloat(Game.SpriteWidth[723] - Game.SpriteWidth[shower.Graphic]) /2.00, eRoundDown);
oindicator.Y = 362-FloatToInt(IntToFloat(Game.SpriteHeight[723] - Game.SpriteHeight[shower.Graphic])/2.00, eRoundUp);


It's that easy, and it creates the PERFECT rotation for ANY sprite.

Only guys who are willing to write function to declare an int can be able to find stuff like that out :D

Khris

Quote from: TheManInBoots on Wed 04/03/2020 04:33:47When Roger is turned around 45 by degrees, the height of the turned dynamic sprite is lower than the height of the original sprite
No; for Roger, a 25x50 sprite, you have to rotate by more than 55 degrees or so to get the same height, and only after it gets lower. And the offset in fact never gets positive for either x or y, but that's just a coincidence. Use other aspect ratios for the sprite and it will.

However you're half right about the other thing.

I did a bunch of tests and it looks like the only thing that matters is the width of the original sprite. Not the aspect ratio, and curiously not the height:
Provided you're using my negative offsets, if the width is even, round down the X offset and round up the Y offset. If it's odd, round up the X offset and round down the Y offset.

I assume the width of your indicator sprite is even?

TheManInBoots

#17
I meant 90 degrees of course, not 45.

TheManInBoots

#18
The width is uneven of my sprite.
It doesn't work if you use the uneven width itself.
I made it work by simply using an even number as width.
It does not even have to be the actual width of the indicator sprite and it still works for the calculation.
You should use the actual width plus/minus 1, otherwise the indicator object is going to appear too far away from it's actual position.

Cleanest way would still be to have an even width to begin with.

Edit: Also after experimenting with it, you shouldn't change the width more than one pixel because changing the ratio width-to-height from the actual sprite in the calculation might mess things up.

Khris

Did a few more tests and I have to correct my previous post:

If the width is odd, round X up, else down.
If the height is odd, round Y down, else up.
("rounding down" here means "move towards negative infinity regardless of sign")

I have tested this with various sprites, square and no, and it holds up so far.

Edit: fixed rounding comment

SMF spam blocked by CleanTalk