三维空间的三角剖分( 3D Delaunay Triangulated graph)第二部分:剖分三维空间
對(duì)三維空間進(jìn)行三角剖分的大體邏輯思路和流程同二維空間的三角剖分是一樣的,二維的代碼實(shí)踐過程可以前往本專欄的第一篇文章:二維 二維空間的三角剖分。
在理清楚二維平面下三角剖分的思路之后,我們嘗試將其中的一些概念升級(jí)一個(gè)維度。
我們先來分析Delaunay三角剖分從二維空間到三維空間過程中的一些變化:
1.剖分成若干個(gè)三角形單元——剖分成若干個(gè)四面體單元
2.根據(jù)x、y排序點(diǎn)集——根據(jù)x、y、z排序點(diǎn)集
3.根據(jù)排序好的點(diǎn)集構(gòu)建超級(jí)三角形——根據(jù)排序好的點(diǎn)集構(gòu)建超級(jí)四面體
4.構(gòu)造用于緩存邊數(shù)據(jù)的列表——構(gòu)造用于緩存面數(shù)據(jù)的列表
5.計(jì)算每個(gè)三角形的外接圓,判斷該三角形與點(diǎn)之間的空間關(guān)系——計(jì)算每個(gè)四面體的外接球,判斷該四面體與點(diǎn)之間的空間關(guān)系
6.最終得到一個(gè)三角形列表——最終得到一個(gè)四面體列表
先上效果圖(圖為Unity中用Gizmos將每個(gè)四面體單元頂點(diǎn)連線畫出來的預(yù)覽圖):
?
嘗試加入障礙物AABB包圍盒后進(jìn)行剖分,并用Mesh網(wǎng)格渲染顯現(xiàn):
?
?
一、輸入與輸出
輸入:
/// <summary>/// 點(diǎn)集/// </summary>public List<Vector3> _vertices = new List<Vector3>();輸出:
/// <summary>/// 四面體列表/// </summary>List<Tetrahedron> _tetrahedron = new List<Tetrahedron>();二、所需要自定義的類:
1.Surface類:存放四面體每一個(gè)面的數(shù)據(jù)
用三個(gè)Vector3來定義一個(gè)三角形面:
public class Surface{public Vector3 P1;public Vector3 P2;public Vector3 P3;public Surface(Vector3 P1,Vector3 P2,Vector3 P3){this.P1 = P1;this.P2 = P2;this.P3 = P3;}}2.Tetrahedron類:存放每個(gè)四面體單元的數(shù)據(jù)
首先是參數(shù)和構(gòu)造函數(shù):
參數(shù)包括四面體的四個(gè)頂點(diǎn)和四個(gè)三角形面、外接球球心、外接球半徑以及一個(gè)判斷是否為可用四面體的bool值;
構(gòu)造函數(shù)傳入的參數(shù)為一個(gè)頂點(diǎn)和一個(gè)三角形面,即以一個(gè)頂點(diǎn)和一個(gè)已存在的三角形面生成一個(gè)新的四面體;
public class Tetrahedron{public Vector3 P1;public Vector3 P2;public Vector3 P3;public Vector3 P4;public Vector3 Center;public double R;public Surface E1;public Surface E2;public Surface E3;public Surface E4;public bool isBad;public Tetrahedron(Vector3 V, Surface P){P1 = V;P2 = P.P1;P3 = P.P2;P4 = P.P3;GetTetrahedronExcenterRadius();SurfaceValue();} }其次是四面體類中的一些計(jì)算函數(shù):
(1).計(jì)算該四面體的外接球:
/// <summary>/// 計(jì)算四面體的外接球/// </summary>public void GetTetrahedronExcenterRadius(){float x1 = P1.x; float x2 = P2.x; float x3 = P3.x; float x4 = P4.x;float y1 = P1.y; float y2 = P2.y; float y3 = P3.y; float y4 = P4.y;float z1 = P1.z; float z2 = P2.z; float z3 = P3.z; float z4 = P4.z;float a11 = x2 - x1;float a12 = y2 - y1;float a13 = z2 - z1;float b1 = (float)0.5 * ((x2 - x1) * (x2 + x1) + (y2 - y1) * (y2 + y1) + (z2 - z1) * (z2 + z1));float a21 = x3 - x1;float a22 = y3 - y1;float a23 = z3 - z1;float b2 = (float)0.5 * ((x3 - x1) * (x3 + x1) + (y3 - y1) * (y3 + y1) + (z3 - z1) * (z3 + z1));float a31 = x4 - x1;float a32 = y4 - y1;float a33 = z4 - z1;float b3 = (float)0.5 * ((x4 - x1) * (x4 + x1) + (y4 - y1) * (y4 + y1) + (z4 - z1) * (z4 + z1));float temp = a11 * (a22 * a33 - a23 * a32) + a12 * (a23 * a31 - a21 * a33) + a13 * (a21 * a32 - a22 * a31);float x0 = ((a12 * a23 - a13 * a22) * b3 + (a13 * a32 - a12 * a33) * b2 + (a22 * a33 - a23 * a32) * b1) / temp;float y0 = -((a11 * a23 - a13 * a21) * b3 + (a13 * a31 - a11 * a33) * b2 + (a21 * a33 - a23 * a31) * b1) / temp;float z0 = ((a11 * a22 - a12 * a21) * b3 + (a12 * a31 - a11 * a32) * b2 + (a21 * a32 - a22 * a31) * b1) / temp;float radius = (float)Math.Sqrt((x0 - x1) *2 + (y0 - y1) * 2 + (z0 - z1) * 2);Center = new Vector3(x0,y0,z0);R = GetDistance(P1, Center);}private double GetDistance(Vector3 A, Vector3 B){return Math.Sqrt(Math.Pow((A.x - B.x), 2) + Math.Pow((A.y - B.y), 2) + Math.Pow((A.z - B.z), 2));}(2).判斷該四面體與一個(gè)點(diǎn)之間的空間關(guān)系:
public bool isComtain(Vector3 node){GetTetrahedronExcenterRadius();if ((node - Center).sqrMagnitude <= R * R)return true;elsereturn false;}(3).通過一個(gè)點(diǎn)與一個(gè)面構(gòu)造出四面體后,對(duì)該四面體中的面參數(shù)進(jìn)行賦值:
public void SurfaceValue(){E1 = new Surface(P1, P2,P3);E2 = new Surface(P1,P2, P4);E3 = new Surface(P1, P3,P4);E4 = new Surface(P2, P3,P4);}(4).檢查該四面體是否包含超級(jí)四面體中的頂點(diǎn)(若包含的話最后要從四面體列表中刪去)
public void Check(Tetrahedron s){Vector3[] su = new Vector3[4];su[0] = s.P1;su[1] = s.P2;su[2] = s.P3;su[3] = s.P4;if (su.Contains(P1) || su.Contains(P2) || su.Contains(P3)|| su.Contains(P4))isBad = true;int i;float ans;Vector3 s1, s2, s3;for (i = 0; i < 4; i++){s1.x = su[1].x - su[0].x; s1.y = su[1].y - su[0].y; s1.z = su[1].z - su[0].z;s2.x = su[2].x - su[0].x; s2.y = su[2].y - su[0].y; s2.z = su[2].z - su[0].z;s3.x = su[3].x - su[0].x; s3.y = su[3].y - su[0].y; s3.z = su[3].z - su[0].z;ans = s1.x * s2.y * s3.z + s1.y * s2.z * s3.x + s1.z * s2.x * s3.y - s1.z * s2.y * s3.x - s1.x * s2.z * s3.y - s1.y * s2.x * s3.z;if (ans == 0)isBad = true;}}三、Delaunay 二維三角剖分過程
1.變量
#region 3D三角剖分參數(shù)/// <summary>/// 點(diǎn)集/// </summary>public List<Vector3> _vertices = new List<Vector3>();/// <summary>/// 超級(jí)四面體/// </summary>Tetrahedron SuperTetrahedron;/// <summary>/// 面列表/// </summary>List<Surface> _surface = new List<Surface>();/// <summary>/// 三角形列表/// </summary>List<Tetrahedron> _tetrahedron = new List<Tetrahedron>();#endregion2.剖分過程:
(1)第一步:對(duì)輸入的點(diǎn)集進(jìn)行排序(根據(jù)x,y,z依次進(jìn)行排序):
_vertices = _vertices.OrderBy(o => o.x).ThenBy(o => o.y).ThenBy(o => o.z).ToList();(2)第二步:根據(jù)排序好的點(diǎn)集構(gòu)建超級(jí)四面體(即找到并構(gòu)造出包含點(diǎn)集中所有節(jié)點(diǎn)的四面體):
SuperTetrahedron = FindSuper_Tetrahedron(_vertices);/// <summary>/// 找到超級(jí)四面體/// </summary>/// <returns></returns>public Tetrahedron FindSuper_Tetrahedron(List<Vector3> _vertices){Vector3 Circle;float Radius;float xmin = _vertices[0].x;float xmax = _vertices[_vertices.Count - 1].x;float ymin = _vertices[0].y;float ymax = _vertices[_vertices.Count - 1].y;float zmin = _vertices[0].z;float zmax = _vertices[_vertices.Count - 1].z;foreach (var dot in _vertices){if (ymin > dot.y)ymin = dot.y;if (ymax <= dot.y)ymax = dot.y;if (zmin > dot.z)zmin = dot.z;if (zmax <= dot.z)zmax = dot.z;}Vector3 P1 = new Vector3(xmin, ymin, zmin);Vector3 P3 = new Vector3(xmax, ymax, zmax);Circle = (P1 + P3) / 2;Radius = Mathf.Sqrt((P1 - P3).sqrMagnitude);Vector3 sP1 = Circle + new Vector3(0, Radius * 3, 0);Vector3 sP2 = Circle + new Vector3(0, -Radius, Radius * 2 * Mathf.Sqrt(2));Vector3 sP3 = Circle + new Vector3(Radius * Mathf.Sqrt(6), -Radius, -Radius * Mathf.Sqrt(2));Vector3 sP4 = Circle + new Vector3(-Radius * Mathf.Sqrt(6), -Radius, -Radius * Mathf.Sqrt(2));Tetrahedron super_Tetrahedron = new Tetrahedron(sP1, new Surface(sP2,sP3,sP4));//Debug.LogError((sP1+sP2+sP3+sP4)/4);return super_Tetrahedron;}同樣是用笨笨的方法(邏輯類似于構(gòu)造AABB包圍盒的思路),這一步有待改進(jìn);
(3)第三步:剖分三維空間!
剖分的過程和二維的思路一樣,從超級(jí)四面體開始,找到存在與四面體外接球內(nèi)的點(diǎn)對(duì)該四面體進(jìn)行分解重構(gòu):
首先將剛剛構(gòu)造好的超級(jí)四面體的四個(gè)頂點(diǎn)加入點(diǎn)集列表:
_vertices.Add(SuperTetrahedron.P1);_vertices.Add(SuperTetrahedron.P2);_vertices.Add(SuperTetrahedron.P3);_vertices.Add(SuperTetrahedron.P4);先別著急剖分!記得先初始化四面體列表和面緩存列表,然后將超級(jí)四面體加入四面體列表:
_tetrahedron.Clear();_surface.Clear();_tetrahedron.Add(SuperTetrahedron);然后就可以大刀闊斧的進(jìn)行剖分啦!循環(huán)走起!
for (int i = 0; i < _vertices.Count; i++){_surface.Clear();int index = 0;while (index < _tetrahedron.Count){if (_tetrahedron[index].isComtain(_vertices[i])){AddSurface(_surface, _tetrahedron[index].E1);AddSurface(_surface, _tetrahedron[index].E2);AddSurface(_surface, _tetrahedron[index].E3);AddSurface(_surface, _tetrahedron[index].E4);_tetrahedron.Remove(_tetrahedron[index]);}else{index++;}}foreach (var e in _surface){Tetrahedron Ttemp = new Tetrahedron(_vertices[i], e);_tetrahedron.Add(Ttemp);}}還記得二維剖分中的AddEdge()函數(shù)嗎?三維剖分中的AddSurface()函數(shù)也是一樣的:
public void AddSurface(List<Surface> _surface, Surface E){bool isAdd = true;int index = 0;while (index < _surface.Count){if (_surface[index].P1 == E.P1 && _surface[index].P2 == E.P2 && _surface[index].P3 == E.P3|| _surface[index].P1 == E.P1 && _surface[index].P2 == E.P3 && _surface[index].P3 == E.P2|| _surface[index].P1 == E.P3 && _surface[index].P2 == E.P2 && _surface[index].P3 == E.P1|| _surface[index].P1 == E.P2 && _surface[index].P2 == E.P1 && _surface[index].P3 == E.P3|| _surface[index].P1 == E.P2 && _surface[index].P2 == E.P3 && _surface[index].P3 == E.P1|| _surface[index].P1 == E.P3 && _surface[index].P2 == E.P1 && _surface[index].P3 == E.P2){_surface.Remove(_surface[index]);isAdd = false;}else{index++;}}if (isAdd){_surface.Add(E);}}剖分完成后,我們開始篩查哪些四面體用了超級(jí)四面體的四個(gè)頂點(diǎn),因?yàn)檫@四個(gè)頂點(diǎn)是我們構(gòu)造出來的本來是不存在的,所以我們要將這些四面體刪去:
int Tindex = 0;while (Tindex < _tetrahedron.Count){_tetrahedron[Tindex].Check(SuperTetrahedron);if (_tetrahedron[Tindex].isBad)_tetrahedron.Remove(_tetrahedron[Tindex]);elseTindex++;}最后,記得移去超級(jí)四面體的四個(gè)頂點(diǎn)和超級(jí)四面體本身:
_tetrahedron.Remove(SuperTetrahedron);_vertices.Remove(SuperTetrahedron.P1);_vertices.Remove(SuperTetrahedron.P2);_vertices.Remove(SuperTetrahedron.P3);_vertices.Remove(SuperTetrahedron.P4);三維空間的三角初步剖分就完成了:
不過目前的三維空間剖分是不完美的,因?yàn)樵谶@樣的剖分情況下會(huì)在很多情況下產(chǎn)生細(xì)而長的四面體,這是我們不希望得到的四面體,所以目前方案還需進(jìn)行優(yōu)化。
總結(jié)
以上是生活随笔為你收集整理的三维空间的三角剖分( 3D Delaunay Triangulated graph)第二部分:剖分三维空间的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HIHO#1245 : 王胖浩与三角形
- 下一篇: 谷歌浏览器恐龙游戏开挂秘诀