4
\$\begingroup\$

I can see this has been asked before in various ways. I am struggling to work it out though hence asking again.

2d sprite that moves and rotates. I'm looking to contain it in a bounding box as it appears that is the most efficient way to do collision detection.

I had no problems getting it to work without rotation but that is simple! Now the sprite rotates I can't seem to find the right way of writing the code.

To create a bounding box for the sprite, my understanding is I need to work out the 4 corners of the sprite after rotation and use these to work out the width + height of the correctly sized bounding box.

I've used the article here - http://msdn.microsoft.com/en-us/library/dd162943(v=vs.85).aspx - specifically the formula:

x' = (x * cos A) - (y * sin A) 
y' = (x * sin A) + (y * cos A)

The code I'm currently using:

x1 = (positionX * Math.cos(rotation)) - (positionY * Math.sin(rotation));
y1 = (positionX * Math.sin(rotation)) + (positionY * Math.cos(rotation));

x2 = (positionX+width/2 * Math.cos(rotation)) - (positionY+height/2 * Math.sin(rotation));
y2 = (positionX+width/2 * Math.sin(rotation)) + (positionY+height/2 * Math.cos(rotation));

positionX and positionY is the current top left hand corner position of the sprite.

rotation is the radians the sprite has been rotated by (and rotation is from the centre).

width and height are of the sprite.

I thought the code above would give me the co-ordinates of the top left and top right corners of the rectangle, rotated appropriately. However, on my canvas, I'm drawing a line from x1,y1 to x2,y2 and it doesn't follow the sprite at all!

Any suggestions? Sorry for the long post!

Cheers!

\$\endgroup\$
13
  • 1
    \$\begingroup\$ Circles are nice if you are able to freely rotate the sprite. Collisions between two circles is dead simple: if distanceBetween < radius1 + radius2 then colliding \$\endgroup\$ Commented Dec 7, 2011 at 17:37
  • \$\begingroup\$ Well I considered circles but am I not going to have a problem keeping the circle in the middle of the rotated sprite? \$\endgroup\$ Commented Dec 7, 2011 at 17:47
  • 1
    \$\begingroup\$ @ChrisEvans, it's just as easy to determine if two circles are intersecting as it is to see if a point resides inside a single circle: if distanceBetween(mouse, object.Centre) < object.Radius then it's inside. Distance between two points is as simple as calculating the Hypotenuse: en.wikipedia.org/wiki/Hypotenuse \$\endgroup\$ Commented Dec 7, 2011 at 17:59
  • 1
    \$\begingroup\$ Ah, I see what you are saying. Apologies, I misunderstood. As Y increases, the object will go down the screen and if rotation increases the sprite rotates clockwise. :) \$\endgroup\$ Commented Dec 7, 2011 at 18:06
  • 1
    \$\begingroup\$ And to apologise further I am just being lazy. I have just implemented it in my code and it works perfectly. Thanks John + Sam. :) \$\endgroup\$ Commented Dec 7, 2011 at 18:11

3 Answers 3

3
\$\begingroup\$

I always try to solve these geometrical problems using transformations instead of trigonometry, because at least for me it's easier to visualize. Here's how I do it on my application in XNA.

Let's say I have a sprite that's been translated, rotated and scaled arbitrarily and I want to fit an AABB to it. I only need to know two things about this sprite:

  • The world matrix containing all three transformations combined
  • The sprite's untransformed extents (i.e. it's original width and height)

(BTW, these two bits of information combined are also all you need in order to represent an OBB)

Knowing that, I proceed to calculate an AABB like this:

// Calculate the position of the four corners in world space by applying
// The world matrix to the four corners in object space (0, 0, width, height)
Vector2 tl = Vector2.Transform(Vector2.Zero, matrix);
Vector2 tr = Vector2.Transform(new Vector2(extents.x, 0), matrix);
Vector2 bl = Vector2.Transform(new Vector2(0, extents.y), matrix);
Vector2 br = Vector2.Transform(extents, matrix);

// Find the minimum and maximum "corners" based on the ones above
float minX = Min(tl.X, Min(tr.X, Min(bl.X, br.X)));
float maxX = Max(tl.X, Max(tr.X, Max(bl.X, br.X)));
float minY = Min(tl.Y, Min(tr.Y, Min(bl.Y, br.Y)));
float maxY = Max(tl.Y, Max(tr.Y, Max(bl.Y, br.Y)));
Vector2 min = new Vector2(minX, minY);
Vector2 max = new Vector2(maxX, maxY);

