四叉树是剖分任意平面区域的重要方法,其核心思想是通过递归算法,离散区域为四边形,四边形边长满足单元尺寸要求。其算法稳定,思路清晰。因为单元形状被四边形限定,生成的网格质量较好。
1. 四叉树
四叉树的结构如下图,每个父节点只有四个子节点。采用链表实现,如果某个父节点没有子节点,四个指针为空;子节点同时存储其父信息,如果父信息为空指针,那么其是根节点。四叉树的数据结构大致如下:
为了满足划分网格,四叉树结构需满足细分四边形、查找相邻四边形的功能。部分代码如下,
struct Vertex //节点坐标
{
Vertex() :x(0.), y(0.) {}
Vertex(double x_,double y_):x(x_),y(y_){}
Vertex(const Vertex& other) :x(other.x), y(other.y) {}
Vertex getMid(const Vertex& other) {
return Vertex((x+other.x)/2,(y+other.y)/2);
}
double distance(const Vertex& other) {
return std::pow(pow(x-other.x,2)+pow(y-other.y,2),0.5);
}
double x;
double y;
};
//节点
struct QUAD {
QUAD() {}
QUAD(const Vertex& v0, const Vertex& v1, const Vertex& v2 , const Vertex& v3){
nodes[0] = v0;
nodes[1] = v1;
nodes[2] = v2;
nodes[3] = v3;
sons[0] = nullptr;
sons[1] = nullptr;
sons[2] = nullptr;
sons[3] = nullptr;
}
void operator=(const QUAD& quad);
Vertex nodes[4]; //存储四个节点
QUAD* parent; //存储父节点,用于查找相邻单元
QUAD* sons[4]; //存储四个子节点,分别是SE,NE,NW,SW;
int level;//记录四边形的level。初始level为0,细化依次level++
//QUAD* SE; //South East
//QUAD* NE; //North East
//QUAD* NW; //North West
//QUAD* SW; //South West
};
class QuadsTree
{
public:
QuadsTree();
void addRoot(QUAD* quad) {
Root = quad;
Root->parent = nullptr;
}
bool splitQUAD(QUAD* current); //添加现有节点的四个子四边形
void refine(double len); //如果四边形对角线长度大于len,细分
void refine();
//查找四边形的邻居。
void getNorth(QUAD* current,QUAD* neighbor1, QUAD* neighbor2);
//void getSouth(QUAD* current);
//void getWest(QUAD* current);
//void getEast(QUAD* current);
void print(QUAD* current, std::vector<TopoDS_Shape>* shapes); //Root-> TL,TR,BL,BR
void getQuads(std::vector<TopoDS_Shape>* shapes);
public:
QUAD* Root;
void PreOrder(QUAD* current);
void add(QUAD* current, int pos, QUAD* quad); //1-4----> BR,TR,TL,BL
void refinement(QUAD* current, double len);
void refinement(QUAD* current);
double spaceFunction(QUAD* current);
};
细分四边形的代码较为简单,当QUAD的对角线长度大于spaceFunction函数返回的尺寸时,将四边形二分。refinement函数采用递归算法,即可从Root开始,细化所有不满足的网格。
void QuadsTree::refinement(QUAD* current) {
if (current->sons[0] == nullptr) {
//计算对角线长度,细化。要满足“level restriction”,此处还应该考虑临近四边形的level
Vertex v0 = current->nodes[0];
Vertex v3 = current->nodes[3];
double lenV0V3 = v0.distance(v3);
double len = spaceFunction(current);
if (lenV0V3 > len) {
splitQUAD(current);
}
else {
return;
}
}
refinement(current->sons[0]);
refinement(current->sons[1]);
refinement(current->sons[2]);
refinement(current->sons[3]);
}
if ((current == parent->sons[0] ) || (current == parent->sons[3])) { //如果Current是 SW,SE;其邻居为同一级的NW,NE或其子节点
QUAD* temp=nullptr;
if ((current == parent->sons[0])) temp = parent->sons[1];
if ((current == parent->sons[3])) temp = parent->sons[2];
if (!temp->sons[0]) { //没有子节点
neighbor1 = temp;
neighbor2 = nullptr;
return;
}
else {
neighbor1 = temp->sons[0];
neighbor2 = temp->sons[3];
return;
}
}
2. 网格划分
使用四叉树进行网格划分的一般步骤如下,
生成平面几何图形的《Bounding Box》
将根据合适的Space function将BoundingBox细分为合适的尺寸。
移除边界外和靠近边界的四边形。
边界恢复。
连接四边形节点,形成连续的区域划分。
以下对这几方面内容将详细介绍,
生成二维图形的Bounding Box较为简单,参考《Bounding Box》,生成AABB形式的。
将区域细分为合适的尺寸,当四边形对角线长度大于中心点的SpaceFunction值时,即细分。并且,当我level值大于相邻四边形的level值时,必须先将相邻四边形细分,再细分此四边形。
当四边形的某一部分在区域外部时,删除。判断四个角点是否都在四边形内部,否则,删除。判断点在区域的内部、外部,参考《带孔平面的网格划分策略》所述的交点个数办法。删除后如下图,
2.4 边界恢复
如上图,删除四边形之后,原始图形边界与四边形边界有未填充的区域。边界恢复的目的是补全这部分区域,采用波前法(Advanced Front Technology),参考《AFT(波前法) 三角网格剖分》。此步的难点在于,查找四边形的边界,并且使其有向,形成闭合的“AFT前沿”。由于此时的原始边界为外边界(逆时针),四边形边界均为内边界(顺时针)。关于查找四边形边界,较为复杂,目前我并没有很清晰的思路。有以下两种思考:
使用四边形邻居关系查找:以某一段边界出发(某个没有邻居的边),沿着其邻居查找。如果某个四边形两条边都没有邻居,都是边界;如果某个四边形的相邻四边形中都是不是边界,在其邻居的邻居中查找,直到找到与前一段边界共节点的边界。如果边界节点与起始点重合,一段完整的边界结束。继续寻找下一段边界,直到所有四边形都找过。对闭合的四边形进行绕向整理,使其顺时针排序。
先找到所有边界,再找环,再绕向。所有没有邻居的边即为边界,以一条边为起始,依次找其共节点下一条边,直到找到闭合的环;以同样的方法,找到下一条环;直到所有边界都找完为止。
此两种方法的复杂度都不小,我并没有编码实现,以后有机会,会做些深入学习。找到四边形边界环如下左图,通过AFT划分之后的模型如下右图。
此六种类别的识别,通过其每条边相邻四边形的情况定义。
第一种:每条边没有邻居或只有一个邻居
某条边有2个邻居,并且其他边没有邻居或只有一个邻居
两条边有两个邻居,并且这两条边相邻。其余两条边没有邻居或只有一个邻居
两条边有两个邻居,并且这两条边相对。其余两条边没有邻居或只有一个邻居。
三条边有2个邻居,另一条边没有边界或只有一条边界。
四条边各有两个边界
参考:
基于自适应笛卡尔网格的虚拟单元方法研究