在上一篇博客中我初步实现了扫描线求交算法,但是经检验,发现存在不少的交点是不能够被检测到的。本周我对于上一篇博客中实现的算法进行了详细的分析,找到了算法失效的原因,并实现了改进版,在新的改进版中没有致命的逻辑错误,但是因为我使用的是浮点数,并且在处理过程中引入了一些误差,在某些非常特殊的情况下,算法可能会失效,但在大部分情况下算法都是能准确的找出交点的。
首先分析以下上一篇博客中实现的算法失效原因进行分析:
该扫描线算法需要两个数据结构:优先队列和搜索树。搜索树中储存目前和扫描线相交的所有线段,并且这些线段应该按照与扫描线交点的x坐标排序。但是使用什么方式能够满足该要求呢?我按照从网上搜索到的一些资料,选择使用map来实现该搜索树,利用每条线段上端点的x坐标作为键值,对搜索树中的线段进行排序,结果正如我在上一篇微博中最后分析的那样,该方法并不能保证维持一个正确的相交线顺序,在找某条线段的左右邻居时会出现很大的问题,因为根本就不能保证找到的左右邻居就是实际的左右邻居。
对此,我在本次选择set容器实现该搜索树,在set容器中储存线段的指针,并定义一个排序函数给该set容器;同时该算法会维持一个全局的变量“CUR_LINE_H”,利用该变量表示扫描线已经运动到了什么位置。因为该比那辆随着算法的运行不断发生变化,在set的比较函数中使用该变量,求出新进入的线段与目前扫描线的交点的x坐标,通该x坐标不断与搜索树中的元素进行比较,可以维持一个正确的相交线段的顺序。
// A code block
struct line
{
Point* first;
Point* second;
line(line*L)
{
first = L->first;
second = L->second;
}
line()
{
}
double Int_Line() const
{
if (fabs(CUR_LINE_H - first->y) < ERROR) return first->x;
else if (fabs(CUR_LINE_H - second->y) < ERROR) return second->x;
double delt = (second->x - first->x) / (second->y - first->y) * (CUR_LINE_H - first->y);
return first->x + delt;
}
bool operator==(const line& L1) const
{
return *L1.first == *first && *L1.second == *second;
}
bool operator<(const line& L) const
{
if (*this == L) return false;
return this->Int_Line() < L.Int_Line();
}
struct comp
{
bool operator()(line* L1, line* L2) const
{
std::cout << L1->first->x << " " << L2->first->x << std::endl;
if (*L1 == *L2)
{
std::cout << "equal" << std::endl;
return false;
}
return L1->Int_Line() < L2->Int_Line();
}
};
};
上面的代码给出了结构体line的定义,以及部分函数的定义,其中comp就是定义的仿函数,用来对set进行比较。Int_line用来计算直线与扫描线交点的x坐标。
拥有了正确的搜索树之后,现在主要需要考虑的是对于每个事件处理的逻辑:
下面给出事件处理函数:
void Cutline::HandleEvent(Point* event)
{
if (event->index == 2)//交点
{
cout << "交点" << endl;
cout << "new line: " << "(" << event->belong1->first->x << "," << event->belong1->first->y << ") " << "(" << event->belong1->second->x << "," << event->belong1->second->y << ") " << endl;
cout << "目前被切线段:" << endl;
for (auto x : CurCutLine) cout << x->first->x << " ";
cout << endl;
cout << event->x << endl;
//去重
if (!intersectpoint.empty() && (*event) == (*intersectpoint.back())) return;//重复点直接跳过
intersectpoint.push_back(event);
//找邻居,判相交,先不能更新CUR_LINE_H,一旦更新将找不到it
auto it1 = CurCutLine.find(event->belong1);
auto it2 = CurCutLine.find(event->belong2);
cout << event->belong2->second->x << endl;
auto left = findleft(it1);
auto right = findright(it2);
//交换顺序
CurCutLine.erase(it1);
CurCutLine.erase(it2);
auto newline1 = event->belong1;
auto newline2 = event->belong2;
newline1->first->y = event->y;
newline1->first->x = event->x + 0.00001;
newline2->first->y = event->y;
newline2->first->x = event->x - 0.00001;
//添加两条新的线段
//CUR_LINE_H = (event->y + P.top()->y) / 2;//将扫描线稍微向下挪一些,方便插入共上端点的交点的两条边,但需要让扫描线在下一事件点上方
CUR_LINE_H = event->y;
CurCutLine.insert(newline1);
CurCutLine.insert(newline2);
if (left != CurCutLine.end())
{
auto jiao = intersec(*left, newline2);
if (jiao && jiao->y < event->y)
{
jiao->belong1 = *left;
jiao->belong2 = newline2;
P.push(jiao);
CUR_LINE_H = max(CUR_LINE_H,(CUR_LINE_H + jiao->y) / 2);//需