Unity3D實現模型隨機切割

本文實例為大傢分享瞭Unity3D實現模型隨機切割的具體代碼,供大傢參考,具體內容如下

模型切割的效果圖如下:

我們都知道,模型是由一個個小三角形面組成的,因此我們不妨將問題簡化,先實現個小目標,完成單個三角形的切割,甚至繼續細分成求一條線段與某個平面的交點。

三角形與切割平面的位置關系主要有以下三種:

1. 三角形與切割平面有兩個交點,一個交點在頂點上,一個交點在邊上。這時,原有的三角形將被分成兩個三角形,分別為013、042。

2. 三角形與切割平面有兩個交點,兩個交點都在邊上。這時,原有的三角形將被分成三個三角形,分別為:034、561、126。

3. 其它(無交點、三角形完全在切割平面上、一條邊在切割平面上)

那麼,我們如何求線段與平面的交點呢?

即已知平面ABC,線段P0P1,求交點P。

故:

N為平面ABC法向量,可得:N= AB X AC;

P在P0P1上,可得:P = P0 + t * L;   L = P1 – P0;

又因P在平面ABC上,可得: N * PA = 0;

代入得:

=> N * (A – P0 + t * L) = 0;

=> N * (A – P0)   + t * N * L = 0;

=> t =  (P0 – A) * N / (N * L);

=> t =  (P0 – A) * (AB X AC) / (N * (P1 – P0));

最終求得P坐標,因為P0P1是線段而非直線,所以我們需要再做個判斷,P是否在線段P0P1中間,用向量點乘可輕易實現。

具體代碼如下,其中abc為切割平面上的三個頂點(確保必定構成一個平面):

