Unity3D實現(xiàn)模型隨機切割
本文實例為大家分享了Unity3D實現(xiàn)模型隨機切割的具體代碼,供大家參考,具體內(nèi)容如下
模型切割的效果圖如下:
我們都知道,模型是由一個個小三角形面組成的,因此我們不妨將問題簡化,先實現(xiàn)個小目標(biāo),完成單個三角形的切割,甚至繼續(xù)細(xì)分成求一條線段與某個平面的交點。
三角形與切割平面的位置關(guān)系主要有以下三種:
1. 三角形與切割平面有兩個交點,一個交點在頂點上,一個交點在邊上。這時,原有的三角形將被分成兩個三角形,分別為013、042。
2. 三角形與切割平面有兩個交點,兩個交點都在邊上。這時,原有的三角形將被分成三個三角形,分別為:034、561、126。
3. 其它(無交點、三角形完全在切割平面上、一條邊在切割平面上)
那么,我們?nèi)绾吻缶€段與平面的交點呢?
即已知平面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坐標(biāo),因為P0P1是線段而非直線,所以我們需要再做個判斷,P是否在線段P0P1中間,用向量點乘可輕易實現(xiàn)。
具體代碼如下,其中abc為切割平面上的三個頂點(確保必定構(gòu)成一個平面):
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; } //切割面位置調(diào)整為相對于模型的本地坐標(biāo) 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; } } } //根據(jù)頂點索引數(shù)組確定mesh被分成了幾份 //經(jīng)實驗:不可行;因為同一個位置的點在不同的面中是不同的點,無法判斷這兩個三角形是否是連接起來的 //故只能按方向?qū)⒛P头殖蓛蓚€ 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) { //判斷新的三角形在面的哪一側(cè); 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(); }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于Silverlight打印的使用詳解,是否為微軟的Bug問題
本篇文章對Silverlight打印的使用進行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C#判斷一個類是否實現(xiàn)了某個接口3種實現(xiàn)方法
這篇文章主要介紹了C#判斷一個類是否實現(xiàn)了某個接口3種實現(xiàn)方法,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下2015-06-06提高C# StringBuilder操作性能優(yōu)化的方法
本篇文章主要介紹使用C# StringBuilder 的項目實踐,用于減少內(nèi)存分配,提高字符串操作的性能。對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-11-11