- 写在之前
最近的一个2D项目中,要求对一个方块不断进行切割,切掉较小者,留下较大者,如此,便引出了一个问题:不同形状图形的数据的更新。而图形的数据结果的计算,就涉及到了计算机图形学相关的知识---三角化的概念边产生。
2. 问题拆分
a> 一个简单的图形(此处仅指2D)--如正方形,在代码中的表示方法
b> 三角化概念以及三角化的方法
3. 关于图形的表示方法
如下图,四个点的坐标信息保存在一个双向循环链表的结构中,这样,我们只需要知道一个点就可以按照我们预定的方向拿到剩下的所有点了。
可识别的图形(左)和转换后的数据示意图(右)
这样,根据三点不同时都在同一条直线的原则,一个平面图形的数据就构建完成了。
4. 三角化
因为计算机的GPU在绘制面的时候,是以三角形为基本单位的,所有的复杂图形都是大量的三角形拼接成的。这就是三角化概念的由来:将简单多边形分解成许多三角形,由这些三角形的顶点构成这个多边形。这个拆解的过程就是三角化的过程。简单举例,上图的矩形三角化之后有两个三角形:△P0 P1 P2、△P2 P3 P0,也就是点集合的索引:012、230的确定的过程就是三角化。
关于多边形的三角化算法,在这里,介绍一种比较简单容易理解的三角化法--耳切法。
耳切法适用于简单多边形的三角化,这里需要说明几个概念。
简单多边形:几何学中将互不相交的一些线段成对连接形成的闭合路径的平面图形,称为简单多边形。任意一个简单多边形至少有3个顶点。
“耳朵”(Ear):组成多边形的顶点,其相邻的两个点连成一条线段,这条线段完全落在这个多边形的内部,而且你会发现,这个顶点在该多边形中是一个凸顶点,将该点移除之后,该简单多边形的边数减少1个,重复这样的过程,最终将该多边形三角化。
以下便是对耳切法理论(Ear Clip)的说明和应用:
一个简单多边形(10条边)
定义上面的多边形点集合P:(0~9),并使用双向链表将这些点保存起来。接着,根据“耳朵”的定义,找出点集合P中所有的“耳”点(即符合条件的凸顶点),即初始状态下的“耳朵”集合E,所有的凸顶点(Convex Vertices)集合C,所有的凹顶点(即该点的两条边形成的内角大于180度,称Reflex Vertices)R。得到初始状态 E={3,4,6,9},C = {0,1,3,4,6,9},R = {2,5,7,8}。
删掉E中的顶点3,得到第一个三角形 T0 = {2,3,4},如此,移除顶点3之后,如下图所示:
移除顶点3后的多边形(9条边)
之前顶点2是凹顶点,耳点3移除之后,仍然是凹顶点。顶点4仍旧是耳点,所以更新后的耳点集合E={4,6,9},凸顶点C={0,1,4,6,9},凹顶点R集合没发生变化。
移除耳点4之后,得到第二个三角形 T1 ={2,4,5},顶点5现在变成了耳点被添加进E集合中:E={5,6,9},C= {0,1,5,6,9},R = {2,7,8}。(顶点2处的内角稍微大于180度)。如下图:
图3 移除顶点4后的多边形(8条边)
移除耳点5,此时顶点6仍然是耳点,E = {6,9},但顶点2的内角小于180度,顶点2成为了凸点被添加到C集合中:C={0,1,2,6,9},R = {7,8},此过程产生第三个三角形T2 = {2,5,6}。如图所示:
图4 移除顶点5之后的多边形(7条边)
移除耳点6,此时一直是凹顶点的2成为了耳点被添加到E中,E = {9,2},凹顶点集合R不变,凸顶点集合C = {0,1,2,9},此过程产生第四个三角形:T3={2,6,7}。如图所示:
图5 移除顶点6之后的多边形(6条边)
移除耳点9,此时顶点0和8变成了耳点被添加到E中:E = {0,2,8},凹顶点R中,顶点8已不是凹顶点,故R = {7},凸顶点 C ={0,1,2,8}。由此产生第五个三角形 T4 = {8,9,0}。如下图所示:
图6 移除顶点9之后的多边形(5条边)
移除耳点0,E集合变为 E = {1,2,8},C = {2,8},R = {7},由此产生第六个三角形T5={8,0,1}。如下图所示:
图7 移除耳点0之后的多变形(4条边)
移除耳点2,剩下三个点,自然到了三角化的最小单元,作为迭代结束条件,产生第七个三角形并记录下来: T6 = {1,2,7},加上最后剩下的三角形作为第八个三角形 T7 = {1,7,8},并退出迭代,整个三角化过程完成;同时,R为空,C={1,7,8}。如下图所示:
图8 移除耳点2之后的多边形(3条边)
图9 被三角化之后的完整多边形
从以上的过程,我们发现,每次移除一个点,其多边形的边数必定会减少1,直至剩余3个点。整个迭代中,以移除某个点之后,剩下的点是否等于3为结束条件,并不断更新点集合E、C、R。这里的三个点集合中, 其中集合C和集合R在整个迭代过程中只是一个辅助计算结果,原文中,作者并未对该两个集合的作用做出解释。这个问题等到了最后在讨论。
以上的迭代过程中,必须要知道一下的几个知识,才可以很好的理解这个迭代算法的含义:
1> 怎样判断一个顶点为凸点?根据对凹顶点的定义,该点处相邻的两条边组成的多边形的内角大于180度,由此可以用向量叉乘得到:的值小于0。其实对这两个向量叉乘的结果取绝对值,得到的结果是这个三角形的面积。
2> 怎样判断一个顶点为凹点?根据对凸顶点的定义,该点处相邻的两条边组成的内角小于180度,同样的值大于0。
3> 怎样判断一个顶点为“耳”点?关键是线段是否都落在多边形内,并且与其他边不相交,即:该点处的内角小于180度,即该点是否包含在凸点集合C中;
该点以及由该点附近两个点组成的三角形中,不包含多边形的其他顶点,Point In Polygon(PIP)问题的解法:
P点与∆ABC的位置示意图
解法1:面积法,如果P在三角形内,那么由P与其他三个点构造的三个三角形的面积之和等于原来三角形的面积,如在三角形外部,则其面积之和肯定大于原三角形面积。
解法2:内角和法,∠BAP + ∠CAP +∠CBP+ ∠ABP + ∠ACP + ∠BCP = 180度,那么P点落在了三角形内部。如果内角和大于180度,那么P点落在三角形外部;如果内角和小于180度,那么P点落在∆ABC平面外,即不在∆ABC内。
解法3:同侧法,即不管顺时针方向还是你是正方向,以其中一个点为起点到下一个点的过程中,如果点P始终在该边的同一侧(左或右),即P点在内部,否则在外部。那么又要用到向量的叉乘。如果选择顺时针方向,即A -> C -> B,相反,如果是逆时针方向,叉乘结果大等于0。
解法4:射线法,在点P处发射沿任意方向(一般计算的时候水平的方向)的射线,该射线跟多边形的交点个数为奇数,则P点在内部,反之在外部。但是也有一定的问题,如下图,其中那条粗线指出了该方法的问题,当这条射线正好跟多边形的一个顶点相交。对于这种情况,在计算之前判断该点是否落在要选定的射线方向上,如果在,排除该点不计,否则进入下一步计算判断。
点与多边形位置示意图
5. 算法实现
public static int[] Triangulate(List<Vector2> m_points)
{
List<int> indices = new List<int>();
int n = m_points.Count;
if (n < 3)
return indices.ToArray();
int[] V = new int[n];
if (Area(m_points) > 0) // 将所有顶点按照顺时针方向排列
{
for (int v = 0; v < n; v++)
V[v] = v;
}
else
{
for (int v = 0; v < n; v++)
V[v] = (n - 1) - v;
}
int nv = n;
int count = 2 * nv;
for (int v = nv - 1; nv > 2;)// 开始迭代
{
if ((count--) <= 0)
return indices.ToArray();
int u = v;
if (nv <= u)
u = 0;
v = u + 1;
if (nv <= v)
v = 0;
int w = v + 1;
if (nv <= w)
w = 0;
if (Snip(u, v, w, nv, V, m_points))
{
int a, b, c, s, t;
a = V[u];
b = V[v];
c = V[w];
indices.Add(a);
indices.Add(b);
indices.Add(c);
for (s = v, t = v + 1; t < nv; s++, t++)
V[s] = V[t];
nv--;
count = 2 * nv;
}
}
indices.Reverse();
return indices.ToArray();
}
6.文章出处说明
Triangulation by Ear Clipping,David Eberly, Geometric Tools, Redmond WA 98052
来源:CSDN
作者:磊_or
链接:https://blog.csdn.net/THUNDERDREAMER_OR/article/details/104184589