Сравнение двух 2D матриц для уникальных точек - PullRequest
2 голосов
/ 01 ноября 2011

В настоящее время я работаю над игрой, в которой у меня будет игрок, проходящий 2D-матрицу. Я хочу иметь возможность изменять окружение игрока в зависимости от его местоположения, как часть процесса рендеринга.

По сути, игрок будет находиться в точке x, y и всегда будет иметь 2 очка. Игрок может перемещать точки x, y в любом направлении, и мне нужно знать, каковы старые ненужные указатели, а также новые точки, близкие игроку.

Я составил краткую диаграмму того, что я имею в виду:

Diagram of Matrix's

Мне нужен список старых точек (красным) и новых точек (зеленым), которые я могу перебрать для выполнения действий.

Я напишу этот метод на C ++, поэтому я действительно ищу шаги sudo-logic, необходимые для того, чтобы это произошло. Я на 50% обошел свой собственный способ сделать это, но я считаю, что это совершенно неэффективно, и я также считаю, что это простой математический способ сделать это.

Ответы [ 4 ]

3 голосов
/ 01 ноября 2011

Вероятно, наиболее эффективным способом было бы вычисление вычитания прямоугольника-прямоугольника, которое может показаться сложным и во многих случаях, но на самом деле это не так сложно:

struct Rect{
    int x0, y0, x1, y1;
    Rect(int x0, int y0, int x1, int y1)
        : x0(x0), y0(y0), x1(x1), y1(y1)
    {}
};

std::vector<Rect> subtract(const Rect& a, const Rect& b) {
    std::vector<Rect> result;
    if (a.y1 <= b.y0 || a.y0 >= b.y1 || a.x1 <= b.x0 || a.x0 >= b.x1) {
        // Trivial case: rectangles are not overlapping
        result.push_back(a);
    } else {
        int ystart = a.y0, yend = a.y1;
        if (ystart < b.y0) { // Something visible above
            result.push_back(Rect(a.x0, ystart, a.x1, b.y0));
            ystart = b.y0;
        }
        if (yend > b.y1) { // Something visible below
            result.push_back(Rect(a.x0, b.y1, a.x1, yend));
            yend = b.y1;
        }
        if (a.x0 < b.x0) { // Something visible on the left
            result.push_back(Rect(a.x0, ystart, b.x0, yend));
        }
        if (a.x1 > b.x1) { // Something visible on the right
            result.push_back(Rect(b.x1, ystart, a.x1, yend));
        }
    }
    return result;
}

Вышеупомянутая функция с двумя прямоугольниками A и B возвращает вектор прямоугольников с результатом A-B.Этот вектор может быть пустым (B охватывает A) или может иметь от одного до четырех прямоугольников (четыре - это когда B строго содержится в A, таким образом, результатом будет прямоугольник с прямоугольным отверстием в нем).

Используя эту функцию, вы можете легко вычислить new-old и old-new области.

Обратите внимание, что схема координат, используемая в приведенном выше коде, предполагает систему координат точка-база (неоснованная на пикселях система координат):

enter image description here

На рисунке выше обратите внимание, что горизонтальные координаты X прямоугольников идут от 0 до W (не W-1), а вертикальные координаты Y идут отОт 0 до H (а не до H-1).

Пиксели - это просто прямоугольники области 1 с координатами (x, y)-(x+1, y+1);центр этого пикселя (x+0.5, y+0.5).Прямоугольник с x0==x1 или y0==y1 является пустым.

Обратите также внимание, что код предполагает (и возвращает) непустые ориентированные прямоугольники, т.е. x0<x1 && y0<y1.

Этот подход разделенияКонцепция координаты пикселя из концепции координат точки упрощает большую часть математики пикселей: например, площадь прямоугольника равна width*height, а не (width-1)*(height-1).

Небольшая программа для проверки с вашим входным регистром - этоследующий

