1

I'm working on a 2D game in C# and XNA and I have succeeded at getting the per-pixel collision detection with sprites working. The challenge that has me puzzled right now is how to calculate the location where the two sprites are colliding.

The reason I need this information is that the nature of this game requires that the objects have rotation and react somewhat accurately according to physics. My current testing environment involves two squares. Any suggestions on how to find the contact point would be well received.

Also if anyone has any suggestions on how to calculate the distance to move the sprites on collision so that they don't overlap that would be great too.

The code I'm using for the collision detection comes from here:

http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed

Thanks all!

Big McLargeHuge
  • 14,841
  • 10
  • 80
  • 108
APalmer
  • 919
  • 3
  • 11
  • 22

1 Answers1

3

For the first part about getting the contact points the code you linked to has done the hard work, instead of returning true return the the coordinate by transforming back to world space.

Heres the slightly modified code from http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed

public static IEnumerable<Vector2> IntersectPixels(
                    Matrix transformA, int widthA, int heightA, Color[] dataA,
                    Matrix transformB, int widthB, int heightB, Color[] dataB)
{
    // Calculate a matrix which transforms from A's local space into
    // world space and then into B's local space
    Matrix transformAToB = transformA * Matrix.Invert(transformB);

    // When a point moves in A's local space, it moves in B's local space with a
    // fixed direction and distance proportional to the movement in A.
    // This algorithm steps through A one pixel at a time along A's X and Y axes
    // Calculate the analogous steps in B:
    Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
    Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);

    // Calculate the top left corner of A in B's local space
    // This variable will be reused to keep track of the start of each row
    Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);

    // For each row of pixels in A
    for(int yA = 0; yA < heightA; yA++)
    {
        // Start at the beginning of the row
        Vector2 posInB = yPosInB;

        // For each pixel in this row
        for(int xA = 0; xA < widthA; xA++)
        {
            // Round to the nearest pixel
            int xB = (int)Math.Round(posInB.X);
            int yB = (int)Math.Round(posInB.Y);

            // If the pixel lies within the bounds of B
            if(0 <= xB && xB < widthB &&
                0 <= yB && yB < heightB)
            {
                // Get the colors of the overlapping pixels
                Color colorA = dataA[xA + yA * widthA];
                Color colorB = dataB[xB + yB * widthB];

                // If both pixels are not completely transparent,
                if(colorA.A != 0 && colorB.A != 0)
                {
                    // then an intersection has been found
                    yield return Vector2.Transform(new Vector2(xA, yA),transformA);
                }
            }

            // Move to the next pixel in the row
            posInB += stepX;
        }

        // Move to the next row
        yPosInB += stepY;
    }

    // No intersection found
}

As for the second part a common method is to add a small force opposite to the collision direction to repel them. This article on game physics is a good primer and there are few ready made physics engines that are robust like Farseer.

The sample the code is for transformed sprites if you don't need this feature you could probably simplify the code. If you don't use a physics engine to keep them from overlapping when you move one it could need another to be moved and so on, the physics engine would take care of this for you.

Edit: Here's some small changes to the MSDN sample so each contact point is drawn with a green pixel.

Add these fields

//Contact points are cleared and re-added each update
List<Vector2> contactPoints = new List<Vector2>();
//Texture for contact display
Texture2D pixelTex;

Add to LoadContent() somewhere

pixelTex = new Texture2D(GraphicsDevice, 1, 1);
pixelTex.SetData<Color>(new[] { Color.White });

Replace end of Update() with this

// Update each block
personHit = false;
contactPoints.Clear();
for(int i = 0; i < blocks.Count; i++)
{
    // Animate this block falling
    blocks[i].Position += new Vector2(0.0f, BlockFallSpeed);
    blocks[i].Rotation += BlockRotateSpeed;

    // Build the block's transform
    Matrix blockTransform =
        Matrix.CreateTranslation(new Vector3(-blockOrigin, 0.0f)) *
        // Matrix.CreateScale(block.Scale) *  would go here
        Matrix.CreateRotationZ(blocks[i].Rotation) *
        Matrix.CreateTranslation(new Vector3(blocks[i].Position, 0.0f));

    // Calculate the bounding rectangle of this block in world space
    Rectangle blockRectangle = CalculateBoundingRectangle(
                new Rectangle(0, 0, blockTexture.Width, blockTexture.Height),
                blockTransform);

    // The per-pixel check is expensive, so check the bounding rectangles
    // first to prevent testing pixels when collisions are impossible.
    if(personRectangle.Intersects(blockRectangle))
    {
        contactPoints.AddRange(IntersectPixels(personTransform, personTexture.Width,
                            personTexture.Height, personTextureData,
                            blockTransform, blockTexture.Width,
                            blockTexture.Height, blockTextureData));
        // Check collision with person
        if(contactPoints.Count != 0)
        {
            personHit = true;
        }
    }

    // Remove this block if it have fallen off the screen
    if(blocks[i].Position.Y >
        Window.ClientBounds.Height + blockOrigin.Length())
    {
        blocks.RemoveAt(i);

        // When removing a block, the next block will have the same index
        // as the current block. Decrement i to prevent skipping a block.
        i--;
    }
}

base.Update(gameTime);

Add to Draw() before spriteBatch.End()

foreach(Vector2 p in contactPoints)
{
    spriteBatch.Draw(pixelTex, new Rectangle((int)p.X, (int)p.Y, 1, 1), Color.FromNonPremultiplied(120, 255, 100, 255));
}
Big McLargeHuge
  • 14,841
  • 10
  • 80
  • 108
Kris
  • 7,110
  • 2
  • 24
  • 26
  • Thank you for your reply, I am however having a difficult time applying this change. First of all, I am unfamiliar with "yield" and why it would be used in this instance. ( have looked it up but on MSDN but im having a hard time understanding it) Also, what exactly would that return in this case and how would i deal with it. before I was calling the function as the "parameter" for an if statement. but I dont suppose that will work in this case. – APalmer May 11 '11 at 04:40
  • I used yield because there may be more than one contact point. The result is an IEnumerable so you can use `foreach` on the result or use Linq's ToArray, ToList extension methods. You could have added the points to a list and returned that but using yield fetches as needed so with Linq you can use `IntersectPixels(...).Any()` as a replacement for the original method that returns bool. Check the edit for an example. – Kris May 11 '11 at 18:19
  • Sir, you are my hero. I believe that I owe you a turkey sandwich. – APalmer May 14 '11 at 23:09