首页/文章/ 详情

四叉树剖分平面区域

11月前浏览619

四叉树是剖分任意平面区域的重要方法,其核心思想是通过递归算法,离散区域为四边形,四边形边长满足单元尺寸要求。其算法稳定,思路清晰。因为单元形状被四边形限定,生成的网格质量较好。

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]);}
      查找相邻四边的算法较为复杂,要分很多情况,以下以查找每个四边形北边的邻居为例说明。

      对上图所标的四种情况分别说明。为了单元光滑过渡,四边形有“one-level restriction”,所以一个四边形的一条边最多有2个邻居。

      1. 如果四边形指针为空,直接返回。
      2. 如果当前四边形的父是根节点。如果Current是NE,NW,北边没有相邻四边形,返回。
      3. 如果当前四边形是SW,SE,其北边为同级的NW,NE或者其子节点。此时只需要访问到Current的父一级就OK。
          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;    }  }
        d. 如果当前四边形是NW,NE,需要访问到Current父节点的父节点。示意图如下,首先要找到current父的北边邻居,然后判断此邻居有没有子节点,如果没有子节点,它就是Current的邻居。如果有,考察其SE的情况,再看SE是否有子节点。

        通过以上定义,我们得到如下的四叉树离散的区域。

        2. 网格划分

        使用四叉树进行网格划分的一般步骤如下,

        • 生成平面几何图形的《Bounding Box

        • 将根据合适的Space function将BoundingBox细分为合适的尺寸。

        • 移除边界外和靠近边界的四边形。

        • 边界恢复。

        • 连接四边形节点,形成连续的区域划分。

        以下对这几方面内容将详细介绍,

        2.1 Bounding Box

        生成二维图形的Bounding Box较为简单,参考《Bounding Box》,生成AABB形式的。

        2.2 细分

        将区域细分为合适的尺寸,当四边形对角线长度大于中心点的SpaceFunction值时,即细分。并且,当我level值大于相邻四边形的level值时,必须先将相邻四边形细分,再细分此四边形。

        2.3 移除边界外和靠近边界的四边
        当满足下式时,认为四边形离边界太近,删除。

        当四边形的某一部分在区域外部时,删除。判断四个角点是否都在四边形内部,否则,删除。判断点在区域的内部、外部,参考《带孔平面的网格划分策略》所述的交点个数办法。删除后如下图,

        2.4 边界恢复

        如上图,删除四边形之后,原始图形边界与四边形边界有未填充的区域。边界恢复的目的是补全这部分区域,采用波前法(Advanced Front Technology),参考《AFT(波前法) 三角网格剖分》。此步的难点在于,查找四边形的边界,并且使其有向,形成闭合的“AFT前沿”。由于此时的原始边界为外边界(逆时针),四边形边界均为内边界(顺时针)。关于查找四边形边界,较为复杂,目前我并没有很清晰的思路。有以下两种思考:

        • 使用四边形邻居关系查找:以某一段边界出发(某个没有邻居的边),沿着其邻居查找。如果某个四边形两条边都没有邻居,都是边界;如果某个四边形的相邻四边形中都是不是边界,在其邻居的邻居中查找,直到找到与前一段边界共节点的边界。如果边界节点与起始点重合,一段完整的边界结束。继续寻找下一段边界,直到所有四边形都找过。对闭合的四边形进行绕向整理,使其顺时针排序。

        • 先找到所有边界,再找环,再绕向。所有没有邻居的边即为边界,以一条边为起始,依次找其共节点下一条边,直到找到闭合的环;以同样的方法,找到下一条环;直到所有边界都找完为止。

        此两种方法的复杂度都不小,我并没有编码实现,以后有机会,会做些深入学习。找到四边形边界环如下左图,通过AFT划分之后的模型如下右图。

        2.5 连接
        连接内部四边形节点即可形成最终网格划分。因为“one-level restriction”,形成的四边形只有以下六种类型(按节点数和节点位置区分)。

        此六种类别的识别,通过其每条边相邻四边形的情况定义。

        • 第一种:每条边没有邻居或只有一个邻居

        • 某条边有2个邻居,并且其他边没有邻居或只有一个邻居

        • 两条边有两个邻居,并且这两条边相邻。其余两条边没有邻居或只有一个邻居

        • 两条边有两个邻居,并且这两条边相对其余两条边没有邻居或只有一个邻居。

        • 三条边有2个邻居,另一条边没有边界或只有一条边界。

        • 四条边各有两个边界

        在实际编码实现时,主要关注每条边是否有2个邻居,有几条边。

        参考:

        Finite Element Mesh Generation

        基于自适应笛卡尔网格的虚拟单元方法研究

        来源:有限元术
        理论科普
        著作权归作者所有,欢迎分享,未经许可,不得转载
        首次发布时间:2023-09-02
        最近编辑:11月前
        寒江雪_123
        硕士 | cae工程师 签名征集中
        获赞 48粉丝 98文章 48课程 9
        点赞
        收藏
        未登录
        还没有评论
        课程
        培训
        服务
        行家
        VIP会员 学习 福利任务 兑换礼品
        下载APP
        联系我们
        帮助与反馈