C++: 基于四叉树数据结构的自适应网格

二叉树是一种典型的非线性存储数据结构,查找效率可以达到\(O(log_2N)\),同样,这类树状结构存在许多种变体,详细参考邓俊辉老师的《数据结构C++》课程。在这里不详细介绍树状数据结构的具体特性,只是初步尝试下基于四叉树数据结构如何实现\(CFD\)计算网格的自适应功能。

四叉树数据数据结构

四叉树数据结构与二维空间网格对应关系如下图所示

基于四叉树数据结构,容易实现对于二维空间区域的局部细化,这对于\(CFD\)计算是十分有用的。一般来说,网格的好坏能够很大程度上影响\(CFD\)数值计算过结果的准确性,网格越精细,流场空间分辨率也就越高,能够描述更为细致的流场,同时也意味着,需要求解的线性方程组更加庞大。

自适应网格就是为了优化此类问题而提出的,在流场内,物理量梯度较大的地方,设置更加细致的网格,流场平缓的区域,网格可以适当稀疏。然而,一般的多面体、多边形网格划分难度较大,为了划分合适的贴体网格,精确拟合物体表面,网格划分过程中计算量同样巨大。因此,自适应网格一般都是基于简单的几何体,规整地四分或者八分网格,以此达到网格划分。著名的\(CFD\)开源求解程序\(Gerris\),便拥有基于四/八叉树建立的网格自适应功能。

源码项目地址(还没更新)

demo

例子:在一块宽度为\(\pi\)的方形区域中间,定义一条曲线\(z=sin(x+t)\),在曲线周围设置网格加密最大至7层,曲线上下0.5为加密区域。效果如下




#include <iostream>
#include "QuadTree.h"
#include "PanelTree.h"
using namespace std;
#define PI 3.1415926
double Eta(const Panel& e, double t) {
double x=e.Centroid(0, 0);
double y=e.Centroid(1, 0);
double z = e.Centroid(2, 0);
return sin(x + t);
} int main(){
Panel patch({ -PI,0,-PI }, { -PI,0,PI }, { PI,0,PI }, { PI,0,-PI });
PanelTree tree(patch);
char buff[100];
for (int i = 0; i < 100; i++) {
sprintf_s(buff, "leafsfile_%04d.txt", i);
//最大加密至7层,曲线上下0.5为加密区域
tree.Expand_demo(tree.root, 7, i/30., Eta, 0.5, -0.5);
tree.WriteLeafsNode(buff);
}
return 0;
}

四叉树模板类 ADT

四叉树模板类