public static GameObject[] Split(GameObject obj, Vector3 a, Vector3 b, Vector3 c)
 {
  if(obj == null)
  {
  return null;
  }
  MeshFilter filter = obj.GetComponent<MeshFilter>();
  if(filter == null)
  {
  return null;
  }
 
  //切割面位置調整為相對於模型的本地坐標
  a = a - obj.transform.position;
  b = b - obj.transform.position;
  c = c - obj.transform.position;
  
  List<Vector3> vertices = new List<Vector3>(filter.mesh.vertices);
  List<int> triangles = new List<int>(filter.mesh.triangles);
  List<Vector2> uvs = new List<Vector2>(filter.mesh.uv);
 
  for (int i = 0; i < filter.mesh.triangles.Length; i = i + 3)
  {
  //取三角形;
  Vector3[] p = new Vector3[3];
  for (int m = 0; m < 3; m++)
  {
   p[m] = filter.mesh.vertices[filter.mesh.triangles[i + m]];
  }
 
  //0 1 2 
  //1 2 0 ==> 切割每條邊,判斷是否有交點,如有交點,在交點處生成兩個新的頂點:L/R
  //2 0 1
  //凡是頂點與平面相交的,一律以新頂點替換
  //判斷以交點為其中一個頂點的三角形在面的哪一面
  //指定:交點到其它頂點形成的向量與平面法向量方向一致,則使用L,否則使用R
  //無交點
  //其中一個頂點在平面上
  //其中的一條邊在平面上
  //整個三角形都在平面上
 
  List<Point> cross = new List<Point>();
 
  for (int m = 0; m < 3; m++)
  {
   //求線段與面的交點-無交點返回null
   Point tpoint = MathfUtils.LineCrossPlane(p[m], p[(m + 1) % 3], a, b, c);
 
   //排除線段兩個端點與平面相交的情況;
   if (MathfUtils.PointAtPlane(p[m], a, b, c) || MathfUtils.PointAtPlane(p[(m + 1) % 3], a, b, c))
   {
   cross.Add(null);
   continue;
   }
 
   cross.Add(tpoint);
  }
 
  int tcount = cross.FindAll(t => t != null).Count;
 
  if (tcount == 0)
  {
   //完全沒交點;
   continue;
  }
 
  
  if(tcount == 1)
  {
   //隻與一條邊有交點;
   //012 tidx = 0 交點x在 0-1上,則有三角形 02x 12x
   //012 tidx = 1 交點x在 1-2上,則有三角形 01x 02x
   //012 tidx = 2 交點x在 2-3上,則有三角形 01x 12x
   int tidx = cross.FindIndex(t => t != null);
   if(tidx < 0)
   {
   continue;
   }
   vertices.Add(cross[tidx].GetVector3());
   vertices.Add(cross[tidx].GetVector3());
   Vector2 tuv = (uvs[triangles[i + tidx]] + uvs[triangles[i + (tidx + 1) % 3]]) * 0.5f;
   uvs.Add(tuv);
   uvs.Add(tuv);
 
   //計算法線,保證新三角形與原來的三角形法線保持一致;
   Vector3 nor0 = Vector3.Cross((p[1] - p[0]).normalized, (p[2] - p[0]).normalized);
 
   //改一個
   triangles[i + 0] = filter.mesh.triangles[i + tidx];
   triangles[i + 1] = filter.mesh.triangles[i + (tidx + 2) % 3];
   triangles[i + 2] = vertices.Count - 2;
   Vector3 nor1 = Vector3.Cross((vertices[triangles[i + 1]] - vertices[triangles[i + 0]]).normalized,
   (vertices[triangles[i + 2]] - vertices[triangles[i + 0]]).normalized);
   if(Vector3.Dot(nor0, nor1) < 0)
   {
   //使用法線方向判斷三角形頂點順序是否與原來一致
   int tpidx = triangles[i + 1];
   triangles[i + 1] = triangles[i + 2];
   triangles[i + 2] = tpidx;
   }
 
   //新增一個
   triangles.Add(filter.mesh.triangles[i + (tidx + 1) % 3]);
   triangles.Add(filter.mesh.triangles[i + (tidx + 2) % 3]);
   triangles.Add(vertices.Count - 1);
   Vector3 nor2 = Vector3.Cross((vertices[triangles[triangles.Count - 2]] - vertices[triangles[triangles.Count - 3]]).normalized,
   (vertices[triangles[triangles.Count - 1]] - vertices[triangles[triangles.Count - 3]]).normalized);
   if (Vector3.Dot(nor0, nor2) < 0)
   {
   int tpidx = triangles[triangles.Count - 1];
   triangles[triangles.Count - 1] = triangles[triangles.Count - 2];
   triangles[triangles.Count - 2] = tpidx;
   }
 
  }
 
  if(tcount == 2)
  {
   //與兩條邊有交點;
   //012 tidx = 0 交點xy不在 0-1上,則有三角形 xy2 xy1 01y
   //012 tidx = 1 交點xy不在 1-2上,則有三角形 xy0 xy2 12y
   //012 tidx = 2 交點xy不在 2-3上,則有三角形 xy1 xy0 01y
   // x-y-tidx+2 是獨立三角形,使用一組頂點
   int tidx = cross.FindIndex(t => t == null);
   if (tidx < 0)
   {
   continue;
   }
 
   //計算法線,保證新三角形與原來的三角形法線保持一致;
   Vector3 nor0 = Vector3.Cross((p[1] - p[0]).normalized, (p[2] - p[0]).normalized);
 
   //x
   vertices.Add(cross[(tidx + 1) % 3].GetVector3());
   vertices.Add(cross[(tidx + 1) % 3].GetVector3());
   Vector2 tuvx = (uvs[triangles[i + (tidx + 1) % 3]] + uvs[triangles[i + (tidx + 2) % 3]]) * 0.5f;
   uvs.Add(tuvx);
   uvs.Add(tuvx);
   //y
   vertices.Add(cross[(tidx + 2) % 3].GetVector3());
   vertices.Add(cross[(tidx + 2) % 3].GetVector3());
   Vector2 tuvy = (uvs[triangles[i + tidx]] + uvs[triangles[i + (tidx + 2) % 3]]) * 0.5f;
   uvs.Add(tuvy);
   uvs.Add(tuvy);
 
   //改一個
   triangles[i + 0] = filter.mesh.triangles[i + (tidx + 2) % 3];
   triangles[i + 1] = vertices.Count - 4;
   triangles[i + 2] = vertices.Count - 2;
   Vector3 nor1 = Vector3.Cross((vertices[triangles[i + 1]] - vertices[triangles[i + 0]]).normalized,
   (vertices[triangles[i + 2]] - vertices[triangles[i + 0]]).normalized);
   if (Vector3.Dot(nor0, nor1) < 0)
   {
   int tpidx = triangles[i + 1];
   triangles[i + 1] = triangles[i + 2];
   triangles[i + 2] = tpidx;
   }
 
   //新增一個
   triangles.Add(filter.mesh.triangles[i + (tidx + 1) % 3]);
   triangles.Add(vertices.Count - 3);
   triangles.Add(vertices.Count - 1);
   Vector3 nor2 = Vector3.Cross((vertices[triangles[triangles.Count - 2]] - vertices[triangles[triangles.Count - 3]]).normalized,
   (vertices[triangles[triangles.Count - 1]] - vertices[triangles[triangles.Count - 3]]).normalized);
   if (Vector3.Dot(nor0, nor2) < 0)
   {
   int tpidx = triangles[triangles.Count - 1];
   triangles[triangles.Count - 1] = triangles[triangles.Count - 2];
   triangles[triangles.Count - 2] = tpidx;
   }
 
   //新增一個
   triangles.Add(filter.mesh.triangles[i + tidx % 3]);
   triangles.Add(filter.mesh.triangles[i + (tidx + 1) % 3]);
   triangles.Add(vertices.Count - 1);
   Vector3 nor3 = Vector3.Cross((vertices[triangles[triangles.Count - 2]] - vertices[triangles[triangles.Count - 3]]).normalized,
   (vertices[triangles[triangles.Count - 1]] - vertices[triangles[triangles.Count - 3]]).normalized);
   if (Vector3.Dot(nor0, nor3) < 0)
   {
   int tpidx = triangles[triangles.Count - 1];
   triangles[triangles.Count - 1] = triangles[triangles.Count - 2];
   triangles[triangles.Count - 2] = tpidx;
   }
  }
  }
 
  //根據頂點索引數組確定mesh被分成瞭幾份
  //經實驗:不可行;因為同一個位置的點在不同的面中是不同的點,無法判斷這兩個三角形是否是連接起來的
  //故隻能按方向將模型分成兩個
  List<List<int>> ntriangles = new List<List<int>>();
  List<List<int>> temps = new List<List<int>>();
  List<List<Vector3>> nvertices = new List<List<Vector3>>();
  List<List<Vector2>> nuvs = new List<List<Vector2>>();
 
  //切割面的法向量;
  Vector3 pnormal = Vector3.Cross((c - a).normalized, (b - a).normalized);
  ntriangles.Add(new List<int>());
  ntriangles.Add(new List<int>());
  temps.Add(new List<int>());
  temps.Add(new List<int>());
  nuvs.Add(new List<Vector2>());
  nuvs.Add(new List<Vector2>());
  nvertices.Add(new List<Vector3>());
  nvertices.Add(new List<Vector3>());
  for (int i = 0; i < triangles.Count; i = i + 3)
  {
  //判斷新的三角形在面的哪一側;
  float t = 0;
  for(int j = 0; j < 3; j++)
  {
   Vector3 dir = (vertices[triangles[i + j]] - a).normalized;
   float tt = Vector3.Dot(dir, pnormal);
   t = Mathf.Abs(tt) > Mathf.Abs(t) ? tt : t;
  }
  
  int tidx = t >= 0 ? 0 : 1;
  
 
  for (int j = 0; j < 3; j++)
  {
   int idx = temps[tidx].IndexOf(triangles[i + j]);
   if (idx < 0)
   {
   ntriangles[tidx].Add(nvertices[tidx].Count);
   nvertices[tidx].Add(vertices[triangles[i + j]]);
   temps[tidx].Add(triangles[i + j]);
   nuvs[tidx].Add(uvs[triangles[i + j]]);
   continue;
   }
 
   ntriangles[tidx].Add(idx);
  }
  }
 
  if(nvertices[0].Count == 0 || nvertices[1].Count == 0)
  {
  //沒有切割到物體
  return null;
  }
 
  //生成新的模型;
  List<GameObject> items = new List<GameObject>();
  MeshRenderer render = obj.GetComponent<MeshRenderer>();
  for (int i = 0; i < ntriangles.Count; i++)
  {
  GameObject tobj = new GameObject(i.ToString());
  tobj.transform.position = obj.transform.position;
  items.Add(tobj);
  MeshFilter fi = tobj.AddComponent<MeshFilter>();
  MeshRenderer mr = tobj.AddComponent<MeshRenderer>();
  
  if(render != null)
  {
   mr.material = render.material;
  }
 
  Mesh mesh = new Mesh();
  mesh.vertices = nvertices[i].ToArray();
  mesh.triangles = ntriangles[i].ToArray();
  mesh.uv = nuvs[i].ToArray();
 
  mesh.RecalculateNormals();
  mesh.RecalculateTangents();
  mesh.RecalculateBounds();
 
  fi.mesh = mesh;
  }
 
  return items.ToArray();
} 

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: