Adventure Game Studio | Forums

AGS Support => Advanced Technical Forum => Topic started by: Scavenger on Sat 17/06/2017 07:36:50

Title: Rotating an object around a pivot point.
Post by: Scavenger on Sat 17/06/2017 07:36:50
I'm trying to create a bell in-engine, which swings back and forth, but I'm having a little bit of trouble - I can rotate the sprite around its central point, and I can move the object in a circle, but I can't sync them up. I don't think I'm good enough at math to work it out.

The sprite is here:
(https://dl.dropbox.com/s/u6jbddtz6sunoxp/bell.gif?dl=0)
It should rotate around the middle of the hole at the top, so I can get it to swing. My current code is:

Code (AGS) Select
DynamicSprite *bellsprite;
int angle = 1;
function room_Load()
{
bellsprite = DynamicSprite.CreateFromExistingSprite (520);
}

function room_RepExec()
{
  angle++;
  if (angle > 359) angle = 1;
bellsprite = DynamicSprite.CreateFromExistingSprite (520);
bellsprite.Rotate (angle, 128, 128);
oBell.Graphic = bellsprite.Graphic;
oBell.X = FloatToInt(((100.0-64.0) * Maths.Cos (Maths.DegreesToRadians (IntToFloat (angle)))) - ((113.0-32.0) *  Maths.Sin (Maths.DegreesToRadians (IntToFloat (angle)))))+64;
oBell.Y = FloatToInt(((100.0-64.0) * Maths.Sin (Maths.DegreesToRadians (IntToFloat (angle)))) + ((113.0-32.0) *  Maths.Cos (Maths.DegreesToRadians (IntToFloat (angle)))))+32;
}


But this is incredibly wrong, I know it. I just don't know how to make it go. Can anyone give me some advice?
Title: Re: Rotating an object around a pivot point.
Post by: Snarky on Sat 17/06/2017 08:33:58
A rotation around an arbitrary point is usually calculated as:

-Translate the coordinate system so the pivot point is at 0,0
-Rotate
-Translate back

What you need to do is to work out the net effect of the two translations.

If the coordinates of your pivot are (pivotX,pivotY), the first translation is (-pivotX,-pivotY) and the second is (pivotX,pivotY). So we need to calculate:

Code (ags) Select
// translate pivot (pivotX,pivotY) to origin, rotate, and return
{
  float pivotTranslation[] = new float[2];
  pivotTranslation[0] = -pivotX;
  pivotTranslation[1] = -pivotY;

  pivotTranslation = Rotate2D(pivotTranslation, angle);
  pivotTranslation[0] = pivotTranslation[0] + pivotX;
  pivotTranslation[1] = pivotTranslation[1] + pivotY;
}


The formula for calculating the x and y coordinates of a point after a rotation by a certain angle is (according to the first result that came up when I googled it (https://stackoverflow.com/questions/20104611/find-new-coordinates-of-a-point-after-rotation)):

Code (ags) Select
float[] Rotate2D(float[] coords, float angle)
{
  float newcoords[] = new float[2];
  newcoords[0] = coords[1]*Maths.Sin(angle) + coords[1]*Maths.Cos(angle); 
  newcoords[1] = coords[1]*Maths.Cos(angle) - coords[0]*Maths.Sin(angle);
  return newcoords;
}


There's a further complication in that rotating the sprite changes its dimensions, so to keep the center centered you have to adjust the position. Depending on how you're doing that currently it might look a little different.

Note that because the pivot may not lie on an integer coordinate after rotation, you're probably going to get a bit of "wobbling" around it when you eventually convert the floats back to ints.
Title: Re: Rotating an object around a pivot point.
Post by: Mandle on Sat 17/06/2017 15:46:03
Couldn't you just extend the top of the sprite as a purple transparent area so that the hole in the ring becomes the center of the sprite?
Title: Re: Rotating an object around a pivot point.
Post by: Khris on Mon 19/06/2017 11:38:41
Here's working code:
DynamicSprite *bellsprite;
int angle = 0;
int bellSlot = 520;

function room_Load()
{
  bellsprite = DynamicSprite.CreateFromExistingSprite(bellSlot);
}

int bellX = 100, bellY = 113; // lower left corner coords (angle 0)
int px = 63, py = 23; // sprite's pivot cords

function room_RepExec()
{
  angle = (angle + 1) % 360;
 
  // set sprite and store dimensions
  bellsprite = DynamicSprite.CreateFromExistingSprite(bellSlot);
  int w = bellsprite.Width;
  int h = bellsprite.Height;
 
  // rotate if necessary
  if (angle > 0) bellsprite.Rotate (angle);
  oBell.Graphic = bellsprite.Graphic;
 
  // calculate sprite dimension change offset
  float xOff = IntToFloat(w - bellsprite.Width) / 2.0;
  float yOff = IntToFloat(bellsprite.Height - h) / 2.0;
 
  // calculate vector from pivot to center
  float vx = IntToFloat(w) / 2.0 - IntToFloat(px);
  float vy = IntToFloat(h) / 2.0 - IntToFloat(py);
  // rotate vector
  float rad = Maths.DegreesToRadians (IntToFloat (angle));
  float cos = Maths.Cos(rad);
  float sin = Maths.Sin(rad);
  float vrx = vx * cos - vy * sin;
  float vry = vx * sin + vy * cos;
   
  oBell.X = bellX + FloatToInt(xOff - vx + vrx, eRoundNearest);
  oBell.Y = bellY + FloatToInt(yOff - vy + vry, eRoundNearest);
}


There are two offsets:
1) the rotation increases the sprite, so that has to be corrected
2) moving the pivot away from the center, like Snarky explained
Title: Re: Rotating an object around a pivot point.
Post by: Mandle on Mon 19/06/2017 14:50:24
Amazing! But couldn't you just extend the top of the sprite as a... (see above)

Or was I wrong about how that might work?
Title: Re: Rotating an object around a pivot point.
Post by: Snarky on Mon 19/06/2017 16:00:08
Yeah, you could, but code gives you more flexibility (e.g. if you want to rotate around different pivots). Also, if the pivot is far from the center, the sprite will have to be a lot bigger, and this will probably slow it down further.
Title: Re: Rotating an object around a pivot point.
Post by: Mandle on Mon 19/06/2017 23:35:14
Quote from: Snarky on Mon 19/06/2017 16:00:08
Yeah, you could, but code gives you more flexibility (e.g. if you want to rotate around different pivots). Also, if the pivot is far from the center, the sprite will have to be a lot bigger, and this will probably slow it down further.

I see... What I was really after was that feeling of finding the keys in the sun-visor while you guys were trying to  hotwire the car... (laugh):P
Title: Re: Rotating an object around a pivot point.
Post by: Snarky on Tue 20/06/2017 00:05:12
Yeah, it's definitely a clever workaround. When you mentioned it I was annoyed that I overlooked such a simple alternative.
Title: Re: Rotating an object around a pivot point.
Post by: Mandle on Tue 20/06/2017 05:15:38
Quote from: Snarky on Tue 20/06/2017 00:05:12
Yeah, it's definitely a clever workaround. When you mentioned it I was annoyed that I overlooked such a simple alternative.

I always follow the path of least work first if possible... :-D
Title: Re: Rotating an object around a pivot point.
Post by: Scavenger on Tue 20/06/2017 20:01:10
I tried both methods, both Khris' code and expanding the sprite so that the pivot point was in the center. The latter method was a lot smoother, the code made the bell jitter around very unpleasantly, which I guess is the problem when you've got a lot of integer math going on.
Title: Re: Rotating an object around a pivot point.
Post by: Snarky on Tue 20/06/2017 22:05:40
Well, Khris's code is all in floats, but the problem is in the rounding.

The issue is that when you rotate the sprite, the corners go outside the original region, and therefore the new sprite has to be bigger to fit it. Even if it's only a tiny fraction of a pixel outside your "canvas", you of course have to add a whole row or column of pixels. This can shift the whole sprite almost a whole pixel right or down, leading to jittering.

When your pivot is at the center of the sprite, symmetry ensures that you add equally many pixels on each side in the rotated sprite, so by re-centering it the pivot stays in the same position, and you avoid any jittering. But when your pivot is not at the center this does not hold, so the sprite will jump around by about a pixel.

Given that AGS doesn't allow sub-pixel positioning of sprites, I really can't think of any way around it.
Title: Re: Rotating an object around a pivot point.
Post by: Khris on Tue 20/06/2017 22:26:47
The only way to create smooth and custom rotations is to calculate the rotated sprite yourself. That's actually what I did for my MarioKart demo, since the built in Rotate function causes jittering for large sprites due to rounding errors.
Unfortunately, doing it in real-time is pretty slow.
Title: Re: Rotating an object around a pivot point.
Post by: Mandle on Wed 21/06/2017 01:38:15
Quote from: Scavenger on Tue 20/06/2017 20:01:10
I tried both methods, both Khris' code and expanding the sprite so that the pivot point was in the center. The latter method was a lot smoother, the code made the bell jitter around very unpleasantly, which I guess is the problem when you've got a lot of integer math going on.

Hehe, there's those keys! :P 8-)

Glad to hear my stop-gap solution helped out! A very nice feeling indeed!

Can't wait to play the game now even if just to see the bell in action... (laugh)