最終效果
思路
- 確定切面
- 根據(jù)切面將原模型頂點分為切面上下兩類
- 將交面進行特殊處理,根據(jù)角度或距離插值運算交點,并添加頂點索引
- 對剖面頂點進行重新排序,連接,UV映射
實現(xiàn)
因為代碼量大只寫局部偽代碼說明。
為了方便這里我們拿BOX舉例。
頂點站隊
當我們確定切面裁切一個box時,我們首先需要做的是把頂點分類,通過向量的點乘將頂點0145和2376分為切面的ab兩組,同時記錄下ab的頂點進入排序和他在切面上下的狀態(tài)。
bool[] above = new bool[MeshInfo.vertices.Count]; int[] newTriangles = new int[MeshInfo.vertices.Count]; for (int i = 0; i < newTriangles.Length; i++) Vector3 vert = MeshInfo.vertices[i]; above[i] = Vector3.Dot(vert - point, normal) >= 0f; newTriangles[i] = a.vertices.Count; a.Add(vert, MeshInfo.uvs[i], MeshInfo.normals[i], MeshInfo.tangents[i]); newTriangles[i] = b.vertices.Count; b.Add(vert, MeshInfo.uvs[i], MeshInfo.normals[i], MeshInfo.tangents[i]);
接下來我們按照原模型的連接方式逐個連接ab中的頂點
for (int i = 0; i < triangleCount; i++) int _i0 = MeshInfo.triangles[i * 3]; int _i1 = MeshInfo.triangles[i * 3 + 1]; int _i2 = MeshInfo.triangles[i * 3 + 2]; a.triangles.Add(newTriangles[_i0]); a.triangles.Add(newTriangles[_i1]); a.triangles.Add(newTriangles[_i2]); else if (!_a0 && !_a1 && !_a2) b.triangles.Add(newTriangles[_i0]); b.triangles.Add(newTriangles[_i1]); b.triangles.Add(newTriangles[_i2]);
通過與原模型頂點索引的比較,我們將得到
切面補間
這樣2個模型,很明顯中間網(wǎng)格需要我們在else中進行補點補線。
這里講下我們如果去切割一個平面。
一個平面被切割時,也就兩個三角面被切割。
假如△abc被坐標軸x切割,我們如何確定點d和e的位置呢。
求角度比scale = ∠yba/ ∠yoa;(float scale = Vector.Dot(a-o,y-o)/Vector.Dot(a-b,y-o);)
d = b+(a-b)*scale;
e = c+(a-c)*scale;
然后根據(jù)網(wǎng)格的連接順序連接,aed,bdc,cde。
然后把頂點和a頂點索引加入到a面中,頂點和b頂點索引加入到b面中。
我們就能得到類似上面的2個模型。
剖面填充
這時我們就要填充剖面,但是剖面的頂點順序我們并不知道。
所以要對頂點進行排序。
局限性排序
幸運的是我們的頂點都是兩兩相連,每個頂點的位置其實有2個面的頂點重疊。
根據(jù)這個特性,我們就可以以此為根據(jù)尋找下個連接頂點的位置,然后刪除重復的頂點。
for (int i = 0; i < edges.Count - 3; i++) Vector3 t = edges[i + 1]; Vector3 temp = edges[i + 3]; for (int j = i + 2; j < edges.Count - 1; j += 2) if ((edges[j] - t).sqrMagnitude < 1e-6) edges[i + 3] = edges[j + 1]; if ((edges[j + 1] - t).sqrMagnitude < 1e-6) edges[j + 1] = edges[i + 2]; edges.RemoveAt(edges.Count - 1);
這樣我們就得到了一個按剖面形狀連接的順序頂點。
只要按012,023,034。。。。的順序連接下去就能把剖面填充。
常規(guī)排序
那如何對凸多邊形上內任意若干的頂點進行連接呢。
這時候我們需要根據(jù)凸多邊形的外切線和角度來確定下個頂點的位置。
這里只提供思路,代碼太長就不貼了,而且在模型切割上效率不如上面的高。
首先將頂點都轉換到切線尋找凸多邊形的外切線向量。
然后逐頂點對比角度。
如果兩個頂點與切線共線,我們就比較距離,一個頂點只有2個頂點連接他。第一次共線距離為近的排序,第二次共線距離為遠的排序。
凹多邊形
有人問凹多邊形如何填充,呵呵,如果填充凹多邊形,要先把凹多邊形分割成若干個凸多邊形進行填充。
UV映射
接下來就是對剖面的頂點UV進行映射。根據(jù)情況賦值的方法不同。
拿BOX舉例,
我們需要把box的bound的size傳入,然后根據(jù) point/size 來確定uv偏移值,根據(jù)切面的法線方向決定取xy?xz?zy?
int count = triangles.Count / 3; for (int i = 0; i < count; i++) int _i0 = triangles[i * 3]; int _i1 = triangles[i * 3 + 1]; int _i2 = triangles[i * 3 + 2]; Vector3 v0 = vertices[_i0] - center + size / 2f; Vector3 v1 = vertices[_i1] - center + size / 2f; Vector3 v2 = vertices[_i2] - center + size / 2f; v0 = new Vector3(v0.x / size.x, v0.y / size.y, v0.z / size.z); v1 = new Vector3(v1.x / size.x, v1.y / size.y, v1.z / size.z); v2 = new Vector3(v2.x / size.x, v2.y / size.y, v2.z / size.z); Vector3 dir = Vector3.Cross(a, b); float x = Mathf.Abs(Vector3.Dot(dir, Vector3.right)); float y = Mathf.Abs(Vector3.Dot(dir, Vector3.up)); float z = Mathf.Abs(Vector3.Dot(dir, Vector3.forward)); uvs[_i0] = new Vector2(v0.z, v0.y); uvs[_i1] = new Vector2(v1.z, v1.y); uvs[_i2] = new Vector2(v2.z, v2.y); uvs[_i0] = new Vector2(v0.x, v0.z); uvs[_i1] = new Vector2(v1.x, v1.z); uvs[_i2] = new Vector2(v2.x, v2.z); uvs[_i0] = new Vector2(v0.x, v0.y); uvs[_i1] = new Vector2(v1.x, v1.y); uvs[_i2] = new Vector2(v2.x, v2.y);
|