编辑:所有矩形都平行于坐标轴。
个人而言,我会使用KD树或BIH树来解决这个问题。它们都是自适应空间数据结构,具有对数(log)搜索时间。我已经在我的光线追踪器中实现了两者,并且它们的效果非常好。
-- 更新 --
将所有固定矩形存储在KD树中。当您测试交叉点时,请按以下方式遍历KD树:
function FindRects(KDNode node, Rect searchRect, List<Rect> intersectionRects)
// searchRect is the rectangle you want to test intersections with
// node is the current node. This is a recursive function, so the first call
// is the root node
// intersectionRects contains the list of rectangles intersected
int axis = node.Axis;
// Only child nodes actually have rects in them
if (node is child)
{
// Test for intersections with each rectangle the node owns
for each (Rect nRect in node.Rects)
{
if (nRect.Intersects(searchRect))
intersectionRects.Add(nRect);
}
}
else
{
// If the searchRect's boundary extends into the left bi-section of the node
// we need to search the left sub-tree for intersections
if (searchRect[axis].Min // Min would be the Rect.Left if axis == 0,
// Rect.Top if axis == 1
< node.Plane) // The absolute coordinate of the split plane
{
FindRects(node.LeftChild, searchRect, intersectionRects);
}
// If the searchRect's boundary extends into the right bi-section of the node
// we need to search the right sub-tree for intersections
if (searchRect[axis].Max // Max would be the Rect.Right if axis == 0
// Rect.Bottom if axis == 1
> node.Plane) // The absolute coordinate of the split plane
{
FindRects(node.RightChild, searchRect, intersectionRects);
}
}
Rect bounds = ...; // Calculate the bounding area of all shapes you want to
// store in the tree
int plane = 0; // Start by splitting on the x axis
BuildTree(_root, plane, bounds, insertRects);
function BuildTree(KDNode node, int plane, Rect nodeBds, List<Rect> insertRects)
if (insertRects.size() < THRESHOLD /* Stop splitting when there are less than some
number of rects. Experiment with this, but 3
is usually a decent number */)
{
AddRectsToNode(node, insertRects);
node.IsLeaf = true;
return;
}
float splitPos = nodeBds[plane].Min + (nodeBds[plane].Max - nodeBds[plane].Min) / 2;
// Once you have a split plane calculated, you want to split the insertRects list
// into a list of rectangles that have area left of the split plane, and a list of
// rects that have area to the right of the split plane.
// If a rect overlaps the split plane, add it to both lists
List<Rect> leftRects, rightRects;
FillLists(insertRects, splitPos, plane, leftRects, rightRects);
Rect leftBds, rightBds; // Split the nodeBds rect into 2 rects along the split plane
KDNode leftChild, rightChild; // Initialize these
// Build out the left sub-tree
BuildTree(leftChild, (plane + 1) % NUM_DIMS, // 2 for a 2d tree
leftBds, leftRects);
// Build out the right sub-tree
BuildTree(rightChild, (plane + 1) % NUM_DIMS,
rightBds, rightRects);
node.LeftChild = leftChild;
node.RightChild = rightChild;
这里有很多明显的优化方法,但构建时间通常不如搜索时间重要。话虽如此,良好的构建树是使搜索快速的关键。如果想学习如何构建快速的kd-tree,请查阅SAH-KD-Tree。
您可以创建两个矩形索引向量(因为两个对角点唯一定义您的矩形),并按其中一个坐标进行排序。然后,使用这两个索引数组搜索重叠区域,这将具有对数复杂度而非线性复杂度。
区间树:是将“lo”值作为区间的键设计的二叉搜索树。例如,如果要在树中插入(23, 46),则需使用BST中的“23”进行插入。
此外,在区间树中,我们在每个节点上保留以该节点为根的子树的最大端点(hi值)。
这种插入顺序使我们能够在R(logN)时间内搜索所有“R”交点。[我们在logN时间内搜索第一个交点,并在RlogN时间内搜索所有R]请参阅区间树文档,了解如何完成插入、搜索以及复杂性的详细信息。
现在,对于这个问题,我们使用一种称为扫描线算法的算法。想象一条垂直线(与y轴平行),它正在扫描2D空间,并在此过程中与矩形相交。
1)通过优先队列或排序将矩形按x坐标(从左边缘开始)递增排列。如果有N个矩形,则复杂度为NlogN。
2)当此线从左向右扫描时,以下是交点情况:
如果线段与一个从未见过的矩形的左侧相交,则将矩形边的y坐标添加到区间树中。[假设(x1,y1)和(x1,y2)是矩形的左边缘坐标,将区间(y1,y2)添加到区间树中] ---> (NlogN)
在区间树上进行范围搜索。[假设(x1,y1)和(x1,y2)是矩形的左边缘坐标,取区间(y1,y2),并在树上执行区间交集查询以查找所有交点] ---> RlogN(实际应用中)
如果线段与矩形的右侧相交,则从区间树中删除其y坐标,因为矩形现在已完全处理。----> NlogN
总复杂度:NlogN + RlogN
Topcoder提供了一种确定点是否在矩形内的方法。它说,假设我们有一个点x1,y1和一个矩形。我们应该在矩形坐标系中选择一个离当前参考位置非常远的随机点,比如x2,y2。
现在,我们应该用点x1,y1和x2,y2构成一条线段。如果这条线段与给定矩形的奇数边相交(在我们的情况下为1,这种方法也可以扩展到一般多边形),则点x1,y1位于矩形内;如果它与偶数边相交,则点x1,y1位于矩形外。
给定两个矩形,我们需要对第一个矩形的每个顶点重复此过程,以确定可能位于第二个矩形内。这样,即使它们不与x或y轴对齐,我们也能确定两个矩形是否重叠。
由于它们不重叠,我建议采用类似(但不完全相同)于Jason Moore(B)的方法。将您的数组按左上角的x排序。然后将副本按左上角的y排序。(当然,您只需对它们进行指针排序以节省内存)。
现在,您可以创建两个集合Sliding_Window_X和Sliding_Window_Y。
您可以使用二分搜索一次在x排序的数组中搜索A窗口的x坐标(左上角)和y坐标。将结果放入相应的Sliding_Window_Set中。现在,将有一个比A的右下角低的x(y)坐标的有序数组中的所有后续矩形添加到其中。
结果是,在Sliding_Window集中,您拥有与A在一个坐标上重叠的窗口。 A的重叠是Sliding_Window_X和_Y的交集。
Sliding_Window集可以通过仅使用2个数字(相应排序数组的开始和结束索引)轻松表示。
根据您所说的移动A,现在重新计算重叠区域非常容易。根据方向,您现在可以添加/删除滑动窗口集合中的元素。也就是说,您只需从集合前/后的排序数组中取下一个元素,可能会在末尾删除一个元素。
让你的矩形集合为 (Xi1,Yi1,Xi2,Yi2),其中 i 的取值范围从 0 到 N。
如果 Ax1 > Bx2 || Ay1 < By2 || Bx1 > Ax2 || By1 < Ay2,则矩形 A 和 B 不能相交。
创建一个针对范围/区间进行优化的树(例如:线段树或区间树) 请参见 http://w3.jouy.inra.fr/unites/miaj/public/vigneron/cs4235/l5cs4235.pdf
使用此树来查找一组三角形,同时您的三角形正在更改坐标。