/*QuadTree.h
* Include; TreeNode Class, QuadTree Class
*/
#pragma once
#include <iostream>
#include <vector>
#include <iomanip>
#include <fstream>
#define FormatOut(string,X) std::cout<< std::setw(20) << std::left <<string<<":"<< (#X)<<" = "<<X<<std::endl template<class T> using vector = std::vector<T>;
template<class T> class TreeNode;
template<class T> using TreeNodePosi = TreeNode<T>*; #define HasChild(p) (p->c1st!=NULL||p->c2nd!=NULL||p->c3rd!=NULL||p->c4th!=NULL)
#define HasParent(p) (p->parent!=NULL)
template<class T>
class TreeNode {
public:
T element;
TreeNodePosi<T> parent, c1st, c2nd, c3rd, c4th;
int height;
TreeNode() :parent(NULL), c1st(NULL), c2nd(NULL), c3rd(NULL), c4th(NULL), height(1) {}
TreeNode(T e) : element(e), parent(NULL), c1st(NULL), c2nd(NULL), c3rd(NULL), c4th(NULL), height(1) {}
TreeNode(T e, TreeNodePosi<T> p) :element(e), parent(p), c1st(NULL), c2nd(NULL), c3rd(NULL), c4th(NULL) {
this->height = 1 + p->height;
}
void insertAsc1st(T const& e) { this->c1st = new TreeNode(e, this); this->c1st->height = this->height + 1; }
void insertAsc2nd(T const& e) { this->c2nd = new TreeNode(e, this); this->c2nd->height = this->height + 1; }
void insertAsc3rd(T const& e) { this->c3rd = new TreeNode(e, this); this->c3rd->height = this->height + 1; }
void insertAsc4th(T const& e) { this->c4th = new TreeNode(e, this); this->c4th->height = this->height + 1; }
}; template<class T>
class QuadTree {
public:
int Leaf_Num;
vector<TreeNodePosi<T>> Leafs;
int size;
TreeNodePosi<T> root;
QuadTree() :root(NULL), size(0), Leaf_Num() {}
QuadTree(const T &root_node) :size(1), Leaf_Num(1) {
root = new TreeNode<T> (root_node);
}
virtual void Expand(TreeNodePosi<T> p); //Expend 1 Level
virtual void Expand(TreeNodePosi<T> p, int Level); //Expand to Level
void DeleteTreeNode(TreeNodePosi<T>& p) { delete p; p = NULL;
} //delete TreeNode
void DeleteTree(TreeNodePosi<T>& p); //delete Tree at p
~QuadTree() {
DeleteTree(this->root);
delete root;
root = NULL;
std::cout << "QuadTree deconstructor" << std::endl;
}
void Travers(TreeNodePosi<T> p, vector<TreeNodePosi<T>>& S);
void Travers();
void WriteLeafsNode(const char* filename);
}; template<class T>
void QuadTree<T>::Expand(TreeNodePosi<T> p) {
T c1_sub, c2_sub, c3_sub, c4_sub;
p->insertAsc1st(c1_sub);
p->insertAsc2nd(c2_sub);
p->insertAsc3rd(c3_sub);
p->insertAsc4th(c4_sub);
size += 4;
} template<class T>
void QuadTree<T>::Expand(TreeNodePosi<T> p, int Level){
if (p->height >= Level) return;
else {
this->Expand(p);
this->Expand(p->c1st, Level);
this->Expand(p->c2nd, Level);
this->Expand(p->c3rd, Level);
this->Expand(p->c4th, Level);
}
} template<class T>
inline void QuadTree<T>::DeleteTree(TreeNodePosi<T>& p){
if (p == NULL) return;
if (HasChild(p)) {
DeleteTree(p->c1st); DeleteTreeNode(p->c1st);
DeleteTree(p->c2nd); DeleteTreeNode(p->c2nd);
DeleteTree(p->c3rd); DeleteTreeNode(p->c3rd);
DeleteTree(p->c4th); DeleteTreeNode(p->c4th);
}
else {
DeleteTreeNode(p);
}
} template<class T>
inline void QuadTree<T>::Travers(TreeNodePosi<T> p, vector<TreeNodePosi<T>>& S){
if (p == NULL) return;
if (!HasChild(p)) {
S.push_back(p);
}
else {
Travers(p->c1st, S);
Travers(p->c2nd, S);
Travers(p->c3rd, S);
Travers(p->c4th, S);
}
} template<class T>
inline void QuadTree<T>::Travers(){
Leafs.clear();
Travers(this->root, this->Leafs);
Leaf_Num = Leafs.size();
FormatOut("Leaf Size", Leaf_Num);
}
//output file!!! Note: the class T should overload << operator
template<class T>
void QuadTree<T>::WriteLeafsNode(const char* filename){
this->Travers();
std::ofstream fout(filename);
for (TreeNodePosi<T> p : this->Leafs)
fout << p->element;
fout.close();
}

PanelTree 类

PanelTree类继承自QuadTree类,数据成员为自定义的Panel结构体,源码依赖开源矩阵库\(Eigen\)