void print_result(const char *name,
                  const std::vector<Rect>& rects)
{
    printf("Result '%s' (%i rects):\n", name, int(rects.size()));
    for (int i=0,n=rects.size(); i<n; i++)
    {
        printf("  %i) (%i, %i) - (%i, %i)\n",
               i+1,
               rects[i].x0, rects[i].y0,
               rects[i].x1, rects[i].y1);
    }
}

int main()
{
    Rect A(1, 1, 6, 6);
    Rect B(3, 2, 8, 7);
    print_result("A-B", subtract(A, B));
    print_result("B-A", subtract(B, A));
    return 0;
}

и вывод этой программы

Result 'A-B' (2 rects):
  1) (1, 1) - (6, 2)
  2) (1, 2) - (3, 6)
Result 'B-A' (2 rects):
  1) (3, 6) - (8, 7)
  2) (6, 2) - (8, 6)
1 голос
/ 01 ноября 2011

Это общий подход для вывода красных и зеленых непересекающихся точек. Вызвать функцию find_points, чтобы сделать работу. Это предполагает, что у вас есть класс Points для хранения набора точек с помощью метода add (x, y) для добавления одной точки к нему.

struct Box {
  int x1; // min x value
  int x2; // max x value + 1
  int y1; // min y value
  int y2; // max y value + 1
};

Box intersection(const Box &box1,const Box &box2)
{
  int wx1 = max(box1.x1,box2.x1);
  int wx2 = min(box1.x2,box2.x2);
  int wy1 = max(box1.y1,box2.y1);
  int wy2 = min(box1.y2,box2.y2);
  Box result = {wx1,wx2,wy1,wy2};
  return result;
}

void output_box(Points &p,int x1,int x2,int y1,int y2)
{
  for (int y=y1; y!=y2; ++y) {
    for (int x=x1; x!=x2; ++x) {
      p.add(x,y);
    }
  }
}

void output_difference(Points &points,const Box &box1,const Box &box2)
{
  output_box(points,box1.x1,box1.x2,box1.y1,box2.y1);
  output_box(points,box1.x1,box2.x1,box2.y1,box2.y2);
  output_box(points,box2.x2,box1.x2,box2.y1,box2.y2);
  output_box(points,box1.x1,box1.x2,box2.y2,box1.y2);
}

void find_points(Points &red,Points &green,const Box &red_box,const Box &green_box)
{
  Box white_box = intersection(red_box,green_box);
  output_difference(red,red_box,white_box);
  output_difference(green,green_box,white_box);
}

РЕДАКТИРОВАТЬ: была ошибка в вычислении wy2 - теперь исправлено.

1 голос
/ 01 ноября 2011

Я переписал свой оригинальный ответ, чтобы он охватывал все случаи и, надеюсь, имеет немного больше смысла: (Это php, а не C ++, но проще было написать его вот так, чем писать sudo-logic. Надеюсь, это сейчас объясню немного лучше.)

Прямоугольник (или квадрат в этом случае) может быть представлен его нижней левой и верхней правой координатой. В этом примере $ Rectangle [0] - это нижняя левая координата, где $ Rectangle [0] ['x'] - это значение x нижней левой координаты, а $ Rectangle [0] ['y'] - значение y. нижней левой координаты. $ Rectangle [1] - верхняя правая координата.

Это можно изменить для чего-то, что будет иметь дело только с квадратами для хранения только одной координаты вершины и длины ребра.

Есть 4 возможности для движения:
1: Движение не произошло, квадраты находятся в одном месте. В этом случае все четыре угловые точки зеленого прямоугольника будут внутри синего прямоугольника.
2: Достаточное движение произошло, чтобы не было перекрытия между двумя квадратами. В этом случае ни одна из четырех угловых точек зеленого прямоугольника не будет находиться внутри синего прямоугольника.
3: Движение произошло только в одном направлении (х или у) и недостаточно далеко, чтобы удалить весь круг. В этом случае 2 из четырех угловых точек зеленого прямоугольника будут внутри синего прямоугольника.
4: Движение произошло в обоих направлениях x и y и недостаточно далеко, чтобы полностью устранить наложение. В этом случае одна из четырех угловых точек зеленого прямоугольника будет внутри синего прямоугольника.

Каждый случай может быть обработан немного по-разному:
1 - Вам не нужно перебирать любые квадраты.
2 - Вам нужно перебрать все квадраты в синем прямоугольнике и все квадраты в зеленом прямоугольнике.
3 - Вам нужно перебрать квадраты, определенные как:

function getOnlyOverlapRectangle($FirstRectangle, $SecondRectangle)
    {
        $PointOne['x'] = $FirstRectangle[0]['x'];
        $PointOne['y'] = $FirstRectangle[0]['y'];
        $PointTwo['x'] = $FirstRectangle[0]['x'];
        $PointTwo['y'] = $FirstRectangle[1]['y'];
        $PointThree['x'] = $FirstRectangle[1]['x'];
        $PointThree['y'] = $FirstRectangle[1]['y'];
        $PointFour['x'] = $FirstRectangle[1]['x'];
        $PointFour['y'] = $FirstRectangle[]['y'];

        //left edge
        if(checkVertexInside($PointOne,$SecondRectangle) && checkVertexInside($PointTwo,$SecondRectangle))
        {
            $overlapRectangle[0]['x'] = $SecondRectangle[1]['x'];
        }
        else
        {
            $overlapRectangle[0]['x'] = $FirstRectangle[0]['x'];
        }

        //bottom edge
        if(checkVertexInside($PointOne,$SecondRectangle) && checkVertexInside($PointFour,$SecondRectangle))
        {
            $overlapRectangle[0]['y'] = $SecondRectangle[1]['y'];
        }
        else
        {
            $overlapRectangle[0]['y'] = $FirstRectangle[0]['y'];
        }

        //right edge
        if(checkVertexInside($PointThree,$SecondRectangle) && checkVertexInside($PointFour,$SecondRectangle))
        {
            $overlapRectangle[1]['x'] = $SecondRectangle[0]['x'];
        }
        else
        {
            $overlapRectangle[1]['x'] = $FirstRectangle[1]['x'];
        }

        //top edge
        if(checkVertexInside($PointTwo,$SecondRectangle) && checkVertexInside($PointThree,$SecondRectangle))
        {
            $overlapRectangle[1]['y'] = $SecondRectangle[0]['y'];
        }
        else
        {
            $overlapRectangle[1]['y'] = $FirstRectangle[1]['y'];
        }


        return $overlapRectangle;
    }

4 - Вам нужно перебрать квадраты, определенные как:

