PNG Non-Transparent Pixel Collision Detection?

Hello, I was trying to get a collision detection working in Monogame and I am not entirely sure how to achieve it.

I know how to check for collision based on rectangle bounds of sprites, but if I want to check using more complex shapes other than rectangles (given by the pixel map of .png files) how can that be achieved?

Any ideas or pointers would be greatly appreciated.

Well if you mean 2D sprites and want pixel perfect collision detection then this is my code;

        public bool IsTouching(IScratchSprite sprite1, IScratchSprite sprite2)
    {
        if (sprite1.Visible && sprite2.Visible)
        {
            if (((ScratchEffects)sprite1.Effects).CheckTexture(sprite1.CurrentCostumeIndex) || ((ScratchEffects)sprite2.Effects).CheckTexture(sprite2.CurrentCostumeIndex))
            {
                UpdateSpriteEffectTextures();// may be a different costume so need to update now to get correct bounding rects
            }
            CostumeResource sprite1costume = (CostumeResource)sprite1.Costumes.GetCostume(sprite1.CurrentCostumeIndex);
            Matrix sprite1transform = GetSpriteTransform(sprite1, sprite1costume);
            Rectangle boundingRect1 = CalculateBoundingRectangle(new Rectangle(0, 0, sprite1costume.texture.Width, sprite1costume.texture.Height), sprite1transform);
            CostumeResource sprite2costume = (CostumeResource)sprite2.Costumes.GetCostume(sprite2.CurrentCostumeIndex);
            Matrix sprite2transform = GetSpriteTransform(sprite2, sprite2costume);
            Rectangle boundingRect2 = CalculateBoundingRectangle(new Rectangle(0, 0, sprite2costume.texture.Width, sprite2costume.texture.Height), sprite2transform);
            Rectangle intersection = Rectangle.Intersect(boundingRect1, boundingRect2);
            if (!intersection.IsEmpty)
            {
                Rectangle sprite1intersection = IntersectionOnSprite(intersection, new Rectangle(0, 0, sprite1costume.texture.Width, sprite1costume.texture.Height), sprite1transform);
                if (sprite1intersection.IsEmpty)
                    return false;
                UpdateSpriteEffectTextures();
                Color[] texture1Data = new Color[sprite1costume.texture.Width * sprite1costume.texture.Height];
                ScratchEffects se1 = (ScratchEffects)sprite1.Effects;
                se1.GetEffectTexture(sprite1costume).GetData(texture1Data);
                Color[] texture2Data = new Color[sprite2costume.texture.Width * sprite2costume.texture.Height];
                ScratchEffects se2 = (ScratchEffects)sprite2.Effects;
                se2.GetEffectTexture(sprite2costume).GetData(texture2Data);
                return IntersectPixels(sprite1transform, sprite1costume.texture.Width, sprite1costume.texture.Height, texture1Data, sprite1intersection,
                    sprite2transform, sprite2costume.texture.Width, sprite2costume.texture.Height, texture2Data);
            }
            return false;
        }
        return false;
    }


   Rectangle IntersectionOnSprite(Rectangle intersection, Rectangle bounds, Matrix transform)
    {
        Matrix revert = Matrix.Invert(transform);// convert back to sprite coords
        Rectangle intersect2 =  CalculateBoundingRectangle(intersection, revert);
        intersect2 = Rectangle.Intersect(bounds,intersect2);// clip to sprite bounds
        return intersect2;
    }

    public Matrix GetSpriteTransform(IScratchSprite sprite, CostumeResource spritecostume)
    {
        Matrix SpriteTransform;
        switch (sprite.RotationStyleType)
        {
            case RotationType.All_Around:
                SpriteTransform = 
                Matrix.CreateTranslation(new Vector3(-spritecostume.ScratchRotationCentreX, -spritecostume.ScratchRotationCentreY, 0.0f)) *
                Matrix.CreateScale(sprite.Scale) * 
                Matrix.CreateRotationZ(sprite.Direction * (float)Math.PI / 180f - (float)Math.PI / 2f) *
                Matrix.CreateTranslation(new Vector3(sprite.ScratchX + 240f, -sprite.ScratchY + 180f, 0.0f));
                break;
            case RotationType.Dont_Rotate:
                SpriteTransform =
                Matrix.CreateTranslation(new Vector3(-spritecostume.ScratchRotationCentreX, -spritecostume.ScratchRotationCentreY, 0.0f)) *
                Matrix.CreateScale(sprite.Scale) *
                Matrix.CreateTranslation(new Vector3(sprite.ScratchX + 240f, -sprite.ScratchY + 180f, 0.0f));
                break;
            case RotationType.Left_Right:
                if (sprite.Direction % 180 >= 0)
                {
                    SpriteTransform =
                    Matrix.CreateTranslation(new Vector3(-spritecostume.ScratchRotationCentreX, -spritecostume.ScratchRotationCentreY, 0.0f)) *
                    Matrix.CreateScale(sprite.Scale) *
                    Matrix.CreateTranslation(new Vector3(sprite.ScratchX + 240f, -sprite.ScratchY + 180f, 0.0f));
                }
                else
                {
                    SpriteTransform =
                    new Matrix(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) *
                    Matrix.CreateTranslation(new Vector3(spritecostume.ScratchRotationCentreX, -spritecostume.ScratchRotationCentreY, 0.0f)) *
                    Matrix.CreateScale(sprite.Scale) *
                    Matrix.CreateTranslation(new Vector3(sprite.ScratchX + 240f, -sprite.ScratchY + 180f, 0.0f));
                }
                break;
            default:
                SpriteTransform = Matrix.Identity;
                break;
        }
        return SpriteTransform;
    }


    public Rectangle CalculateBoundingRectangle(Rectangle rectangle, Matrix transform)
    {
        // Get all four corners in local space
        Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
        Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
        Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
        Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);

        // Transform all four corners into work space
        Vector2.Transform(ref leftTop, ref transform, out leftTop);
        Vector2.Transform(ref rightTop, ref transform, out rightTop);
        Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
        Vector2.Transform(ref rightBottom, ref transform, out rightBottom);

        // Find the minimum and maximum extents of the rectangle in world space
        Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
                                  Vector2.Min(leftBottom, rightBottom));
        Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
                                  Vector2.Max(leftBottom, rightBottom));

        // Return as a rectangle
        return new Rectangle((int)min.X, (int)min.Y,
                             (int)(max.X - min.X), (int)(max.Y - min.Y));
    }	



    bool IntersectPixels(
                    Matrix transformA, int widthA, int heightA, Color[] dataA,
                    Rectangle intersection,
                    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(new Vector2(intersection.Left,intersection.Top), transformAToB);

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

            // For each pixel in this row
            for (int xA = intersection.Left; xA < intersection.Right; xA++)
            {
                Color colorA = dataA[xA + yA * widthA];
                if (colorA.A != 0)
                {
                    // 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 colorB = dataB[xB + yB * widthB];

                        // If both pixels are not completely transparent,
                        if (colorB.A != 0)
                        {
                            // then an intersection has been found
                            return true;
                        }
                    }
                }
                // Move to the next pixel in the row
                posInB += stepX;
            }

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

        // No intersection found
        return false;
    }

I think that just about covers it! Hopefully you can see that I am comparing 2 sprites and that the sprites can have many costumes (Textures or png’s if you prefer) and also graphical effects can be applied to the textures and also sprites can be scaled and have various rotation styles…

1 Like

thanks a bunch!

those sprite functionality are really useful!