/*
*PanelTree.h
*/
#pragma once
#include "QuadTree.h"
#include <iostream>
#include <iomanip>
#include <fstream>
#include "Eigen/Dense" #define FOUT_V(name) out << std::setw(20) << std::scientific << std::setprecision(4) << e.##name<<"," #define FOUT(name) out << std::setw(20) << std::scientific << std::setprecision(4) << e.##name(0, 0)<<","\
<< std::setw(20) << std::scientific << std::setprecision(4) << e.##name(1, 0)<<","\
<< std::setw(20) << std::scientific << std::setprecision(4) << e.##name(2, 0)<<"," template<class T> using Mat3 = Eigen::Matrix<T, 3, 1>; using string = std::string;
using ofstream = std::ofstream; struct Panel {
public:
Mat3<double> P1 = { 0,0,0 };
Mat3<double> P2 = { 0,0,0 };
Mat3<double> P3 = { 0,0,0 };
Mat3<double> P4 = { 0,0,0 };
Mat3<double> Centroid = { 0,0,0 };
Mat3<double> Normal = { 0,0,0 };
double ds, Fraction;
Panel(Mat3<double> p1, Mat3<double> p2, Mat3<double> p3, Mat3<double> p4);
Panel() {};
Panel(const Panel& e); //copy constructor
Panel& operator =(const Panel& e); //overload =
void Cal();
void Cal_Fraction(double eta);
friend ofstream& operator << (ofstream& out, const Panel& e) {
FOUT(Centroid); FOUT(Normal);
FOUT_V(ds); FOUT_V(Fraction);
FOUT(P1); FOUT(P2); FOUT(P3); FOUT(P4);
out << std::endl;
return out;
}
}; class PanelTree : public QuadTree<Panel>
{
public:
PanelTree(const Panel &root) :QuadTree<Panel>(root) {}
public:
bool IsContainFreeSurface(Panel& e, double t, double (*Eta)(const Panel&, double));
bool IsNearFreeSurface(Panel& e, double t, double (*Eta)(const Panel&, double), double up, double down);
//bool IsContainCircle(Panel& e, double t, Mat3<double>(*Circle)(const Panel&, double));
//bool IsNearCircle(Panel& e, double t, Mat3<double>(*Circle)(const Panel&, double), double r0);
virtual void Expand(TreeNodePosi<Panel> p) override;
void Expand_demo(TreeNodePosi<Panel> p, int Level, double t, double (*Eta)(const Panel&, double), double up, double down);
};
/*
*PanelTree.cpp
*/
#include "PanelTree.h" Panel::Panel(Mat3<double> p1, Mat3<double> p2, Mat3<double> p3, Mat3<double> p4) {
this->P1 = p1; this->P2 = p2; this->P3 = p3; this->P4 = p4;
this->Cal();//计算中心、ds、法相向量等参数;
this->Fraction = 0;
} Panel::Panel(const Panel& e) {
this->P1 = e.P1; this->P2 = e.P2; this->P3 = e.P3; this->P4 = e.P4;
this->Centroid = e.Centroid;
this->Normal = e.Normal;
this->Fraction = e.Fraction;
this->ds = e.ds;
} Panel& Panel::operator =(const Panel& e) {
this->P1 = e.P1; this->P2 = e.P2; this->P3 = e.P3; this->P4 = e.P4;
this->Centroid = e.Centroid;
this->Normal = e.Normal;
this->Fraction = e.Fraction;
this->ds = e.ds;
return *this;
} void Panel::Cal() {
Mat3<double> n1 = { 0,0,0 }, n2 = { 0,0,0 };
double Costheta = 0, Sintheta = 0;
Mat3<double> tmp = { 0,0,0 };
n1 = P1 - P3;
n2 = P2 - P4;
Costheta = n1.dot(n2) / (n1.norm() * n2.norm());
Sintheta = sqrt(1 - pow(Costheta, 2));
ds = 0.5 * (n1.norm() * n2.norm() * Sintheta);
Centroid = (P1 + P2 + P3 + P4) / 4.;
tmp = n1.cross(n2);
Normal = tmp.normalized();
} void Panel::Cal_Fraction(double eta) {
double f[4]{};
f[0] = this->P1(2, 0) - eta;
f[1] = this->P2(2, 0) - eta;
f[2] = this->P3(2, 0) - eta;
f[3] = this->P4(2, 0) - eta;
double fN = 0, fP = 0;
if (f[0] <= 0 && f[1] <= 0 && f[2] <= 0 && f[3] <= 0)
this->Fraction = 1.;
else {
if (f[0] >= 0 && f[1] >= 0 && f[2] >= 0 && f[3] >= 0)
this->Fraction = 0.;
else {
for (int i = 0; i < 4; ++i) {
if (f[i] >= 0) fP += f[i];
else fN += f[i];
}
this->Fraction = abs(fN) / (abs(fP) + abs(fN));
}
}
} bool PanelTree::IsContainFreeSurface(Panel& e,double t,double(*Eta)(const Panel&, double)){
double eta = Eta(e, t);
e.Cal_Fraction(eta);
return !((abs(e.Fraction - 1) < 1.0e-5) || (abs(e.Fraction - 0) < 1.0e-5));
} bool PanelTree::IsNearFreeSurface(Panel& e, double t, double(*Eta)(const Panel&, double), double up, double down){
double eta = Eta(e, t);
e.Cal_Fraction(eta);
double z = e.Centroid(2, 0);
return ((z - eta) >= down && (z - eta) <= up);
} //bool PanelTree::IsContainCircle(Panel& e, double t, Mat3<double>(*Circle)(const Panel&, double))
//{
// return false;
//}
//
//bool PanelTree::IsNearCircle(Panel& e, double t, Mat3<double> (*Circle)(const Panel&, double), double r0)
//{
// Mat3<double> pos = Circle(e, t);
// double dis = (e.Centroid - pos).norm();
// return abs(dis)<=r0;
//} void PanelTree::Expand(TreeNodePosi<Panel> p){
Panel subP1, subP2, subP3, subP4;
Mat3<double> c12 = { 0,0,0 };
Mat3<double> c23 = { 0,0,0 };
Mat3<double> c34 = { 0,0,0 };
Mat3<double> c41 = { 0,0,0 };
c12 = (p->element.P1 + p->element.P2) / 2.;
c23 = (p->element.P2 + p->element.P3) / 2.;
c34 = (p->element.P3 + p->element.P4) / 2.;
c41 = (p->element.P4 + p->element.P1) / 2.;
//subP1
subP1.P1 = p->element.P1;
subP1.P2 = c12;
subP1.P3 = p->element.Centroid;
subP1.P4 = c41;
//subP2
subP2.P1 = p->element.P2;
subP2.P2 = c23;
subP2.P3 = p->element.Centroid;
subP2.P4 = c12;
//subP3
subP3.P1 = p->element.P3;
subP3.P2 = c34;
subP3.P3 = p->element.Centroid;
subP3.P4 = c23;
//subP4
subP4.P1 = p->element.P4;
subP4.P2 = c41;
subP4.P3 = p->element.Centroid;
subP4.P4 = c34;
subP1.Cal(); subP2.Cal(); subP3.Cal(); subP4.Cal();
p->insertAsc1st(subP1); p->insertAsc2nd(subP2);
p->insertAsc3rd(subP3); p->insertAsc4th(subP4);
Leaf_Num += 3;
size += 4;
} void PanelTree::Expand_demo(TreeNodePosi<Panel> p, int Level, double t, double (*Eta)(const Panel&, double), double up, double down)
{
if (p->height >= Level) return;
bool IsMin = p->height <= 4;
bool IsContain = this->IsContainFreeSurface(p->element, t, Eta);
bool IsNear = this->IsNearFreeSurface(p->element, t, Eta, up, down);
if (IsMin||IsNear||IsContain) {
this->Expand(p);
this->Expand_demo(p->c1st, Level, t, Eta, up, down);
this->Expand_demo(p->c2nd, Level, t, Eta, up, down);
this->Expand_demo(p->c3rd, Level, t, Eta, up, down);
this->Expand_demo(p->c4th, Level, t, Eta, up, down);
}
}

