计算几何学:线段求交扫描线算法(改进版)

本文详细分析了扫描线求交算法存在的问题,通过使用set容器和自定义比较函数解决了线段顺序错误的问题。同时,介绍了算法在处理交点时的逻辑,以及在特定情况下因引入误差可能导致的失效。尽管存在理论上的失效可能性,但实验证明算法在大部分情况下表现准确。
摘要由CSDN通过智能技术生成

在上一篇博客中我初步实现了扫描线求交算法,但是经检验,发现存在不少的交点是不能够被检测到的。本周我对于上一篇博客中实现的算法进行了详细的分析,找到了算法失效的原因,并实现了改进版,在新的改进版中没有致命的逻辑错误,但是因为我使用的是浮点数,并且在处理过程中引入了一些误差,在某些非常特殊的情况下,算法可能会失效,但在大部分情况下算法都是能准确的找出交点的。

首先分析以下上一篇博客中实现的算法失效原因进行分析:
该扫描线算法需要两个数据结构:优先队列和搜索树。搜索树中储存目前和扫描线相交的所有线段,并且这些线段应该按照与扫描线交点的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);//需
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值