// And create the AABB
RectangleF aabb = new RectangleF(min, max - min);

Maybe it's longer but at least I understand every step of the process this way.

\$\endgroup\$
1
\$\begingroup\$

You have a parentheses issue. Use ((positionX+width/2) * Math.cos(rotation)) instead of (positionX+width/2 * Math.cos(rotation)), and repeat everywhere for X and Y.

(Edit: removed off-topic part after clarification from author)

\$\endgroup\$
2
  • \$\begingroup\$ I apologise but I didn't mean to have the width/2 in there at all - that was from some playing about trying to get it to work. \$\endgroup\$ Commented Dec 7, 2011 at 17:48
  • \$\begingroup\$ I see what you mean now, I got that wrong. x1 = ((self.getCentralX()-width/2) * Math.cos(rotation)) - ((self.getCentralY()-height/2) * Math.sin(rotation)); y1 = ((self.getCentralX()-width/2) * Math.sin(rotation)) + ((self.getCentralY()-height/2) * Math.cos(rotation)); x2 = ((self.getCentralX()+width/2) * Math.cos(rotation)) - ((self.getCentralY()+height/2) * Math.sin(rotation)); y2 = ((self.getCentralX()+width/2) * Math.sin(rotation)) + ((self.getCentralY()+height/2) * Math.cos(rotation)); I've tried that but it also doesn't work. The line disappears as the object rotates. \$\endgroup\$ Commented Dec 7, 2011 at 17:53
0
\$\begingroup\$

I have come across a similar problem in a game I'm developing. The code below should work for any matrix. Basically it takes the top left and bottom right points, transforms them and then calculates which point is the new top left, new width, new height and stores it into a Rectangle. This is for C# XNA. Probably not the fastest method, but it works for all matrix transformations.

    private Rectangle transformRectUsing(Rectangle tileBounds, Matrix m)
    {
        Vector2 x = new Vector2(tileBounds.Left, tileBounds.Top); x = Vector2.Transform(x, m);
        Vector2 y = new Vector2(tileBounds.Right, tileBounds.Bottom); y = Vector2.Transform(y, m);
        Vector2 newTopLeft;
        if (x.X < y.X)
            newTopLeft.X = x.X;
        else
            newTopLeft.X = y.X;
        if (x.Y < y.Y)
            newTopLeft.Y = x.Y;
        else
            newTopLeft.Y = y.Y;

        Rectangle result = new Rectangle(Convert.ToInt32(newTopLeft.X), Convert.ToInt32(newTopLeft.Y), Convert.ToInt32(Math.Abs(x.X - y.X)), Convert.ToInt32(Math.Abs(x.Y - y.Y)));
        return result;
    }
\$\endgroup\$
4
  • \$\begingroup\$ I have not tried it, but it seems to me this won't work correctly unless a few other steps are taken. First, because the sprite's transformation matrix is usually going to be defined in object space, while its bounds will usually be in world space. And even if you took care to work in the same space, transforming only the top-left and bottom-right corners isn't enough to fit an AABB. This is actually a pretty common mistake. You need to transform all four corners and then find the absolute minimum and maximum values of x and y between all four of them for it to work. \$\endgroup\$ Commented Dec 8, 2011 at 6:03
  • \$\begingroup\$ It's hard to explain without an actual picture of the problem but check this image. The blue square is your sprite, and the AABB you want to get is the black square outline. Now try to consider what would happen if you only used the top-left/bottom-right corners (pick any two diagonal corners on the blue square). If you try to draw an AABB between those two corners, you'll get a much smaller area that doesn't cover the entire sprite. \$\endgroup\$ Commented Dec 8, 2011 at 6:09
  • \$\begingroup\$ Kind sir you are entirely correct! I have realised that my code only works for rotation by values divisible by 90 degrees (90, 180, 280, 360 etc). Thank you for alerting me to this. \$\endgroup\$ Commented Dec 8, 2011 at 11:42
  • \$\begingroup\$ Glad I could help! I actually first found out about this on this book where they specifically warn you about this pitfall. \$\endgroup\$ Commented Dec 8, 2011 at 14:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .