【问题标题】:Comparing two 2D Matrix's for Unique Points比较两个 2D 矩阵的唯一点
【发布时间】:2011-11-01 13:48:04
【问题描述】:

我目前正在开发一款游戏,我将让玩家穿越 2D 矩阵。作为渲染过程的一部分,我希望能够根据玩家的位置修改玩家周围的环境。

基本上,玩家将在 x,y 点,并且始终有 2 个点在他周围。玩家可能会向任何方向移动 x,y 点,我需要知道不再需要的旧指针是什么,以及玩家接近的新点。

我已经为我的意思绘制了一张简图:

我需要一个旧点(红色)和新点(绿色)的列表,我可以对其进行迭代以执行操作。

我将用 C++ 编写这个方法,所以我真的在寻找实现这一点所需的 sudo 逻辑步骤。我通过自己的方式完成了大约 50%,但我认为这完全是低效的,我也相信这是一种简单的数学方法。

【问题讨论】:

  • 你现在用什么方法?
  • 有一种非常快的方法,它使用简单的算术,我看看能不能快速汇总一下。

标签: c++ math matrix


【解决方案1】:

可能最有效的方法是计算矩形-矩形减法,这在很多情况下可能看起来很困难,但实际上并不那么难:

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;
}

上面的函数给定两个矩形AB,返回一个矩形向量,结果为A-B。这个向量可能是空的(B 覆盖A)或者可能有一到四个矩形(四个是当B 严格包含在A 中时,因此结果将是一个带有矩形孔的矩形)。

使用此函数,您可以轻松计算new-oldold-new 面积。

请注意,上述代码中使用的坐标模式假定基于点的坐标系(不是基于像素的坐标系):

请注意,在上图中,矩形的水平 X 坐标从 0 到 W(不是 W-1),垂直 Y 坐标从 0 到 H(而不是 H-1)。

像素只是区域 1 坐标为(x, y)-(x+1, y+1) 的矩形;这个像素的中心是(x+0.5, y+0.5)。带有x0==x1y0==y1 的矩形是空的。

另请注意,代码假定(并返回)非空方向的矩形,即x0&lt;x1 &amp;&amp; y0&lt;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)

【讨论】:

  • 仅供参考,这并不完全正确 - 如果两个框由于
  • @S.Richmond:不,代码是正确的;只是它基于点坐标系而不是像素坐标系。我已经添加了解释。在处理像素精确的渲染时,点坐标系统要好得多,并且可以避免特殊情况和代码中-1+1 的需要。
  • 啊!好吧,这更有意义!由于我使用的是像素坐标系统,因此我的代码遇到了很多麻烦。我面临的问题是如何正确循环返回的坐标。例如,我得到 (4,4)(5,8) 返回。这是 1 行,每行 5 个“点”。但是我想不出一种方法来循环而不是得到 2 行。 For 循环:for(int x = block-&gt;x0; x &lt;= block-&gt;x1; x++) { for(int y = block-&gt;y0; y &lt;= block-&gt;y1; y++) { } }
  • 使用点坐标系必须从 x0 循环到 x1 排除...例如for (int y=r.y0; y&lt;r.y1; y++) { for (int x=r.x0; x&lt;r.x1; x++) { ... } }
  • 这就是我已经得到的。内部 for 循环不会总是失败,因为 x
【解决方案2】:

我已经重新编写了我的原始答案,因此它涵盖了所有情况,并希望更有意义:(它是 php 而不是 C++,但像这样写出来比写出 sudo-logic 更容易。希望它现在会更好地解释自己。)

一个矩形(在这种情况下是正方形)可以用它的左下角和右上角的坐标来表示。对于这个例子,$Rectangle[0] 是左下角坐标,$Rectangle[0]['x'] 是左下角坐标的 x 值,$Rectangle[0]['y'] 是 y 值的左下角坐标。 $Rectangle[1] 是右上角的坐标。

这可以修改为仅处理正方形以仅保存一个顶点坐标和边长。

有四种移动方式:
1:没有移动,方块在同一个地方。在这种情况下,绿色矩形的所有四个角点都将位于蓝色矩形内。
2:发生了足够的运动,导致两个方格之间没有重叠。在这种情况下,绿色矩形的四个角点都不会在蓝色矩形内。
3:仅在一个方向(x 或 y)发生了运动,并且距离不足以消除整个一圈。在这种情况下,绿色矩形的四个角点中的 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;
    }

【讨论】:

  • 其实这并没有考虑到所有的可能性。不过,这是一种方法,您可以多考虑​​一下以使其涵盖所有这些。不过矩形减法看起来更好。
  • 是的,看起来不错,但老实说我有点困惑。谢谢你的帮助。 :)
  • 基本上它将非重叠区域拆分为矩形并迭代其中的点。除非它不适用于所有情况。
【解决方案3】:

这是输出红色和绿色不重叠点的通用方法。调用函数 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 时存在错误——现已修复。

【讨论】:

  • 澄清一下,使用上述代码可以:find_points(pRed, pGreen, red_box, green_box);完成了吗?
  • 刚刚将代码放入并开始使用它,但我注意到“Points”不是实际类型。你希望我用我使用的任何东西来解决这个问题吗?
  • 是的,点数将是您想要保存点数的任何数据结构。
  • Hrm 这似乎没有按预期工作。使用 box1 和 box2 作为 ((4,4) (8,8)) 执行上述代码会导致所有点在新旧点都被识别。我现在正在调试它以尝试解决它。​​
  • 糟糕!计算 wy2 时出现错误。现在已经修复了。
【解决方案4】:
  1. 列出旧点(红色)。
  2. 列出新点(绿色)。
  3. 遍历一个列表。对于每个点,在另一个列表中查找它,如果它在那里,从两个列表中删除它。

编辑:

让我换个说法:

  1. 列出旧社区中的点(蓝色边框内)。
  2. 列出新邻域中的点(在绿色边界内)。
  3. 遍历一个列表。对于每个点,在另一个列表中查找它,如果它在那里,从两个列表中删除它。

您将看到不再可见的点(红色)和新可见的点(绿色)的列表。

【讨论】:

  • 问题是根据两个区域(蓝色和绿色方块)找到红色和绿色部分。换句话说,我还不能采取步骤 1 或 2。
猜你喜欢
  • 2016-07-31
  • 1970-01-01
  • 2017-10-27
  • 1970-01-01
  • 2010-10-30
  • 1970-01-01
  • 2012-03-08
  • 1970-01-01
  • 2014-02-12
相关资源
最近更新 更多