XNA Rotated PerPixel Collision

Tuesday, January 05 2010 -

Yesterday I posted a quick post on a small project I was playing with (Link), and today I saw a post on the XNA Forums where a user was having problems with rotated rectangles and collision. So to help here is the ruff code that i have been playing with, hopefully it will help some one with their project, ATM it is not really complete and i am not fully happy with it. Currently the system slows down the game when I am doing the check on over 300 objects.

A lot of this code is based on the Samples from the Creators Site.

Code for Rotated PerPixel Collision.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace TranslatedSpriteCollisions
{
    public class GameObject
    {
        private Texture2D texture;
        private Color[] colorData;
        private Matrix transform;
        public Vector2 Position;
        public Vector2 Origin;
        public Vector2 Velocity;
        public float Rotation;
        public float Scale = 1.0f;
        public Texture2D Texture
        {
            get { return texture; }
            set
            {
                texture = value;
                colorData = new Color[texture.Width * texture.Height];
                texture.GetData(colorData);
                this.Origin = new Vector2(texture.Width / 2, texture.Height / 2);
            }
        }
        public Matrix Transform
        {
            get { return transform; }
        }
        public void UpdateTransform()
        {
            transform = Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
                        Matrix.CreateScale(Scale) *
                        Matrix.CreateRotationZ(Rotation) *
                        Matrix.CreateTranslation(new Vector3(Position, 0.0f));
        }
        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(Texture, Position, null, Color.White, Rotation, Origin,
                Scale, SpriteEffects.None, 0.0f);
        }
        public bool IntersectPixels(GameObject b)
        {
            return IntersectPixels(transform, texture.Width, texture.Height, colorData,
                           b.transform, b.texture.Width, b.texture.Height, b.colorData);
        }
        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels between two
        /// sprites.
        /// </summary>
        /// <param name="transformA">World transform of the first sprite.</param>
        /// <param name="widthA">Width of the first sprite's texture.</param>
        /// <param name="heightA">Height of the first sprite's texture.</param>
        /// <param name="dataA">Pixel color data of the first sprite.</param>
        /// <param name="transformB">World transform of the second sprite.</param>
        /// <param name="widthB">Width of the second sprite's texture.</param>
        /// <param name="heightB">Height of the second sprite's texture.</param>
        /// <param name="dataB">Pixel color data of the second sprite.</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        public static bool IntersectPixelsSlow(
            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);
            // For each row of pixels in A
            for (int yA = 0; yA < heightA; yA++)
            {
                // For each pixel in this row
                for (int xA = 0; xA < widthA; xA++)
                {
                    // Calculate this pixel's location in B
                    Vector2 positionInB =
                        Vector2.Transform(new Vector2(xA, yA), transformAToB);
                    // Round to the nearest pixel
                    int xB = (int)Math.Round(positionInB.X);
                    int yB = (int)Math.Round(positionInB.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
                            return true;
                        }
                    }
                }
            }
            // No intersection found
            return false;
        }
        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels between two
        /// sprites.
        /// </summary>
        /// <param name="transformA">World transform of the first sprite.</param>
        /// <param name="widthA">Width of the first sprite's texture.</param>
        /// <param name="heightA">Height of the first sprite's texture.</param>
        /// <param name="dataA">Pixel color data of the first sprite.</param>
        /// <param name="transformB">World transform of the second sprite.</param>
        /// <param name="widthB">Width of the second sprite's texture.</param>
        /// <param name="heightB">Height of the second sprite's texture.</param>
        /// <param name="dataB">Pixel color data of the second sprite.</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        public static bool 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
                            return true;
                        }
                    }
                    // Move to the next pixel in the row
                    posInB += stepX;
                }
                // Move to the next row
                yPosInB += stepY;
            }
            // No intersection found
            return false;
        }
    }
}
kick it on DotNetKicks.com kick it on gamedevkicks.com

Similar Posts

  1. Phoenix, Adding to the Background
  2. Phoenix, Drawing Text on the Screen
  3. Getting Started with Blender 3D and XNA

1 comment(s)

It's unlikely that you have to test all 300 of those objects at a per-pixel level. Check to see if the rotated bounding rectangles of the objects intersect first. I bet Graphics Gems has an algorithm to do that efficiently.

You could even cut that work down if you code something like "if the centers of the two objects are more than x pixels apart, they can't be overlapping no matter how they're rotated".

Check you skip are checks that can't slow your code.

Clicky Web Analytics