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凸包
两个通用的操作函数
- Finding whether a face is visible from a outside point’s view
- 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());
}
}