【问题标题】:Rectangle selection algorithm矩形选择算法
【发布时间】:2020-12-08 05:26:51
【问题描述】:

矩形位于双向链表中。在这种情况下,矩形是一个图形,由矩形结构的数据组成(无论如何)。

struct rect {
    int l; /* left */
    int t; /* top */
    int r; /* right */
    int b; /* bottom */
};

它的字段有严格的位置(我保证它适用于任何矩形)。

[l; t]          [r; t]
       +------+ 
       |      |
       |      |
       +------+ 
[l; b]          [r; b]

矩形存储在对象中。

struct object {
    struct rect *rect;
    bool selected;
};

对象存储在列表的节点中。

struct node {
    struct object *obj;
    struct node *next;
    struct node *prev;
}; 

Rectangles 从列表的头部开始连续绘制——首先绘制一个矩形,靠近列表的开头。因此,每个下一个数字都与前面的数字重叠。

我选择矩形,它带有一个由鼠标光标形成的选择矩形。在从末尾迭代列表期间完成选择检查。

struct node *node = getend(list);
struct object *obj;

do {
    obj = node->obj;

    /* Check, if a selection rectangle somehow 
     * intersects with a rectangle of the current 
     * object. */
    if (rcheck(sel_rect, obj->rect))
        obj->selected = true;
}
while ((node = node->prev));

看看我的问题的演示,在下面一点。

如您所见,选择矩形选择了两个矩形:黄色和绿色。在这种情况下,只应选择黄色图形;一般来说——如果在前向矩形后面有另一个图形(后向矩形),并且选择矩形确实覆盖了这个图形的“可见”部分(在动画上它是绿色多边形,由重叠黄色图形形成),但覆盖了其“隐藏”部分,则应选择后向矩形


forward 矩形意味着它位于更靠近列表末尾的位置; 向后 矩形 - 它位于更靠近其开头的位置。

此算法用于游戏的二维地图编辑器,用于矩形纹理选择。

【问题讨论】:

  • 选择循环应该使用for (struct node *node = getend(list); node; node = node->prev) { ... }。使用do/while 循环编码时,您无法处理空列表。
  • 根据经验,将矩形描述为top, left, bottom, right 会产生比top, left, width, height 更简单的代码。

标签: c algorithm geometry selection rectangles


【解决方案1】:

五种不同的方法:

屏幕空间选择

将矩形从后到前顺序渲染到“像素”的临时数组/网格上,但使用“矩形 ID”而不是颜色。使用选择矩形选择“像素”并检查它们以确定所选矩形的“矩形 ID”。这可能是最简单的选择(并且可能性能最差)。

选择矩形细分

从前向后搜索(就像你一样);但是当你找到一个与选择矩形重叠的矩形时,使用它的边缘将选择矩形分成子矩形并丢弃与找到的矩形重叠的子矩形。这将为您留下 0 到 8 个不与找到的矩形重叠的子矩形。使用每个生成的子矩形(而不是原始选择矩形)重复此过程,以找到更多被选中的矩形。请注意(对于“最坏情况”),对于每个选定的矩形,子矩形的数量可以增加 7。

矩形列表细分

使用前面矩形的边缘对矩形列表进行预处理,以细分任何背面多边形,并丢弃任何重叠的子矩形;这样生成的矩形列表不再包含任何重叠的内容。在这之后;找到与选择矩形重叠的所有(子)矩形。请注意,如果矩形列表不经常更改并且可以多次重复使用相同的“预处理矩形列表”,这会更好。

选择多边形减法

假设选择区域是一个任意形状,有N个顶点和N个边,刚开始的时候恰好是4个顶点和4个边。从前到后搜索(就像你一样);但是当你找到一个与选择多边形重叠的矩形时,从选择多边形中减去重叠区域。这非常复杂。部分原因是选择多边形可以分成“分离/非接触”的部分和/或可以变成“甜甜圈状”。这可以与以前的方法相结合 - 例如使用重叠矩形的边缘将选择多边形分割成子多边形,然后丢弃重叠的子多边形,然后合并共享公共边的子多边形以减少子多边形的数量。

