Пиксельная проблема столкновения в C # - PullRequest
1 голос
/ 27 апреля 2009

Я пишу небольшой двумерный игровой движок на C # для своих собственных целей, и он отлично работает, за исключением обнаружения столкновений спрайтов. Я решил сделать это для каждого пикселя обнаружения (проще всего для меня реализовать), но это не работает так, как должно. Код обнаруживает столкновение задолго до того, как это произойдет. Я проверил каждый компонент обнаружения, но не могу найти проблему.

Метод обнаружения столкновений:

public static bool CheckForCollision(Sprite s1, Sprite s2, bool perpixel) {
    if(!perpixel) {
        return s1.CollisionBox.IntersectsWith(s2.CollisionBox);
    }
    else {
        Rectangle rect;
        Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
        int posx1 = rect.X;
        int posy1 = rect.Y;

        Image img2 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s2.Image, s2.Position, s2.Origin, s2.Rotation, out rect), s2.Scale);
        int posx2 = rect.X;
        int posy2 = rect.Y;

        Rectangle abounds = new Rectangle(posx1, posy1, (int)img1.Width, (int)img1.Height);
        Rectangle bbounds = new Rectangle(posx2, posy2, (int)img2.Width, (int)img2.Height);

        if(Utilities.RectangleIntersects(abounds, bbounds)) {

            uint[] bitsA = s1.GetPixelData(false);

            uint[] bitsB = s2.GetPixelData(false);

            int x1 = Math.Max(abounds.X, bbounds.X);
            int x2 = Math.Min(abounds.X + abounds.Width, bbounds.X + bbounds.Width);

            int y1 = Math.Max(abounds.Y, bbounds.Y);
            int y2 = Math.Min(abounds.Y + abounds.Height, bbounds.Y + bbounds.Height);

            for(int y = y1; y < y2; ++y) {
                for(int x = x1; x < x2; ++x) {
                    if(((bitsA[(x - abounds.X) + (y - abounds.Y) * abounds.Width] & 0xFF000000) >> 24) > 20 &&
                        ((bitsB[(x - bbounds.X) + (y - bbounds.Y) * bbounds.Width] & 0xFF000000) >> 24) > 20)
                        return true;
                }
            }
        }
        return false;
    }
}

Способ поворота изображения:

internal static Image RotateImagePoint(Image img, Vector pos, Vector orig, double rotation, out Rectangle sz) {
    if(!(new Rectangle(new Point(0), img.Size).Contains(new Point((int)orig.X, (int)orig.Y))))
        Console.WriteLine("Origin point is not present in image bound; unwanted cropping might occur");
    rotation = (double)ra_de((double)rotation);
    sz = GetRotateDimensions((int)pos.X, (int)pos.Y, img.Width, img.Height, rotation, false);
    Bitmap bmp = new Bitmap(sz.Width, sz.Height);
    Graphics g = Graphics.FromImage(bmp);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
    g.RotateTransform((float)rotation);
    g.TranslateTransform(sz.Width / 2, sz.Height / 2, MatrixOrder.Append);
    g.DrawImage(img, (float)-orig.X, (float)-orig.Y);
    g.Dispose();
    return bmp;
}       
internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
    Rectangle sz = new Rectangle();
    if (Crop == true) {
        // absolute trig values goes for all angles
        double dera = de_ra(rotation);
        double sin = Math.Abs(Math.Sin(dera));
        double cos = Math.Abs(Math.Cos(dera));
        // general trig rules:
        // length(adjacent) = cos(theta) * length(hypotenuse)
        // length(opposite) = sin(theta) * length(hypotenuse)
        // applied width = lo(img height) + la(img width)
        sz.Width = (int)(sin * imgheight + cos * imgwidth);
        // applied height = lo(img width) + la(img height)
        sz.Height = (int)(sin * imgwidth + cos * imgheight);
    }
    else {
        // get image diagonal to fit any rotation (w & h =diagonal)
        sz.X = imgx - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
        sz.Y = imgy - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
        sz.Width = (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0)) * 2;
        sz.Height = sz.Width;

    }
    return sz;
}

Способ получения пикселей:

