平面划分
0、DCEL
是什么
-
DoubleConnectEdgeList 双向链连接表
-
DCEL用于表示平面划分
- 中心线
- 得老内三角划分
- 维诺图
- 等大多数平面划分结构
-
它连接这三种元素的关系:顶点(点Point3d)、边(有序的两个点Tuple<Point3d, Point3d>)、面(List<Tuple<Point3d, Point3d>>(可以转化为Polyline))
-
对于每一个面,内部的vector顺序都是逆时针的,最外面的外包框是顺时针的。
干什么
-
1、对边界进行遍历寻找(寻找外包框)
- 从任意一个边进行顺时针走,直到遇到已经走过的点,此点之前的另一条路线全删除,留下的就是外边框。
-
2、通过一个面获得其旁边的面
- 对于这个面上的每一条边,获取反向边,通过DCEL获取这个反向边所在的多边形(面)
-
3、通过一个顶点获取其旁边所有链接的边
- DCEL结构自带
-
4、拓展:给定一个DCEL描述,有一条单向线穿过这个图形,怎么找到其穿过的面?
- 答:这里给出三角剖分的做法(其他的可以类比),首先找到随意一个和线相交的点(遍历外框,kdtree,四叉树等),找这个面上其他和其相交的面,巴拉巴拉之字形不写了
-
5、遍历一个面
- 输入是一个边,通过这个边终点作为下一个边起点找到逆时针第一个边作为下一条边,loop直到遇到最初的起点。形成闭环。
-
6、加一个顶点(在某条边上)
- 一条对边变成两对对边,修改对应的DCEL
-
7、加一条边
- 平面分割,并修改DCEL结构
-
8、。。。。。。
1、维诺图
是什么
-
定义
- 平面中一堆点,每个点有自己的领域,领域中任意位置到这个基点的距离都要小于到其他点的距离(很自然想到垂直平分线、抛物线)
- 维诺图本身是根据n个基点构造的,结果中最多3n-6条边,最多2n-5个顶点,是线性的
-
算法
-
复杂度
- 扫描线算法:是一种基于排序的算法,类比可得,最低时间复杂度O(NlogN)
-
小流程
-
空间O(N)
- 扫描线从图像的一边到另一边(假设为从上到下)
- 扫描线会在遇到(扫描到)某个点(event point)时触发事件
- 海岸线下移(y减小),每遇到一个基点,都可通过树logN找到其所在位置并加入
-
事件触发
-
基点事件(site event)
- 水平的扫描线在前,当遇到event point的时候,触发事件:海岸线将出现一个新的弧,弧相交的断点(brakpoint)处(由1边2)维诺边发生分叉:推论:因此海岸线弧的数量不超过2 * n - 1
-
圆事件( circle event)
- 默认情况下随着扫描线的推移,海岸线中的某条弧收缩为一个点(随后衍生为维诺边),海岸线生成的维诺图边界将发生合并;题外话:对于弧线收缩为一个点(维诺顶点):这个点是它所在维诺图细胞核和两边细胞细胞核构成的圆的圆心,而这个圆是空圆。
-
-
海岸线(beach line)
-
海岸线的每条弧都是抛物线线
- 每个海岸线弧线对应一个事件队列中的事件
-
海岸线处于上方点和下方平直的扫描线中央,绝对意义的中间:海岸线上任意一点到已经扫描的点中最近那个点的距离与到海岸线(垂线)的距离是一样的
-
海岸线的提出是为了保证海岸线扫过的部分不会再改变,如果只用平直的一条扫描线,还会对扫描的部分进行维诺图修改。
-
海岸线的直观结果是每个扫描过的点和扫描线形成的抛物线的合并然后取最小的值(最低的部分)
-
-
用到的数据结构
-
DCEL: 双向链连接表 doubly connected edge list
- 每个树内部节点对应一个DCEL边
-
平衡二分查找树:存储海岸线,也就是status structure,每个叶子记录每个弧(同海岸线从左向右一一对应)(以及这个弧对应的基点),其他内部结点表示断点(存储基点对)
-
优先队列:用来存储事件队列(每个点的y坐标就是优先级)
-
邻接三元组:存储海岸线上相邻的三条弧
-
-
退化情况:
- 扫描线上出现多个事件(边界:最初就有这种事情,某个上方将没有海岸线,要单独处理)
- 圆事件时候多个基点共圆,可以事后处理掉0长度的边(合并)
- 三个连续共线(扫描线)基点,永不会发生共线
-
-
干什么
-
用于将空间分区,每个区域中的位置到所在区域基点距离最近
-
拓展用途
-
中心线(骨架)
- 用于解决形状分析
-
拓展
-
拓展算法
-
对于线段集而非点集的维诺图
- 用于运动规划
-
最远点维诺图
-
2、some临近图
一系列的具有临近关系的图
-
DT是他们的超图
-
可以先计算出DT然后在计算出这些重要的图
-
整体关系
- DT >= GG >= RNG >= EMST >= NNG
0、Delaunay Triangulation(DT)
-
定义
-
德劳内三角剖分
-
任意相邻两点构成的直线,定是某空圆的弦
-
唯一生成的
-
同样是三角剖分,得老内剖分的结果:是一种使最小角达到最大的三角剖分
-
算法
- //德劳内三角剖分 RIC算法
#include
using namespace std;
- //德劳内三角剖分 RIC算法
-
// 代表一个点
struct Point {
int X;
int Y;
};
// 代表一个三角形
struct Trangle {
Point a, Point b, Point c;
};
// 返回德劳内图形DT中某点P所在的三角形 (会用到DCEL结构)
T TriangleContaining (Polyline DT, Point P) {
//这里用一个桶来存储其中所有的点,每个桶的外边界就是一个已经剖分过的三角形
}
//找到与abc构成三角形的右边的三角形中ab边对应的点x
Point RightSite(Point a, Point b) {
//使用DCEL 既b\a边所在三角形中的另一个顶点
//没有返回null
}
//查看x是否在p、a、b三点构成的内接圆中
int InCircle(Point p, Point a, Point b, Point x) {
//一个行列式 关于 a\b\x\p的四阶行列式
//优点:没有除法,速度O(1)
return f(pabx); //大于0表示在圆内,小于表示在圆形外
}
//用ab替换px
void FlipEdge(Point a, Point b, Point p, Point x) {
//这里面要操作的结构是要改变上面的存储桶三角形所包含点的位置
//原本四边形p a x b可分割为T(p, a, b)&T(a, b, x),现在分割为T(p, a, x) & T(p, b, x)
}
//测试固定a、b两点,与p构成的外接圆是否为空圆(圆内不存在任何点)
//如果不为空圆,则修改为最优(使用了递归)
void STest(Point p, Point a, Point b) {
//获取从a到b直线右边的那个点(利用DCEL的双向结构)
//(与a、b所在线段构成三角形(会有左右两个)右边的那个顶点)
Point x = RightSite(a, b);
//如果不存在点x在ab右侧,即ab为多边形的边界,最简单的情况,不用处理
if(!x) return;
//查看x是否在p、a、b三点构成的内接圆中
if(InCircle(p, a, b, x) > 0) {
//用ab替换px
FlipEdge(a, b, p, x);
//测试新增加的值得怀疑的两条线
STest(p, a, x);
STest(p, x, b);
}
}
//德劳内三角形的递归做法
//测试并修复图中可疑的pa、pb、pc线(因为可能破坏结构不符合空圆要求)
void SwapTest(Point p, Point a, Point b, Point c) {
STest(p, a, b);
STest(p, b, c);
STest(p, c, a);
}
//德劳内三角剖分的递推做法
//只需改变递归做法中的反转测试
SwapTest(p, a, b, c) {
queue q = {(a, b), (b, c), (c, a)};
wahile(!q.empty()) {
(a, b) = q.dequeue();
Point x = RightSite(a, b);//find t(a,x,b) on opposite side(using DCEL)
if(!x) connect; //in case x doesnot exist
if(InCircle(p, a, b, x)) { //if x voilates in-circle condition
FlipEdge(a, b, p, x);//replace ab with px and
Q.enqueue((a, x), (x, b));//insert the 2 new triangles
}
}
}
//在已经正确的德劳内图形中插入点P
void Insert (Point p) {
//首先找到包含P这个点的三角形(P在哪个三角形内部)
Trangle T(a, b, c) = TriangleContaining(DT, p);
//链接pa、pb、pc三条线 并插入到德劳内三角剖分图形中
connect(p, a);
connect(p, b);
connect(p, c);
//测试并修复图中可疑的pa、pb、pc线(因为可能破坏结构不符合空圆要求)
SwapTest(p, a, b, c);
}
int main() {
Point a, Point b, Point c;
//假设原本是一个超巨大的三角形
Polyline DT(a, b, c);
//不断地加入点构成更多的三角形。
//类似于建堆
for(int i = 2; i < points.size(); ++i) {
Insert(points[i]);
}
return 0;
}
-
关系
- DT是维诺图互为对偶图
0.5、Convex Hull
-
定义
- 凸包
-
关系
- 凸包会包含所有的点
- CH是DT的子图
1、Gabriel Graph(GG)
-
定义
- 任意两相连点构成的直线为直径,这个圆是空圆
-
关系
- GG是DT的子图
- GG是连通图
2、Relative Neighborhood Graph(RNG)
-
定义
- 相对邻居图
- 任意两相连点P、Q,p为圆心半径到q的圆,以及q为圆心同样半径的圆,相交部分是空的(不含有其他点)
-
关系
- RNG是GG子图
- RNG是连通图
3、Euclidean Minimum Spanning Tree (EMST)
-
定义
-
欧式最小生成树
-
Euclidean
- 所有点都应当属于一个欧氏空间
- 任意两点之间的权重就是其欧氏距离
-
使所有点 连通且最权重最小的生成树
-
如果直接去求,那么点之间两两都有距离,就是一个完全图(N^2条边),可以通过DT使边减少为N的常数倍
-
然后依旧是正常的最小生成树算法
- 克鲁斯卡尔O(n * n * logn)
- 普利姆O(n * n)
-
-
-
关系
-
EMST是RNG的子图
- 反证法
-
EMST是连通图
-
EMST没有环路
-
4、Nearest Neighbour Graph(NNG)
-
定义
- 最近邻图
- 每个点指向离它最近的那个点
-
关系
- NNG是EMST的子图
- NNG可能不连通(Forest)
- NNG有向
5、拓展
-
Minimum Weighted Triangulation(MWT)
-
定义
- 最小权重三角划分
- 每个三角形三边之和尽可能的小
-
关系
- 此算法和德劳内三角划分区分开来
- 这个问题是NP-complete的
-
-
Euclidean Traveling Salesman Problem
-
定义
- 欧式旅行商问题
- 是一种NP—Hard问题
-
关系
-
但是可以快速获得一个次优的解,这个次优近似解最差也是最优解的两倍以内
-
算法
- 首先生成EMST,利用DCEL遍历EMST并生成环路,遇到见过的就跳过
-
-
ps:有一种1.5倍次优解,但是算法难度较大
-
-
3、非均匀网格
定义
- 可以引入新的点(Steiner Point)生成三角剖分
- 称为Steiner Triangulation(Steiner三角剖分)
目的
- 让三角剖分后的结果每个三角形的每个角度都在45 - 90中
- 且三角形数量少
- 且三角形大小变化均匀
基于四叉树的非均匀网格生成算法:
-
名词
- 每个结点代表一个正方形,结点的四个孩子分别代表其父亲正方形的四个象限(quadtree subdivision,四叉树划分)
- 有些边界上的结点可能多于4个顶点,仍称之为正方形;正方形顶点称为角顶点(corner vertex)
- 连接两个相邻顶点的为侧边side,完全落在一个边上的是edge;邻居:两个正方形共用一条边
-
用四叉树保存平面上的一组点;只要某个正方形中包含的点多于一个,就递归进行分割(有点类似德劳内三角划分的桶结构)。
-
分析
- 初始正方形:点集的最小包围正方形
- 四叉树会很不均匀,但有极限:
- 深度不超过log(s(初始正方形边长)/c(各点中最近距离)) + 3/2
- n个点,d深度的四叉树,节点数为O((d + 1)*n),可在O((d + 1)*n)时间构造出来
-
邻居查找
- 某个正方形的邻居要么是当前爹爹的孩子(同系),要么是它爹爹邻居的后代或者他爹爹邻居本人
- 所以可以得到:邻居查找时间复杂度为O(d(深度) + 1)
-
以上的四叉树很不平衡(面积很大的正方形与面积很小的正方形相邻)
平衡四叉树
-
引入一种四叉树变种:平衡四叉树(balanced quadtree):任何邻居正方形的边长不超过两倍
-
只是需要一个平衡化的函数,在之前的四叉树基础上操作即可
-
算法
- //将四叉树变为平衡四叉树
quadtree BalanceTheQuadtree(quadtree qdtree) {
queue leavesQ;
while(!leavesQ.empty()) {
leaf topLeaf = leavesQ.top();
if(needSplit(topLeaf ,qdtree)) {
Block blockTopLeaf = LeaveToBlock(topLeaf);//将topLeave生成内部点;
qdtree.AddLeave(sonA, sonB, sonC, sonD);//其四个孩子都是叶子(是其四个象限);
if(haveaPoint(blockTopLeaf)) {//如果topLeave中有一个点
//从blockTopLeaf中取出,存入对应的新的叶子
}
//将四个新叶子插入leavesQ;
//检查topLeaf的各个邻居,看是否需要分割
//如果某些邻居需要分分割,将其加入到leavesQ
}
}
return qdtree;
}
- //将四叉树变为平衡四叉树
-
-
//是否需要将topLeaf进行分割
bool needSplit(leaf topLeaf, quadtree qdtree) {
通过邻居查找算法,查看附近的边是否是自己的一半以内
}
-
拓展
-
八叉树
- 渲染/光线追踪
- 弹道轨迹
- 总之就是位置确定相关
-
从四叉树到网格
-
构造四叉树的递归终止条件:知道正方形不在于任何目标边界相交,或者正方形边长缩短到一个单位,就不再继续细分。
-
一般考虑:对于内部没有边穿过的正方形,为他们添加一条对角线即可。但这样并不是一致的。
-
平衡化四叉树后按照如下算法划分三角形:
-
两种情况:
- a、侧边内部不含顶点,直接一条对角线划分正方形
- b、侧边有顶点,则根据平衡结果一定是侧边中点,再此正方形中心加入Steiner点,然后链接边上所有顶点。
-
小功能
• 在点集中获取临近某个多边形的点
-
• A、通过维诺图获取
- • Function:VoronoiDiagramNearPoints
- • 原理:做维诺图,和目标图形相交的图形所包含的点就是近点
- • 根据维诺图的性质:目标图形任意点到这些近点的距离一定小于到其他点的距离
-
• B、通过带约束的德劳内三角划分获取
- • Function:ConfirmingDelaunay TrianglationNearPoints
- • 原理:在做德劳内三角划分的时候,如果点距离约束较近,直接做垂线
-
• A\B比较
- • 多数情况下,A的结果数量较少,B结果数量较多
• 找到任意闭合多边形的凸点(边)和凹点(边)
-
• Function:OutPoints
-
• 调用Func:PointClassify
- • 作用:将多边形上的点分为凹凸两类
- • 原理:方向判断
-
• 原理:先将点分为凹凸两类,通过凸包辅助确定哪一类是凸点
-
• 其他:此函数可以顺便获得多边形的凹边、凸边(函数中注释部分)
• 中心线
-
• 简化中心线
- • Function:CLSimplify
- • 删除当前中心线上所有的最远端树枝(叶子)
-
• 循环删除最远端树枝(可选次数)
-
• Functioon:CutBrancheLoop
-
• 调用函数:CutBranche
- • 适配循环删除逻辑的删枝方法
- • 原理:在原始中心线的每一个叶子进行回溯删除,直到分叉位置
-
-
• 获取闭合多边形上的折角(分叉点)
- • Function:WallEdgePoint
- • 如同一棵大树(带树根),根据树的粗细获得树的不同分层和分叉点
• 网格生成
-
• 通过维诺图生成网格
-
• Function:VoronoiDiagramConnect
-
• 调用函数:ConnectNeighbor
- • 找到某点相邻(两维诺图cell的边重合)的点并连接
-
• 原理:找到包含每个点所在的维诺图的cell,连接相邻的距离最长的4个点;最后保留有compair的成对的边
-
-
• 通过得劳内三角划分生成网格
- • Function:DelaunanyTriangulation Connect
- • 原理:对于三角划分后的每个三角形,仅保留最短的两条边;最后保留有compair的成对的边
-
• 通过有限制的德劳内三角划分生成网格
- • Function:ConformingDelaunanyTriangulation Connect
- • 备注:此函数由于笔者太菜,没写对
• 网格处理
-
• 分割某个多边形为多个三角形、四边形、五边形
-
• Function:SplitPolyline
-
• 调用函数:Split2Order
- • 从多边形特定的位置分割,使之成为两个
-
-
• 合并两个有共边的多边形为一个
- • Function:MergePolyline
-
• 将连接度数超过4个的点连接度数减少为1个
- • DeleteConnectUpToFour
- • 删除最小夹角中度数距离最短的那条线
• DCEL相关
-
• 维诺图、中心线、德劳内三角划分得到的输出的原生结果均可转化为双边结构
-
• 生成结构
-
• Function:BuildPolygons
-
• 调用函数:
-
• GetNextConnectPoint
- • 获取为生成多边形下一个将要连接的点
-
• Tuples2Polyline
-
• 将一堆乱序的首尾相接的有向线生成连接在一起生成多段线,如果多段线首位一致,则闭合为多边形
-
• 调用函数:OrderTuples
- • 将一堆乱序的线排序
-
-
-
• 将一堆首尾相连的DCEL线组合成多边形;同时建立一种结构:可通过一条有向边来得知其所在的多边形
-
• 业务相关
-
• 有优先级的获取边界上要连接的点
-
• Function:PriorityBorderPoints
-
• 调用函数:BestConnectPt
- • 找到某点的最佳连接点
-
-
• 通过方向获取点
- • Function:GetPointByDirection
-
• 通过方向获取最近的点
- • Function:Get ClosestPointByDirection
XMind: ZEN - Trial Version