2D-3D凸包C++代码实现

本文详细介绍了2D凸包的概念及其在碰撞检查、动物活动范围和稳健估计等领域的应用。文章提供了两种不同的2D凸包算法实现:Gift wrapping算法和Modified Graham's算法。接着,讨论了3D凸包的构建,包括面可见性判断和点在3D形状内的检测。通过这些算法,可以有效地处理几何数据并进行三维空间的分析。
摘要由CSDN通过智能技术生成

2D凸包

凸集是包含所有点的最小集合,凸包是将凸集中的点连接起来形成一个简单多边形所得到的多边形。
1.由原始点集中的全部或部分点组成,并将其余的点包起来。
2.每个顶点的内角都是凸角

凸包的应用

1.碰撞检查
在这里插入图片描述
2.动物活动范围在这里插入图片描述

3.寻找稳健估计
在这里插入图片描述

凸包算法

wrapping algorithm

在这里插入图片描述

void convexhull2DGiftwrapping(std::vector<Point2d>& _points, std::vector<Point2d>& _convex)
{
	if (_points.size() <= 3)
		return;
	// find LTL
	Point2d bottom_point = _points[0];

	for (auto& point : _points)
	{
		if (point[Y] < bottom_point[Y] ||
			(point[Y] == bottom_point[Y] && point[X] < bottom_point[X]))
			bottom_point = point;
	}

	Point2d min_polar_point = _points[0];
	float current_polor_angle = 360.0;
	for (size_t i = 0; i < _points.size(); ++i)
	{
		float polar_angle = polarAngle(_points[i], bottom_point);
		if (bottom_point != _points[i] && current_polor_angle > polar_angle)
		{
			current_polor_angle = polar_angle;
			min_polar_point = _points[i];
		}
	}

	_convex.push_back(bottom_point);
	_convex.push_back(min_polar_point);

	Point2d ref_point = min_polar_point;
	int index_before_last = 0;

	while (true)
	{
		current_polor_angle = 360;
		for (size_t i = 0; i < _points.size(); ++i)
		{
			Vector2f vec1 = ref_point - _convex[index_before_last];
			Vector2f vec2 = _points[i] - ref_point;

			float between_angle = angle(vec1, vec2);
			if (ref_point != _points[i] && 
				current_polor_angle > between_angle)
			{
				current_polor_angle = between_angle;
				min_polar_point = _points[i];
			}
		}

		if (min_polar_point == bottom_point)
			break;

		index_before_last++;
		_convex.push_back(min_polar_point);
		ref_point = min_polar_point;
	}
	
}

Modified Grahams algorithm

与上一个算法类似,该算法也从选取凸壳中的一个已知点(例如:最左边的顶点)开始。
构建凸包分两个阶段。上凸包和下凸包。

首先从左到右对输入点集进行排序。
凸包内任意点朝向内部的角应该是凸角。
我们使用Stack作为中间结构来存储当前的凸包。

伪代码

在这里插入图片描述

void convexhull2DModifiedGrahams(std::vector<Point2d>& _points, std::vector<Point2d>& _convex)
{
	if (_points.size() <= 3)
		return;

	//std::sort(_points.begin(), _points.end());
	std::sort(_points.begin(), _points.end(), [](const Point2d& a, const Point2d& b)
		{
			if (a[X] < b[X] || (a[X] == b[X] && a[Y] < b[Y]))
				return true;
			return false;
		});

	std::vector<Point2d> l_upper;
	std::vector<Point2d> l_lower;

	//建立上凸包
	l_upper.push_back(*_points.begin());
	l_upper.push_back(*(std::next(_points.begin())));

	int index = 0;
	for (size_t i = 2; i < _points.size(); i++)
	{
		index = l_upper.size();
		const auto& next_point = _points[i];
		while (l_upper.size() > 1 && left(l_upper[index - 2], l_upper[index - 1], next_point))
		{
			l_upper.pop_back();
			index = l_upper.size();
		}
		l_upper.push_back(next_point);
	}

	//建立下凸包
	std::reverse(_points.begin(), _points.end());

	l_lower.push_back(*_points.begin());
	l_lower.push_back(*(std::next(_points.begin())));

	int index = 0;
	for (size_t i = 2; i < _points.size(); i++)
	{
		index = l_lower.size();
		const auto& next_point = _points[i];
		while (l_lower.size() > 1 && left(l_lower[index - 2], l_lower[index - 1], next_point))
		{
			l_lower.pop_back();
			index = l_upper.size();
		}
		l_lower.push_back(next_point);
	}
	
	l_upper.pop_back();
	l_lower.pop_back();

	_convex.insert(_convex.end(),l_upper.begin(),l_upper.end());
	_convex.insert(_convex.end(), l_lower.begin(), l_lower.end());
}

