How to Get Texture Screen Coordinates Data

Started by
1 comment, last by mCray 11 years, 2 months ago

I'm trying to create pixel perfect 2D collision detection using DirectX LockRectangle method. The following thing are the ones that I've already been able to do:

1.Locking a texture and reading its bit data.

2.Plotting texture's bit data into 2D array.

Ofcourse the data that I get is not transformed to screen coordinates (without rotation, scaling, etc.). I understand the theory that should let me create pixel perfect collision but what I don't know is how to get transformed bit data. Is it possible to do it with directX?

[C# code would be appreciated.]

Advertisement

This is just an idea, I haven't implemented it or anything.

First, you transform your sprite however you need to do, (rotation, scaling).
Second, you get the pixel data of the transformed sprite.

Next, I assume you're using some sort of 2D camera, which means you should have a viewport. From there, you can translate each pixel by the distance between your sprite's center and the left and top of your viewport, and add that distance to each pixel. (minus half width/height)

That should put you in the same screen location as a translate would.

Again, I haven't done this, just some ideas to try.

Perception is when one imagination clashes with another

This is just an idea, I haven't implemented it or anything.

First, you transform your sprite however you need to do, (rotation, scaling).
Second, you get the pixel data of the transformed sprite.

Next, I assume you're using some sort of 2D camera, which means you should have a viewport. From there, you can translate each pixel by the distance between your sprite's center and the left and top of your viewport, and add that distance to each pixel. (minus half width/height)

That should put you in the same screen location as a translate would.

Again, I haven't done this, just some ideas to try.

The problem is that I'm using a square vertexbuffer with a texture on it and I am not sure if I have enough time to redevelop my engine to use sprites at this point but thanks for suggestions(I'm doing this for a college project so I have limited amount of time).

In the mean time I did sort of found a workaround, I don't like it but it should suffice for now.

1.First I load the bit data of the texture in the in the center of the 2D array. That array has width and height the size of the texture's hypotenuse which is the largest possible size that it can have no matter how it is rotated.


//Creates collision mask from a texture. Collision mask is used to evaluate pixel perfect collision.
        public unsafe static void GenerateCollisionMask(eTexture e)
        {
            //Gets texture information like its width and height. 
            SurfaceDescription s = TextureList[(int)e].Texture.GetLevelDescription(0);

            //Creates a pointer to the texture's colour bit data.
            uint* pData = (uint*)TextureList[(int)e].Texture.LockRectangle(0, LockFlags.None).InternalData.ToPointer();

            //Calculates the hypotenuse of the texture. Hypotenuse is the longest possible width or height
            //that texture can have after rotation. I use it to set up an array of the right size.
            int htn = (int)Math.Sqrt(Math.Pow(s.Width, 2) + Math.Pow(s.Height, 2));

            //This arrey that will store texture's opaque pixels.
            byte[,] tmp = new byte[htn, htn];

            //I want to place texture's data in the center of the array which is larger then
            //the texture, so I use these variables to offset pixels.
            int x_offset = (htn / 2) - (s.Width / 2);
            int y_offset = (htn / 2) - (s.Height / 2);

            //Loops though the array and stores opaque pixels. Note that the array starts at the offset.
            for (int y = y_offset; y < y_offset + s.Height; y++)
            {
                for (int x = x_offset; x < x_offset + s.Width; x++)
                {
                    //'*pData' contains uint which has an ARGB value of the pixel which is what this variable contains.
                    Color c = Color.FromArgb((int)*pData);

                    //If alpha value of 'c' is 0 than that pixel is opaque therefore that array location is set to 2.
                    if (c.A != 0)
                    {
                        tmp[x, y] = 2;
                    }
                    //else it is not opaque and it is set to 1
                    else
                    {
                        tmp[x, y] = 1;
                    }
                    //This line moves pData to the next set of ARGB value.
                    pData++;
                }
            }

            //After opaque pixels are plotted in the tmp array I save the reference of tmp into another array.
            CollisionMasks[(int)e] = tmp;
            //Unlocks the texture so that it could be accessed by other processes.
            TextureList[(int)e].Texture.UnlockRectangle(0);
        }