//Gets subset of $FirstRectangle that is outside of $SecondRectangle
function getFirstOverlapRectangle($FirstRectangle, $SecondRectangle)
    {
        //left edge
        $Point['x'] = $FirstRectangle[0]['x'];
        $Point['y'] = $FirstRectangle[1]['y'];
        if(checkVertexInside($Point,$SecondRectangle))
        {
            $overlapRectangle[0]['x'] = $SecondRectangle[1]['x'];
        }
        else
        {
            $overlapRectangle[0]['x'] = $FirstRectangle[0]['x'];
        }

        //bottom edge
        if($FirstRectangle[0]['y'] < $SecondRectangle[0]['y'] < $FirstRectangle[1]['y'])
        {
            $overlapRectangle[0]['y'] = $SecondRectangle[0]['y'];
        }
        else
        {
            $overlapRectangle[0]['y'] = $SecondRectangle[1]['y'];
        }

        //right edge
        $overlapRectangle[1]['x'] = min($FirstRectangle[1]['x'],$SecondRectangle[0]['x']);

        //top edge
        $overlapRectangle[1]['y'] = $FirstRectangle[1]['y'];

        return $overlapRectangle;
    }

    //Gets second subset of $FirstRectangle that is outside of $SecondRectangle
    function getSecondOverlapRectangle($FirstRectangle, $SecondRectangle)
    {
        //top edge
        if($FirstRectangle[0]['y'] < $SecondRectangle[0]['y'] < $FirstRectangle[1]['y'])
        {
            $overlapRectangle[1]['y'] = $SecondRectangle[0]['y'];
        }
        else
        {
            $overlapRectangle[1]['y'] = $SecondRectangle[1]['y'];
        }

        //bottom edge
        $overlapRectangle[0]['y'] = $FirstRectangle[0]['y'];

        //left edge
        $Point['x'] = $SecondRectangle[1]['x'];
        $Point['y'] = $SecondRectangle[1]['y'];
        if(checkVertexInside($Point,$FirstRectangle))
        {
            $overlapRectangle[0]['x'] = $SecondRectangle[1]['x'];
        }
        else
        {
            $overlapRectangle[0]['x'] = $FirstRectangle[0]['x'];
        }

        //right edge
        $Point['x'] = $FirstRectangle[0]['x'];
        $Point['y'] = $FirstRectangle[1]['y'];
        if(checkVertexInside($Point,$SecondRectangle))
        {
            $overlapRectangle[1]['x'] = $FirstRectangle[0]['x'];
        }
        else
        {
            $overlapRectangle[1]['x'] = $SecondRectangle[1]['x'];
        }


        return $overlapRectangle;
    }

Таким образом, возможное решение будет работать так:

Определите, сколько углов зеленого прямоугольника находится внутри синего прямоугольника. Включите этот номер:
0: перебрать все точки зеленого прямоугольника и все точки синего прямоугольника
1: перебрать результат getFirstOverlapRectangle и getSecondOverlapRectangle
2: перебрать результат getOnlyOverlapRectangle
3: этого не должно случиться ...
4: не перебирайте квадраты

Итерация должна работать примерно так:

for($CurrentXCoord = $OldSquaresOne[0]['x'] + 0.5; $CurrentXCoord < $OldSquaresOne[1]['x'];  $CurrentXCoord ++)
    {
        for($CurrentYCoord = $OldSquaresOne[0]['y'] + 0.5; $CurrentYCoord < $OldSquaresOne[1]['y'];  $CurrentYCoord ++)
        {
            //do stuff.
        }
    }

+0,5 гарантирует, что вы не пересекаете граничные точки дважды, поэтому вы указываете квадраты внутри синих / зеленых квадратов по их центральной координате, а не по угловой координате.

Функция checkVertexInside будет выглядеть примерно так:

function checkVertexInside($Point, $Rectangle)
    {
        if
        (
            $Point['x'] <= $Rectangle[1]['x'] && $Point['x'] >= $Rectangle[0]['x']
            && $Point['y'] <= $Rectangle[1]['y'] && $Point['y'] >= $Rectangle[0]['y']
        )
        {
            return true;
        }

        return false;
    }

    function getRectanglePoint($topFlag,$rightFlag,$Rectangle)
    {
        $Point['x'] = $Rectangle[$rightFlag]['x'];
        $Point['y'] = $Rectangle[$topFlag]['y'];
        return $Point;
    }
0 голосов
/ 01 ноября 2011
  1. Составьте список старых точек (красный).
  2. Составьте список новых точек (зеленый).
  3. Итерация по одному списку. Для каждой точки найдите его в другом списке и, если он есть, удалите его из обоих списков.

EDIT:

Позвольте мне перефразировать это:

  1. Составьте список точек в старом районе (внутри синей границы).
  2. Составьте список точек в новом районе (внутри зеленой границы).
  3. Итерация по одному списку. Для каждой точки найдите его в другом списке и, если он есть, удалите его из обоих списков.

У вас останется список точек, которые больше не видны (красный), и вновь видимых точек (зеленый).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...