3D凸包

在这里插入图片描述
在这里插入图片描述

两个通用的操作函数

  1. Finding whether a face is visible from a outside point’s view
  2. Finding whether the point is inside a 3D shape or not

在这里插入图片描述

float scalerTripleProduct(yhaida::Vector3f v1, yhaida::Vector3f v2, yhaida::Vector3f v3)
{
	auto bc_cross = crossProduct3D(v2, v3);
	return dotProduct(v1, bc_cross);
}
float FaceVisibility(const Facespace& _f, const Point3d& _p)
{
	Point3d p1, p2, p3;
	p1 = *_f.vertices[0]->point;
	p2 = *_f.vertices[1]->point;
	p3 = *_f.vertices[2]->point;

	auto a1 = p2 - p1;
	auto b1 = p3 - p1;
	auto c1 = _p - p1;

	double vol1 = yhaida::scalerTripleProduct(a1, b1, c1);
	return vol1;
}

在这里插入图片描述
下一个函数依赖上一个函数

在这里插入图片描述
在这里插入图片描述

bool isInside(Point3d& _point, std::vector<Point3d>& _points);
bool isInside(Point3d& _point, std::vector<Face>& _faces);

具体没有实现,就说一下大致的思路,按照上一个函数判断一个点是否对于这个面可见这个函数来实现,就是如果点对于多面体的各个面都可见那么这个点就在这个多面体里面。

代码

static void adjust_normal(Facespace* _face, Point3d& _ref_point)
{
	int order = FaceVisibility(*_face, _ref_point);
	if (order < 0)
	{
		//平面的法线朝外需要被switch
		_face->normal_switch_needed = true;
	}
}

static void construct_initial_polyhedron(std::vector<Vertexspace3d*> _points,
	int i,
	std::vector<Facespace*>& faces,
	std::vector<Edgespace3d*> edges,
	Point3d& ref_point)
{
	//1.创建面  逆时针顺序
	faces.push_back(new Facespace(_points[i + 0], _points[i + 1], _points[i + 2]));
	faces.push_back(new Facespace(_points[i + 0], _points[i + 1], _points[i + 3]));
	faces.push_back(new Facespace(_points[i + 1], _points[i + 2], _points[i + 3]));
	faces.push_back(new Facespace(_points[i + 2], _points[i + 0], _points[i + 3]));

	//判断都是法线朝外
	for (size_t i = 0; i < faces.size(); i++)
	{
		adjust_normal(faces[i], ref_point);
	}

	//2.创建边  按照创面的点顺序来创建边
	edges.push_back(new Edgespace3d(_points[i + 0], _points[i + 1]));
	edges.push_back(new Edgespace3d(_points[i + 1], _points[i + 2]));
	edges.push_back(new Edgespace3d(_points[i + 2], _points[i + 0]));
	edges.push_back(new Edgespace3d(_points[i + 0], _points[i + 3]));
	edges.push_back(new Edgespace3d(_points[i + 1], _points[i + 3]));
	edges.push_back(new Edgespace3d(_points[i + 2], _points[i + 3]));

	//3.往面里面添加边界
	faces[0]->addEdge(edges[0]);
	faces[0]->addEdge(edges[1]);
	faces[0]->addEdge(edges[2]);

	faces[1]->addEdge(edges[0]);
	faces[1]->addEdge(edges[3]);
	faces[1]->addEdge(edges[4]);

	faces[2]->addEdge(edges[1]);
	faces[2]->addEdge(edges[4]);
	faces[2]->addEdge(edges[5]);

	faces[3]->addEdge(edges[2]);
	faces[3]->addEdge(edges[5]);
	faces[3]->addEdge(edges[3]);

	//4.往边设置对应的共享的面
	edges[0]->faces[0] = faces[0];
	edges[0]->faces[1] = faces[1];

	edges[1]->faces[0] = faces[0];
	edges[1]->faces[1] = faces[2];

	edges[2]->faces[0] = faces[0];
	edges[2]->faces[1] = faces[3];

	edges[3]->faces[0] = faces[1];
	edges[3]->faces[1] = faces[3];

	edges[4]->faces[0] = faces[2];
	edges[4]->faces[1] = faces[1];

	edges[5]->faces[0] = faces[3];
	edges[5]->faces[1] = faces[2];
}

