下面介紹Delaunay三角剖分算法:
一. 生成凸包
生成凸包的算法在我的另一個博文有詳細介紹
二. 凸包切分
在凸包鏈表中每次尋找一個由相鄰兩條凸包邊組成的三角形,在該三角形的內部和邊界上都不包含凸包上的任何其它點。將這個點去掉后得到新的凸包鏈表。重復這個過程,直到凸包鏈表中只剩三個離散點為止。將凸包鏈表中的最后三個離散點構成一個三角形,結束凸包三角剖分過程,這一過程只對凸包中的點進行處理。
List<Triangle> DivideHull(List<Point> pts)
{
List<Point> hpts;
for(int i = 0; i< pts.size() ; i++)
hpts.push_back(pts[i]);
List<Triangle> tins; //保存得到的三角形
while(hpts.size() >2) //一直到最后剩下離散的三個點
{
int tag = 0;
float minangle = 180; //每次構成相鄰的邊,優先找角度最小的三角形
for(int i = index; i< hpts.size() ; i++)
{
float tri_angle = 180.0;
if(i == 0){
tri_angle = Gemetry::angle3D(hpts.last(),hpts[i],hpts[i+1]);
}
else if(i == hpts.size()-1){
tri_angle = Gemetry::angle3D(hpts[i-1],hpts[i],hpts[0]);
}
else{
tri_angle = Gemetry::angle3D(hpts[i-1],hpts[i],hpts[i+1]);
}
if(tri_angle < minangle)
{
tag = i;
minangle = tri_angle;
}
}
int tagb = tag-1;
int tage = tag+1;
if(tag == 0)
tagb =hpts.size()-1;
if(tag == hpts.size()-1)
tage = 0;
tins.push_back(Triangle(hpts[tagb],hpts[tag],hpts[tage]));
hpts.removeAt(tag);
}
return tins;
}
三. 離散點內插
算法流程:
1、選擇一個尚未構成三角形的離散點
2、在已經生成的三角形中,找出該離散點的三角形,有兩種情況:在三角形內部和在三角形邊上。
3、如果離散點在三角形的內部,則將該三角形以及三角形的邊刪除,然后將三個頂點以及離散點分別連接,形成三個新的三角形。如果離散點在三角形的邊上,記錄點所在的邊E,根據拓撲關系,找出該邊的左右相鄰三角形T1,T2,添加四條新邊和四個新三角形NT,刪除T1,T2以及邊E,基本上操作是相同的。
對於新生成的三角形,不能直接加入到三角形數組里,需要挨個對其邊進行空外接圓檢測。具體做法為:對於新生成的三角形的邊E,找出該邊相鄰的兩個三角形,判斷該邊一側的對角的頂點是否位於另外一個三角形的外接圓的里面。如果是,則將邊E刪除,再將兩個對角連接起來,形成兩個新的三角形T3,T4。
對於新生成三角形T3,T4,同樣需要進行空外接圓檢測,一直迭代下去,直到所有新生成的三角形都通過空外接圓檢測為止。
4、重復1、2、3,直到所有非凸殼離散點都插入完為止。
List<Triangle> getDelaunay(List<Triangle> hulltins, List<Point> pts)
{
for(int i = 0; i<pts.size(); i++)
{
List<Triangle> delTin; //保存要刪除的三角形
for(int j =0; j< hulltins.size(); j++)
{
if(hulltins[j].isInTriangle(pts[i]) == true) //該點在三角形內部
{
delTin.push_back(hulltins[j]);
}
if(hulltins[j].isOnTriangle(pts[i]) == true){ //點在三角形邊上
delTin.push_back(hulltins[j]);
}
List<Line> borderLines;//保存離散點的相鄰邊
List<Triangle> newTri;//保存新得到的三角形,之后用於檢測空圓
if(delTin.size() == 1) //在三角形內部
{
Line l1 = delTin[0].l1;
Line l2 = delTin[0].l2;
Line l3 = delTin[0].l3;
borderLines.push_back(l1);
borderLines.push_back(l2);
borderLines.push_back(l3);
for(int j = 0; j< borderLines.size();j++)
{
newTri.push_back(Triangle(borderLines[j].p1,borderLines[j].p2,pts[i]));
}
hulltins.removeOne(delTin[0]);
}else if(delTin.size() == 2)//在三角形邊上,則要找到四條相臨邊,去掉重復的邊
{
Line l[3];
l[0] = delTin[0].l1;
l[1] = delTin[0].l2;
l[2] = delTin[0].l3;
int index = 0;//保存重復邊的下標
//四條相臨邊,去掉重復的邊
for( int m =0;m< 3; m++)
if(delTin[1].containsLine(l[m]) ==0)
{
borderLines.push_back(l[m]);
}else
{
index = delTin[1].containsLine(l[m])-1;
}
for( int m =0;m< 3; m++)
{
if(m!=index)
borderLines.push_back(delTin[1].l[m]);
}
for(int j = 0; j< borderLines.size();j++)
{
newTri.push_back(Triangle(borderLines[j].p1,borderLines[j].p2,pts[i]));
}
hulltins.removeOne(delTin[0]);
hulltins.removeOne(delTin[1]);
}
// 對新得到的三角形進行檢測空圓
delTin.clear();//之后保存需要刪除的新生成的三角形
for( int s = 0; s< newTri.size(); s++){ //每一個三角形
for(int j = 0; j< 3 ; j++){ //每一條邊
Line line = newTri[s].l[j];
for( int m = 0; m< hulltins.size() ; m++)//在三角形數組里搜索包含該邊的三角形
{
{
if(hulltins[m].containsLine(line) )//如果三角形包含該邊
{
Circle tinCircle = Circle::genTriCircle(hulltins[m]);
if(tinCircle.isInCircle(vec3(pts[i]))) //如果當前離散點在三角形的外接圓上
{
delTin.push_back(newTri[s]); //后面要刪除該三角形
int x = hulltins[m].containsLine(line)-1;
//對於這個三角形的三個邊,去掉重復邊還有兩個邊,分別於當前離散點構成一個新的三角形
for( int k = 0; k<3;k ++)
{
if(x!=k){
newTri.push_back(Triangle(hulltins[m].l[k].p1,hulltins[m].l[k].p2,pts[i])); //對於
}
}
hulltins.removeAt(m);//去除該三角形
}
}
}
}
}
}
//最終把新的三角形加入到三角形數組里
for( int m =0; m< newTri.size() ; m++)
{
hulltins.push_back(newTri[m]);
}
for( int m = 0; m< delTin.size() ; m++)
{
hulltins.removeOne(delTin[m]);
}
}
return hulltins;
}
