2021SC@SDUSC
BooleanMesh 建立CGAL网格
(一)函数功能概述
validatePosition | 判断输入的位置是否为合法位置 |
buildCgalMesh | 根据位置和索引建立CGAL网格结构 |
fetchFromCgalMesh | 从网格结构中抓取顶点和面信息,放入vertices和faces中 |
isNullCgalMesh | 判断网格结构是否为空 |
(二)CGAL库介绍
BooleanMesh建立CGAL网格借助了计算几何算法库CGAL实现。CGAL库是一个大型C + +库的几何数据结构和算法,可以对网格执行布尔操作。
BooleanMesh最主要使用的CGAL中的内容是其表面网格Surface_mesh类。Surface_mesh类是halfedge(半边数据结构)数据结构的一个实现,它用于表示多面体面。它是一个替代包Halfedge数据结构和三维多面体表面,并且Surface Mesh类是基于索引而不是基于指针的。
CGAL半边结构:Surface_mesh是一种以边为中心的数据结构,能够维护顶点、边和面的关联信息。每条边由两条方向相反的半边表示。每个半边存储对入射面和入射顶点的引用。此外,它将下一个和上一个半边入射的参照存储到其入射面。对于每个面和每个顶点,将存储一条入射半边。半边不存储相对半边的索引,因为Surface_mesh在内存中连续存储相对半边。
上图为CGAL半边结构图示与参与的函数
(三)具体函数分析
1.validatePosition函数
validatePosition函数判断输入参数position是否为合法位置,通过isnan函数分别判断position的x、y、z是否为数字,是数字,则返回 false,否则返回 true;isinf函数判断position的x、y、z是否为无限大,若无限大,则返回true,否则返回false。
因此只要position中的坐标值有一个分量是无限大的或者不是数字的,函数就会返回false,表明position不合法。
//验证位置,判断position是否合法
//isnan函数判断指定参数是否为数字,是数字,返回 false,否则返回 true;isinf判断数据是否为无限大,若无限大,返回1,否则返回0
inline bool validatePosition(const QVector3D &position)
{
if (std::isnan(position.x()))
return false;
if (std::isnan(position.y()))
return false;
if (std::isnan(position.z()))
return false;
if (std::isinf(position.x()))
return false;
if (std::isinf(position.y()))
return false;
if (std::isinf(position.z()))
return false;
return true;
}
2.buildCgalMesh函数
buildCgalMesh函数用于建立CGAL网格。函数首先判断了用于建立面的每个顶点的合法性,一旦出现不合法的顶点,则面的合法性变为false,建立就停止;如果顶点合法,就将此顶点信息建立到existedKeys、positionKeys、positionsInKeys中,以便后续使用。
对面中的点进行遍历,如果最终面的合法性为true且positionKeys中超过三个元素(至少三个点才能确定一个面),那么就将顶点和此面加入到网格结构mesh中。
//建立网格mesh,返回值是网格
template <class Kernel>
typename CGAL::Surface_mesh<typename Kernel::Point_3> *buildCgalMesh(const std::vector<QVector3D> &positions, const std::vector<std::vector<size_t>> &indices)
{
typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh = new typename CGAL::Surface_mesh<typename Kernel::Point_3>;
std::map<PositionKey, typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> vertexIndices;
for (const auto &face: indices) {
std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices;
bool faceValid = true;
std::vector<PositionKey> positionKeys;
std::vector<QVector3D> positionsInKeys;
std::set<PositionKey> existedKeys;
for (const auto &index: face) {
const auto &position = positions[index];
//position是positions[index]
if (!validatePosition(position)) {
faceValid = false;
break;
}
//如果position是有效的点
//看positionkey.h
auto positionKey = PositionKey(position);
if (existedKeys.find(positionKey) != existedKeys.end()) {
continue;
}
//如果在existedKeys中没有positionKey,则将其信息分别插入
existedKeys.insert(positionKey);
positionKeys.push_back(positionKey);
positionsInKeys.push_back(position);
}
if (!faceValid)
continue;
if (positionKeys.size() < 3)
continue;
//如果facevalid=true且positionKeys中超过三个元素(至少三个点一个面)
for (size_t index = 0; index < positionKeys.size(); ++index) {
const auto &position = positionsInKeys[index];
const auto &positionKey = positionKeys[index];
auto findIndex = vertexIndices.find(positionKey);
//如果positionKey在vertexIndices中
if (findIndex != vertexIndices.end()) {
faceVertexIndices.push_back(findIndex->second);//findIndex的second是它的索引
}
//如果positionKey不在vertexIndices中,向mesh中加入顶点,并在vertexIndices和faceVertexIndices中进行同步
else {
auto newIndex = mesh->add_vertex(typename Kernel::Point_3(position.x(), position.y(), position.z()));
vertexIndices.insert({positionKey, newIndex});
faceVertexIndices.push_back(newIndex);
}
}
mesh->add_face(faceVertexIndices);
}
return mesh;
}
3.fetchFromCgalMesh函数
fetchFromCgalMesh函数根据参数mesh网格中的信息,将顶点和面的信息提取出来放入vertices和faces中。
顶点信息的提取是通过对网格中的点一一进行遍历,取出网格中的点及它们的坐标值进行的。
面的信息的提取是通过面迭代器faceIt、顶点迭代器vbegin, vend对半边结构进行遍历确定面的信息来进行的。
/从网格中抓取信息,对vertices和faces进行填充
template <class Kernel>
void fetchFromCgalMesh(typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces)
{
std::map<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index, size_t> vertexIndicesMap;
//将mesh中的顶点加入vertices中,并对vertexIndicesMap进行赋值
for (auto vertexIt = mesh->vertices_begin(); vertexIt != mesh->vertices_end(); vertexIt++) {
auto point = mesh->point(*vertexIt);
float x = (float)CGAL::to_double(point.x());
float y = (float)CGAL::to_double(point.y());
float z = (float)CGAL::to_double(point.z());
vertexIndicesMap[*vertexIt] = vertices.size();
vertices.push_back(QVector3D(x, y, z));
}
//faceIndices中存的是面的顶点,然后再存入faces中
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range faceRage = mesh->faces();
//mesh->face的迭代器,面迭代器类型是面范围的嵌套类型
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range::iterator faceIt;
//tie函数用于方便地将半边结构的两个顶点赋给vbegin和vend(简化语法)
for (faceIt = faceRage.begin(); faceIt != faceRage.end(); faceIt++) {
CGAL::Vertex_around_face_iterator<typename CGAL::Surface_mesh<typename Kernel::Point_3>> vbegin, vend;
std::vector<size_t> faceIndices;
for (boost::tie(vbegin, vend) = CGAL::vertices_around_face(mesh->halfedge(*faceIt), *mesh);
vbegin != vend;
++vbegin){
faceIndices.push_back(vertexIndicesMap[*vbegin]);
}
faces.push_back(faceIndices);
}
}
4.isNullCgalMesh函数
isNullCgalMesh函数判断参数mesh网格结构是否为空。通过对面的迭代器进行访问,根据起始是否等于终止来判断网格结构是否为空,如果相等,则为空;如果不相等,则不为空。
emplate <class Kernel>
bool isNullCgalMesh(typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh)
{
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range faceRage = mesh->faces();
return faceRage.begin() == faceRage.end();
}
补充:PositionKey类
class PositionKey
{
public:
PositionKey(const QVector3D &v);
PositionKey(float x, float y, float z);
const QVector3D &position() const;
bool operator <(const PositionKey &right) const;
bool operator ==(const PositionKey &right) const;
private:
long m_intX = 0;
long m_intY = 0;
long m_intZ = 0;
QVector3D m_position;
static long m_toIntFactor;
};
构造函数 | PositionKey(const QVector3D &v); PositionKey(float x, float y, float z); |
返回m_position | const QVector3D &position() const; |
运算符重载函数 | bool operator <(const PositionKey &right) const; bool operator ==(const PositionKey &right) const; |
位置信息 | m_intX、m_intY、m_intZ、m_position |
转换位置的因子 | m_toIntFactor |
1.构造函数
//两个构造函数
PositionKey::PositionKey(const QVector3D &v) :
PositionKey(v.x(), v.y(), v.z())
{
}
PositionKey::PositionKey(float x, float y, float z)
{
m_position.setX(x);
m_position.setY(y);
m_position.setZ(z);
//m_position=(x,y,z)
m_intX = x * m_toIntFactor;
m_intY = y * m_toIntFactor;
m_intZ = z * m_toIntFactor;
}
2.运算符重载函数
‘<’的运算符重载实现的是两个PositionKey类的变量进行比较时,以x->y->z的顺序进行比较
‘=’ 的运算符重载实现当两个PositionKey类的变量进行比较时,只有x、y、z都相等时,才return true
bool PositionKey::operator <(const PositionKey &right) const
{
if (m_intX < right.m_intX)
return true;
if (m_intX > right.m_intX)
return false;
if (m_intY < right.m_intY)
return true;
if (m_intY > right.m_intY)
return false;
if (m_intZ < right.m_intZ)
return true;
if (m_intZ > right.m_intZ)
return false;
return false;
}
bool PositionKey::operator ==(const PositionKey &right) const
{
return m_intX == right.m_intX &&
m_intY == right.m_intY &&
m_intZ == right.m_intZ;
}