//找新边中的两个端点是否在新面上,如果在那么就是相邻,反之亦然
static bool incident_face(Facespace* _face, Edgespace3d* _edge)
{
	auto r1 = std::find(_face->vertices.begin(), _face->vertices.end(), _edge->vertices[0]);
	auto r2 = std::find(_face->vertices.begin(), _face->vertices.end(), _edge->vertices[1]);
	if (r1 != std::end(_face->vertices) && r1 != std::end(_face->vertices))
		return true;
	return false;
}

void convexhull3D(std::vector<Point3d>& _points, std::vector<Facespace*>& faces)
{
	std::vector<Vertexspace3d*> vertices;
	for (size_t i = 0; i < _points.size(); i++)
	{
		vertices.push_back(new Vertexspace3d(&_points[i]));
	}

	std::vector<Edgespace3d*> edges;

	//1.找到不共面的四个点
	//TODO: 可以找到不是连续的四个点
	size_t i = 0, j = 0;
	bool found_noncoplaner = false;
	for (i = 0; i < _points.size() - 3; i++)
	{
		if (!coplaner(_points[i], _points[i + 1], _points[2], _points[i + 3]));
		{
			found_noncoplaner = true;
			break;
		}
	}
	//全部共面
	if (!found_noncoplaner)
	{
		std::cout << "All the points are coplaner" << std::endl;
		return;
	}

	//找到一个在四面体内部的点
	//WARNING:这个计算方式可能这个点在外部吧?
	float x_mean = (_points[i][X] + _points[i + 1][X] + _points[i + 2][X] + _points[i + 3][X]) / 4;
	float y_mean = (_points[i][Y] + _points[i + 1][Y] + _points[i + 2][Y] + _points[i + 3][Y]) / 4;
	float z_mean = (_points[i][Z] + _points[i + 1][Z] + _points[i + 2][Z] + _points[i + 3][Z]) / 4;
	float x_p = x_mean;
	float y_p = y_mean;
	float z_p = z_mean;

	Point3d point_ref(x_p, y_p, z_p);
	construct_initial_polyhedron(vertices, i, faces, edges, point_ref);

	//把已经挑出来的四个点,标记为已经处理
	vertices[i]->processed = true;
	vertices[i + 1]->processed = true;
	vertices[i + 2]->processed = true;
	vertices[i + 3]->processed = true;

	//2.增加一个新的点
	//     判断其是否在上述四面体的内部,如果在内部其继续,否则创建一个新的3D凸包
	for (size_t i = 0; i < vertices.size(); i++)
	{
		if (vertices[i]->processed)
			continue;

		std::vector<Facespace*> visible_faces;
		std::vector<Edgespace3d*> border_edges;
		std::vector<Edgespace3d*> tobe_deleted_edges;

		//判断vertices[i]与每个面的可见性
		for (size_t j = 0; j < faces.size(); j++)
		{
			float volum = FaceVisibility(*faces[i], *vertices[i]->point);
			//1.法线朝内
			//2.法线朝外
			if ((!faces[j]->normal_switch_needed && volum < 0)
				|| (faces[j]->normal_switch_needed && volum > 0))
			{
				faces[j]->visible = true;
				visible_faces.push_back(faces[j]);
			}
		}

		// 点如果在3D凸包内部
		// 很奇怪,点对于面可见是在外面而言
		if (!visible_faces.size())
			continue;	// Point is inside

		// 处理外部的点
		// 如果这个点处于两个面之间,那么需要删除这条边,再由两个面的点组成新的面、线即可
		// 如果这个点只是正对一个面可见那么这个点连接这个面的点组成新的面与线即可
		for (size_t k = 0; k < visible_faces.size(); k++)
		{
			for (size_t j = 0; j < visible_faces[k]->edges.size(); j++)
			{
				//点对于两面可见
				if (visible_faces[k]->edges[j]->faces[0]->visible &&
					visible_faces[k]->edges[j]->faces[1]->visible)
				{
					//删除这条边
					tobe_deleted_edges.push_back(visible_faces[k]->edges[j]);
				}
				else
				{
					//在可见面中至少有一条边是可见的
					border_edges.push_back(visible_faces[k]->edges[j]);
				}
			}
		}

		//因为使处于外面的点,所以需要创建新的面与新的边
		std::vector<Facespace*> new_faces;
		std::vector<Edgespace3d*> new_edges;

		const unsigned int new_size = border_edges.size();
		std::list<Vertexspace3d*> unque_vertices;
		for (size_t j = 0; j < new_size; j++)
		{
			unque_vertices.push_back(border_edges[j]->vertices[0]);
			unque_vertices.push_back(border_edges[j]->vertices[1]);
		}

		//排序去重
		unque_vertices.sort();
		unque_vertices.unique([](Vertexspace3d* a, Vertexspace3d* b)
			{
				return *(a->point) == *(b->point);
			});

		std::list<Vertexspace3d*>::iterator it;

		for (size_t j = 0; j < new_size; j++)
		{
			it = unque_vertices.begin();
			//it 表示某个迭代器,n 为整数。该函数的功能是将 it 迭代器前进或后退 n 个位置。
			std::advance(it, j);

			new_edges.push_back(new Edgespace3d(*it, vertices[i]));
			new_faces.push_back(new Facespace(border_edges[j]->vertices[0], vertices[i], border_edges[j]->vertices[1]));
			
			//往旧边中把旧面替换新面
			if (border_edges[j]->faces[0]->visible)
			{
				border_edges[j]->faces[0] = new_faces[new_faces.size() - 1];
			}
			else
			{
				border_edges[j]->faces[1] = new_faces[new_faces.size() - 1];
			}
		}

		//判断每个面的法线方向
		for (size_t j = 0; j < new_size; j++)
		{
			adjust_normal(new_faces[j], point_ref);
		}

		//往新边中添加新面
		for (size_t j = 0; j < new_edges.size(); j++)
		{
			std::vector<Facespace*> incident_faces;
			for (size_t k = 0; k < new_faces.size(); k++)
			{
				//查看新面与新边是否相邻
				if (incident_face(new_faces[k], new_edges[j]))
					incident_faces.push_back(new_faces[k]);
			}
			new_edges[j]->faces[0] = incident_faces[0];
			new_edges[j]->faces[1] = incident_faces[1];
		}

		//往新面中添加旧边与新边
		for (size_t j = 0; j < new_size; j++)
		{
			new_faces[j]->addEdge(border_edges[j]);

			for (size_t k = 0; k < new_edges.size(); k++)
			{
				if (incident_face(new_faces[j], new_edges[k]))
					new_faces[j]->addEdge(new_edges[k]);
			}
		}

		for (size_t k = 0; k < tobe_deleted_edges.size(); k++)
		{
			for (size_t j = 0; j < edges.size(); j++)
			{
				if (*tobe_deleted_edges[k] == *edges[j])
					edges.erase(edges.begin() + j);
			}
		}

		for (size_t k = 0; k < visible_faces.size(); k++)
		{
			for (size_t j = 0; j < faces.size(); j++)
			{
				if (visible_faces[k] == faces[j])
					faces.erase(faces.begin() + j);
			}
		}

		faces.insert(faces.end(), new_faces.begin(), new_faces.end());
		edges.insert(edges.end(), new_edges.begin(), new_edges.end());
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yhaida

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值