矩形列表减法

使用与“选择多边形减法”类似的方法预处理矩形列表(并将它们全部转换为任意形状的多边形)。在这之后;找到与选择矩形重叠的所有(子)多边形。请注意,如果矩形列表不经常更改并且可以多次重复使用相同的“预处理多边形列表”,这会更好。

备注

与我描述的“如果可以的话先拆分然后合并”方法相比,多边形减法有更先进的技术,这种方法要复杂得多,性能也更好。一篇研究论文,见https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-SA-97135.pdf

我自己,如果不需要尽可能高的性能(并且如果矩形列表比选择矩形更频繁地更改),我可能会使用“选择矩形细分”方法。

【讨论】:

  • 我也赞成选择矩形细分,因为使用递归函数编写代码很简单。请注意,对于列表的其余部分,要处理的子矩形的最大数量是 4,而不是 8:例如,可以首先将选择矩形分成 3 个水平带,对象上方,对象下方,中间带可以是进一步水平分成3个矩形,其中只有2个需要进一步处理。如果前景中有很多小矩形,这种方法效率很低。
【解决方案2】:

我自己想到了解决方案,结果比我预期的要困难一些。我花了一段时间。

列表遍历的方法没有改变——我从前到后对每个节点进行选择检查。

首先,我选择root 矩形(第一个矩形,与选择相交)。接下来,如果其他矩形相交,则检查它们是否与root 相交,以确保它们不只是在旁边。

我记住了r(当前矩形)和root(包括root)之间的矩形——进一步检查将需要它。矩形存储在rect_list(在另一个列表中)。

开始遍历此列表。此时,我首先检查rsr(一个节点的矩形)没有相交,并将它们的交集存储在ir 中。此外,选择矩形应该与ir 相交。

if (!rintersect(r, sr, ir))
    continue;

if (!rcheck(sel_rect, ir))
    continue;

现在,我扫描ir 周围的区域——矩形每一边的坐标点:

struct rect scan_area {
    ir.l - 1,
    ir.t - 1,
    ir.r + 1,
    ir.b + 1
};

检查当前矩形和选择中的点,但不是在所有记忆的矩形中(list_check 这样做)。

bool l = false;
bool t = false;
bool r = false;
bool b = false;
struct point p;
int i;

for (i = ir.l; i <= ir.r; i++) {
    p.x = i;

    p.y = ir.t - 1;
    if ((t = list_check(rect_list, p))
        break;

    p.y = ir.b + 1;
    if ((b = list_check(rect_list, p))
        break;
}

for (i = ir.t; i <= ir.b; i++) {
    if (t || b)
        break;

    p.y = i;

    p.x = ir.l - 1;
    if ((l = list_check(rect_list, crd))
        break;

    p.x = ir.r + 1;
    if ((r = list_check(rect_list, crd))
        break;
}

if ((obj->selected = l || t || r || b))
    break;

我不会说这个算法效率很低,将它与第一个Brendan提出的方法相比:屏幕空间选择,因为我需要首先检查所有矩形;其次,检查这些矩形的所有像素,包括选择的每个像素。

客观地说,这个算法并不快 (O(n^3))。但是,这在我的任务中并不明显。

【讨论】:

  • 您的两个矩形相交的公式不正确,因为wh 应该是矩形的尺寸,而不是右下角的坐标。 +1-1 调整也很可疑。恐怕您的解决方案只是巧合。在选择矩形中的所有点上迭代的蛮力方法可以工作,但效率非常低。然而,您的迭代并没有实现这一点,因为循环没有嵌套。
  • @chqrlie,是的,您提出的符号使问题更清楚。我更新了问题和自己的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-27
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多