2. I rotate the source array and plot its data into another array (destination array) which is the true mask of the object.


   //Rotates collision mask so that it would match rotated texture on the screen.
    private unsafe void UpdateCollisionMask()
    {
        //Gets texture information like its width and height. 
        SurfaceDescription sd = Methods.TextureList[(int)eTexture.Tank].Texture.GetLevelDescription(0);
        //Gets a reference to the source array where original(untransformed) mask is located.
        byte[,] source = Methods.CollisionMasks[(int)eTexture.Tank];

        //Aligns the angle to match object's angle on the screen.
        int a = Angle + 90;
        if (a >= 360) { a -= 360; }

        //This list of points is used to find the height and width of the texture after it is rotated
        //So that I could create an array which isn't larger then necessary.
        List<Point> Points = new List<Point>();

        //point 0 is the rotated top left corner of the texture.
        Points.Add(new Point((int)Methods.RotatePointX(a, 0, 0, 0, 0),
            (int)Methods.RotatePointY(a, 0, 0, 0, 0)));
        //point 1 is the rotated top right corner of the texture.
        Points.Add(new Point((int)Methods.RotatePointX(a, sd.Width, 0, 0, 0),
            (int)Methods.RotatePointY(a, sd.Width, 0, 0, 0)));
        //point 2 is the rotated bottom left corner of the texture.
        Points.Add(new Point((int)Methods.RotatePointX(a, 0, sd.Height, 0, 0),
            (int)Methods.RotatePointY(a, 0, sd.Height, 0, 0)));
        //point 3 is the rotated bottom right corner of the texture.
        Points.Add(new Point((int)Methods.RotatePointX(a, sd.Width, sd.Height, 0, 0),
            (int)Methods.RotatePointY(a, sd.Width, sd.Height, 0, 0)));

        //these variables will contain the width and height of the destination array.
        int dest_w = 0;
        int dest_h = 0;

        //Finds the height and width of the source array after it is rotated.
        if (Points[0].X <= Points[1].X && Points[0].X <= Points[2].X || Points[0].X >= Points[1].X && Points[0].X >= Points[2].X)
        {
            dest_w = Math.Abs(Points[0].X - Points[3].X); 
            dest_h = Math.Abs(Points[1].Y - Points[2].Y);
        }
        else if (Points[0].Y >= Points[1].Y && Points[0].Y >= Points[2].Y || Points[0].Y <= Points[1].Y && Points[0].Y <= Points[2].Y)
        {
            dest_w = Math.Abs(Points[1].X - Points[2].X);
            dest_h = Math.Abs(Points[0].Y - Points[3].Y);
        }

        //This array will store the rotated source array.
        byte[,] dest = new byte[dest_w + 1, dest_h + 1];

        //Calculates the hypotenuse of the texture
        int src_htn = (int)Math.Sqrt(Math.Pow(sd.Width, 2) + Math.Pow(sd.Height, 2));

The variables 'xs', 'ys', 'xm', 'ym' is pretty much the only reason I don't like this method. The problem is that I don't understand why it works this way. Both of them have practically the same values as far as I can say and yet somehow I have to use one set of these in one part of the loop (seen below) and another set of these in another part of the loop or else I am either getting "index is outside the bounds of the array" error or part of the source data is not retrieved and saved in the destination array... The funny thing is that this was my personal addition to the code that I found online, it works and yet I don't know why.


        //Calculates the center point of the texture.
        int xs = (src_htn / 2) - (sd.Width / 2);
        int ys = (src_htn / 2) - (sd.Height / 2);

        //Calculates the center point of the texture.
        int xm = Math.Abs(src_htn - dest_w) / 2;
        int ym = Math.Abs(src_htn - dest_h) / 2;

        //Copies and rotates source array into the destination array
        for (int y = 0; y < src_htn; y++)
        {
            for (int x = 0; x < src_htn; x++)
            {
                //Takes the x and y of the destination and rotates it
                int px = (int)Methods.RotatePointX(a, x, y, src_htn, src_htn);
                int py = (int)Methods.RotatePointY(a, x, y, src_htn, src_htn);

                //if rotated destination's x and y is not outside the bounds of the source array then
                if (px >= 0 && px < xs + sd.Width && py > 0 && py < ys + sd.Height && source[px,py] == 2)
                {
                    //Set the destination x and y array location to the value of source px and py array location.
                    //xm and ym offsets source pixels so that there wouldn be any unnecessary space to
                    //the left and up side of the array.
                    dest[x - xm, y - ym] = source[px, py];
                }
            }
        }

        #region "troubleshooting"
        /////////////////////////////////////////
        for (int x = 0; x < dest_w + 1; x++)
        {
            dest[x, 0] = 2;
            dest[x, dest_h] = 2;
        }

        for (int y = 0; y < dest_h + 1; y++)
        {
            dest[0, y] = 2;
            dest[dest_w, y] = 2;
        }

        SurfaceDescription s = Methods.TextureList[(int)eTexture.a].Texture.GetLevelDescription(0);

        uint* pData = (uint*)Methods.TextureList[(int)eTexture.a].Texture.LockRectangle(0, LockFlags.None).InternalData.ToPointer();

        for (int y = 0; y < s.Height; y++)
        {
            for (int x = 0; x < s.Width; x++)
            {
                if (x < dest_w + 1 && y < dest_h + 1)
                {
                    if (dest[x, y] == 2) { *pData = (uint)Color.Black.ToArgb(); }
                    if (dest[x, y] == 1) { *pData = (uint)Color.Red.ToArgb(); }
                    if (dest[x, y] == 0) { *pData = (uint)Color.Red.ToArgb(); }
                }
                else
                {
                    *pData = (uint)Color.Red.ToArgb();
                }
                pData++;
            }
        }
        Methods.TextureList[(int)eTexture.a].Texture.UnlockRectangle(0);
        ////////////////////////////////////////
        #endregion

This topic is closed to new replies.

Advertisement