C++: 基于四叉树数据结构的自适应网格(初探)的更多相关文章

  1. [转]基于四叉树(QuadTree)的LOD地形实现

    实现基于四叉树的LOD地形时,我遇到的主要问题是如何修补地形裂缝. 本段我将描述使用LOD地形的优势,我实现LOD地形的思路,实现LOD地形核心模块的详细过程,以及修补地形裂缝的思路. 首先,LOD地 ...

  2. 基于Unity的AR开发初探:第一个AR应用程序

    记得2014年曾经写过一个Unity3D的游戏开发初探系列,收获了很多好评和鼓励,不过自那之后再也没有用过Unity,因为没有相关的需求让我能用到.目前公司有一个App开发的需求,想要融合一下AR到A ...

  3. 【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-4 基于重要性采样的材质初探

     Preface 我们今天来把第三本书从开局到现在讲的一大堆理论运用到我们的框架中,那么今天我们首先将原始的材质改为基于重要性采样原理的材质 这一篇是代码工程中进行MC理论应用的初步尝试篇  Read ...

  4. 基于内存的关系数据库memsql初探

    背景 广告系统中,算法模型预估需要根据广告的实时转化统计结果,才能做出更精准的预估:同时,支持多维度聚合查询(例如按照广告各个不同层级维度,按照时间不同粒度的维度),并跨大区合并.一开始的版本是基于m ...

  5. 基于Unity的AR开发初探:发布AR应用到Android平台

    本文接上一篇,介绍一下如何通过Unity发布第一个AR应用至Android平台,在Android手机上使用我们的第一个AR应用. 一.一些准备工作 1.1 准备Java JDK 这里选择的是JDK 1 ...

  6. 基于Docker的UI自动化初探

    本文来自网易云社区 前言 一直以来,项目迭代的时间都是比较紧张的,开发加班加点coding,测试加班加点提bug.都说"时间像海绵里的水,挤挤总会有的"(当然这里的"挤挤 ...

  7. 三维CAD塑造——基于所述基本数据结构一半欧拉操作模型

    三维CAD塑造--基于所述基本数据结构一半欧拉操作模型(elar, B_REP) (欧拉操作  三维CAD建模课程 三维CAD塑造 高曙明老师  渲染框架 brep 带洞 带柄 B_REP brep ...

  8. 基于各种基础数据结构的SPFA和各种优化

    一.基于各种数据结构的SPFA 以下各个数据均为不卡SPFA的最短路模板:P3371 [模板]单源最短路径(弱化版)的测试时间 1.STL队列:用时: 1106ms / 内存: 8496KB #inc ...

  9. Redis学习笔记一:数据结构与对象

    1. String(SDS) Redis使用自定义的一种字符串结构SDS来作为字符串的表示. 127.0.0.1:6379> set name liushijie OK 在如上操作中,name( ...

随机推荐

  1. 微信小程序学习笔记四 自定义组件

    1. 自定义组件 类似Vue或react中的自定义组件 小程序允许我们使用自定义组件的方式来构建页面 1.1 创建自定义组件 类似于页面, 一个自定义组件由json wxml wxss js 4个文件 ...

  2. 关于通用Mapper new Example使用记录

    关于通用MapperExample使用记录 环境准备 需要集成 mybatis 的 generator 插件,方便自动生成 实体类和 mapper 类,还可以生成xml,不过一般我们都不用 xml b ...

  3. IMO 2021 第 1 题拓展问题的两个极值的编程求解

    IMO 2021 第 1 题拓展问题的两个极值的编程求解 本篇是 IMO 2021 第一题题解及相关拓展问题分析 的续篇. 拓展问题三: (I). 求 n 的最小值,使得 n, n + 1, ..., ...

  4. IoT边缘,你究竟是何方神圣?

    摘要:IoT边缘扮演着纽带的作用,连接边缘和云,将边缘端的实时数据处理,云端的强大计算能力两者结合,创造无限的价值. 本文分享自华为云社区<IoT边缘如何实现海量IoT数据就地处理>,作者 ...

  5. 5M1E,软件质量管理最佳解决方案

    - 如何做好一个产品? - 用户.需求.文化.价值.设计.流程,这些因素缺一不可.- 那么,如何做好产品的质量管理?- 人.机器.物料.方法.环境.测量,这些因素同样缺一不可.能够影响产品质量波动的因 ...

  6. JDK、JRE、JVM的基本介绍

    一 .Java三大版本 JavaSE 标准版(桌面程序.控制台开发-) JavaWE 嵌入式开发(手机.家电-) JavaEE 企业开发(web端.服务器开发-) 二.JDK.JRE.JVM区别 JD ...

  7. 从kratos分析BBR限流源码实现

    什么是自适应限流 自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load.CPU 使用率.总体平均 RT.入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流 ...

  8. MySQL——MySQL体系结构

    1.连接层 2.SQL层: (1)将接收到的SQL语句,解析成执行计划 (2)查询优化器 ---->选择最优的执行计划,交给存储引擎 (3)查询缓存: 缓存执行计划 (4)附加功能:权限. 语法 ...

  9. linux 常用命令脑图

  10. Windows中nginx多次启动的问题

    在Windows上做开发环境中的nginx服务器.为了使nginx在后台运行,使用如下命令来启停nginx: cd <nginx安装目录> # 开启nginx并在后台运行 start ng ...