Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

XNA 2D Basic Collision Detection with Rotation (Sprite2Sprite)

by Ioannis Panagopoulos

(If you are in a hurry and do not want to know how it's done but just do it, dowload the project and extract the CollisionDetection2D.cs file to include in your project that contains the collision detection methods)

In my previous post ,we have seen several collision detection techniques that can be used in 2D games in XNA. In that post, we have left out the changes that affect our code when textures can also be rotated. In this post, I intend to fill this gap by picking up from exactly where we stopped and adding rotation to the equation.

Therefore, if you haven't read the previous post , I recommend that you go and give it a look. In this post we will cover what happens when we want to check for collision between two sprites that can be rotated.? In the next post we will work with the case where the collision needs to be checked with elements from the scene (such as a car in a road), and the sprite (eg the car) can be rotated.

Prior to considering the two aforementioned problems lets see how we can rotate a texture in XNA. Rotating is done by using a different overload of the _spriteBatch.Draw method. We want our sprite (DrawableGameComponent) to be decorated with two more properties:

private Vector2 _textureReferencePoint;
public Vector2 TextureReferencePoint
{
    get { return _textureReferencePoint; }
    set { _textureReferencePoint = value; }
}

private float _rotationAngle;
public float RotationAngle
{
    get { return _rotationAngle; }
    set { _rotationAngle = value; }
}

(...)

The first is the reference point of the texture and the second is the angle of the rotation. The reference  is the point that is used as reference when we want to put a texture to a specific position. It is expressed in texture coordinates which means that point (0,0) is the upper-left corner of the texture. In the basic draw method where we do not use the texture's reference point, XNA defaults to point (0,0). So when we say that our texture should be drawn at _position (a Vector2 object) the texture's reference point is moved to _position and the texture's pixels are drawn based on that point:

If we change the texture's reference point, then, the new reference point will be moved to _position, resulting in a different placement of the texture at _postion. So if reference is set to (texture.width/2,texture.height/2) then:

The texture's reference point plays also a central role in rotation since it is the center around which the rotation will be performed. The following figure shows two 90 degrees rotations around different reference points:

 The rotation angle is measured in rads. You can use degrees and convert them to rads using the following formula:

 

((2 * (float)Math.PI) / 360) * AngleInDegrees.

 

Having those two properties available, we can now use a different overload of the SpriteBatch.Draw method to rotate our texture as we draw it on screen (the rotation will be performed around the TextureReferencePoint and the texture's TextureReferencePoint point will be placed at _position):

 _spriteBatch.Draw(_texture, _position,null,Color.White,RotationAngle,TextureReferencePoint,1.0f,SpriteEffects.None,0f);

 

The 1.0f stands for scaling (play with it to make your texture smaller/bigger).

And now for the tricky part that concerns collisions:

 

Sprite/Sprite collision detection (extends the previous post)

The first thing to be done is to adjust our bouding shapes accordingly since now the texture's origin has changed and _position points at the central point of the texture and not its upper left corner. Then, we need to take into consideration that the texture is rotated so along with it we must rotate the bounding shape.

We use the following method that rotates a point (PointToRotate) around an arbitary origin (OriginOfRotation point):

private Vector2 _rotatePoint(Vector2 PointToRotate,Vector2 OriginOfRotation,float ThetaInRads)
{
    Vector2 RotationVector = PointToRotate - OriginOfRotation;
    Vector2 RotatedVector = new Vector2()
    {
        X = (float)(RotationVector.X * Math.Cos(ThetaInRads) - RotationVector.Y * Math.Sin(ThetaInRads)),
        Y = (float)(RotationVector.X * Math.Sin(ThetaInRads) + RotationVector.Y * Math.Cos(ThetaInRads))
    };

    return OriginOfRotation + RotatedVector;
}

Apparently in the case where we use bounding circles, we do not have to do anything apart from taking into consideration that the origin of the texture is not at (0,0) but at (texture.width/2, texture.height/2) and therefore the center of the bounding circle is just the _position vector as follows:

public Vector2 CircleCenter
{
    get { return new Vector2(_position.X,_position.Y); }
}
public int CircleRadius
{
    get { return Math.Max(_texture.Width / 2, _texture.Height / 2); }
}

But with rectangles and triangles there is the need of rotating the bounding shape along with the texture. We use the aforementioned rotation method as follows. For the triangle:

public Vector2 TrianglePoint1
{
    get { return _rotatePoint(_position - TextureReferencePoint, _position, RotationAngle); }
}
public Vector2 TriangleOffset1 { get; set; }
public Vector2 TrianglePoint2
{
    get { return _rotatePoint(_position - TextureReferencePoint + TriangleOffset1, 
                                                _position, RotationAngle); }
}
public Vector2 TriangleOffset2 { get; set; }
public Vector2 TrianglePoint3
{
    get { return _rotatePoint(_position - TextureReferencePoint + TriangleOffset2,
                                                _position, RotationAngle); }
}

And for the rectangle we keep the same properties upper left point, width and height and we also use the rotation angle and the rotation origin.

For the circle and the triangle the collision detection methods demonstrated at the previous post work perfectly. But the method used for the bounding rectangles fails since it does not support rotation. To solve this we divde the rectangle in two triangles and then we use the same method we use  for the triangles. Therefore we do not pass the upper left corners and the widths and heights but the individual points of the rectangle making a method that can detect collisions for any 4point shape. You can see this in action (see the methods _createTrianglesFromRectangle() and BoundingRectangle() from the project).

But how do we deal with Per-Pixel collision detection? First, we do a collision detection for the rotated bounding rectangles by using the previously mentioned "bounding rectangle" approach. If this detects a collision the we need to check a per-pixel collision. But to do this we need to know the overlapping area of two rotated bounding rectangles which is kinda difficult. Actually, even if we could find the overlapping area of the two rectangles it would be difficult to perform a per-pixel collision detection on this area which could be of any shape (not parallel to the axis).

So instead of that we use a bounding rectangle that encloses the rotated texture as shown in the figure (check the _getBoundingRectangleOfRotatedRectangle() method of the project):

All we want to do is get the pixels within this bounding rectagles and perform a per-pixel collision detection on them. Apparently those are not the pixels of the texture only. The plot thickens...

A solution is a follows:

  • Draw the texture on screen.
  • Get a snapshot of the screen at the bounding rectangles of the rotated textures.
  • Perform a PerPixel collision detection on those two snapshots.

But how can we take a snapshot that contains only the two textures and not the background? Well, we create an imaginary drawing surface and we draw on that surface the textures one by one. Upon drawing each one on the imaginary surface we take the desired snapshots. Then we have what we need to perform perpixel collision detection the "traditional" way (Check the methods _getSingleSpriteAloneFromScene() and PerPixelWR() from the project).

  (You may also want to check out a newer post on integrating a Physics Engine with XNA here)

Shout it

kick it on DotNetKicks.com

blog comments powered by Disqus
hire me