public uint[] GetPixelData(bool useBaseImage) {
    Rectangle rect;
    Image image;
    if (useBaseImage)
        image = Image;
    else
        image = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(Image, Position, Origin, Rotation, out rect), Scale);

    BitmapData data;
    try {
        data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    }
    catch (ArgumentException) {
        data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);
    }

    byte[] rawdata = new byte[data.Stride * image.Height];
    Marshal.Copy(data.Scan0, rawdata, 0, data.Stride * image.Height);
    ((Bitmap)image).UnlockBits(data);
    int pixelsize = 4;
    if (data.PixelFormat == PixelFormat.Format24bppRgb)
        pixelsize = 3;
    else if (data.PixelFormat == PixelFormat.Format32bppArgb || data.PixelFormat == PixelFormat.Format32bppRgb)
        pixelsize = 4;

    double intdatasize = Math.Ceiling((double)rawdata.Length / pixelsize);
    uint[] intdata = new uint[(int)intdatasize];

    Buffer.BlockCopy(rawdata, 0, intdata, 0, rawdata.Length);

    return intdata;
} 

Пиксельный метод поиска работает, и метод вращения работает также, поэтому единственное место, где код может быть неправильным, это код обнаружения столкновения, но я действительно не знаю, где может быть проблема.

Ответы [ 3 ]

1 голос
/ 02 мая 2009

Я думаю, что нашел вашу проблему.

internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
    Rectangle sz = new Rectangle(); // <-- Default constructed rect here.
    if (Crop == true) {
            // absolute trig values goes for all angles
            double dera = de_ra(rotation);
            double sin = Math.Abs(Math.Sin(dera));
            double cos = Math.Abs(Math.Cos(dera));
            // general trig rules:
            // length(adjacent) = cos(theta) * length(hypotenuse)
            // length(opposite) = sin(theta) * length(hypotenuse)
            // applied width = lo(img height) + la(img width)
            sz.Width = (int)(sin * imgheight + cos * imgwidth);
            // applied height = lo(img width) + la(img height)
            sz.Height = (int)(sin * imgwidth + cos * imgheight);

            // <-- Never gets the X & Y assigned !!!
    }

Поскольку вы никогда не назначали imgx и imgy для координат X и Y прямоугольника, каждый вызов GetRotateDimensions будет создавать прямоугольник с одним и тем же местоположением. Они могут иметь разные размеры, но они всегда будут в положении по умолчанию X, Y. Это может привести к действительно ранним столкновениям, которые вы видите, потому что всякий раз, когда вы пытаетесь обнаружить столкновения на двух спрайтах, GetRotateDimensions помещает свои границы в одну и ту же позицию независимо от того, где они на самом деле находятся.

Как только вы исправили эту проблему, вы можете столкнуться с другой ошибкой:

Rectangle rect;
Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
// <-- Should resize rect here.
int posx1 = rect.X;
int posy1 = rect.Y;

Вы получаете прямоугольник границы с помощью функции RotateImagePoint, но затем изменяете размер изображения. X и Y в прямоугольнике, вероятно, не совсем совпадают с границами измененных размеров изображения. Я предполагаю, что вы имеете в виду, что центр изображения остается на месте, в то время как все точки сужаются или расширяются от центра при изменении размера. Если это так, то вам нужно изменить размеры прямоугольника и изображения, чтобы получить правильную позицию.

1 голос
/ 27 апреля 2009

Я не думаю, что многие люди здесь будут тщательно изучать ваш код, чтобы выяснить, что именно не так. Но я могу прийти с некоторыми подсказками, как вы можете найти проблему.

Если столкновение происходит задолго до того, как предполагается, я полагаю, что ваш ограничивающий флажок не работает должным образом.

Я бы изменил код, чтобы выгрузить все данные о прямоугольниках при столкновении. Таким образом, вы можете создать некоторый код, который будет отображать ситуацию при столкновении. Это может быть проще, чем просматривать цифры.

Кроме того, я сомневаюсь, что обнаружение столкновений на пиксель проще для вас. Когда вы учитываете вращение и масштабирование, это быстро становится трудно понять правильно. Вместо этого я бы сделал обнаружение столкновений на основе полигонов.

Я сделал свой собственный 2D-движок, как вы, но я использовал обнаружение столкновений на основе полигонов, и это работало нормально.

0 голосов
/ 27 апреля 2009

Я сомневаюсь, что это реальная проблема, но LockBits не гарантирует, что данные битов выровнены по ширине изображения.

Т.е., может быть некоторая прокладка. Вам необходимо получить доступ к изображению, используя data[x + y * stride], а не data[x + y * width]. Страйд также является частью BitmapData.

...