Unity3D實(shí)現(xiàn)模型隨機(jī)切割
本文實(shí)例為大家分享了Unity3D實(shí)現(xiàn)模型隨機(jī)切割的具體代碼,供大家參考,具體內(nèi)容如下
模型切割的效果圖如下:


我們都知道,模型是由一個(gè)個(gè)小三角形面組成的,因此我們不妨將問(wèn)題簡(jiǎn)化,先實(shí)現(xiàn)個(gè)小目標(biāo),完成單個(gè)三角形的切割,甚至繼續(xù)細(xì)分成求一條線段與某個(gè)平面的交點(diǎn)。
三角形與切割平面的位置關(guān)系主要有以下三種:
1. 三角形與切割平面有兩個(gè)交點(diǎn),一個(gè)交點(diǎn)在頂點(diǎn)上,一個(gè)交點(diǎn)在邊上。這時(shí),原有的三角形將被分成兩個(gè)三角形,分別為013、042。

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

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

那么,我們?nèi)绾吻缶€段與平面的交點(diǎn)呢?
即已知平面ABC,線段P0P1,求交點(diǎn)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),因?yàn)镻0P1是線段而非直線,所以我們需要再做個(gè)判斷,P是否在線段P0P1中間,用向量點(diǎn)乘可輕易實(shí)現(xiàn)。
具體代碼如下,其中abc為切割平面上的三個(gè)頂點(diǎn)(確保必定構(gòu)成一個(gè)平面):
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)整為相對(duì)于模型的本地坐標(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 ==> 切割每條邊,判斷是否有交點(diǎn),如有交點(diǎn),在交點(diǎn)處生成兩個(gè)新的頂點(diǎn):L/R
//2 0 1
//凡是頂點(diǎn)與平面相交的,一律以新頂點(diǎn)替換
//判斷以交點(diǎn)為其中一個(gè)頂點(diǎn)的三角形在面的哪一面
//指定:交點(diǎn)到其它頂點(diǎn)形成的向量與平面法向量方向一致,則使用L,否則使用R
//無(wú)交點(diǎn)
//其中一個(gè)頂點(diǎn)在平面上
//其中的一條邊在平面上
//整個(gè)三角形都在平面上
List<Point> cross = new List<Point>();
for (int m = 0; m < 3; m++)
{
//求線段與面的交點(diǎn)-無(wú)交點(diǎn)返回null
Point tpoint = MathfUtils.LineCrossPlane(p[m], p[(m + 1) % 3], a, b, c);
//排除線段兩個(gè)端點(diǎn)與平面相交的情況;
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)
{
//完全沒(méi)交點(diǎn);
continue;
}
if(tcount == 1)
{
//只與一條邊有交點(diǎn);
//012 tidx = 0 交點(diǎn)x在 0-1上,則有三角形 02x 12x
//012 tidx = 1 交點(diǎn)x在 1-2上,則有三角形 01x 02x
//012 tidx = 2 交點(diǎn)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);
//計(jì)算法線,保證新三角形與原來(lái)的三角形法線保持一致;
Vector3 nor0 = Vector3.Cross((p[1] - p[0]).normalized, (p[2] - p[0]).normalized);
//改一個(gè)
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)
{
//使用法線方向判斷三角形頂點(diǎn)順序是否與原來(lái)一致
int tpidx = triangles[i + 1];
triangles[i + 1] = triangles[i + 2];
triangles[i + 2] = tpidx;
}
//新增一個(gè)
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)
{
//與兩條邊有交點(diǎn);
//012 tidx = 0 交點(diǎn)xy不在 0-1上,則有三角形 xy2 xy1 01y
//012 tidx = 1 交點(diǎn)xy不在 1-2上,則有三角形 xy0 xy2 12y
//012 tidx = 2 交點(diǎn)xy不在 2-3上,則有三角形 xy1 xy0 01y
// x-y-tidx+2 是獨(dú)立三角形,使用一組頂點(diǎn)
int tidx = cross.FindIndex(t => t == null);
if (tidx < 0)
{
continue;
}
//計(jì)算法線,保證新三角形與原來(lái)的三角形法線保持一致;
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);
//改一個(gè)
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;
}
//新增一個(gè)
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;
}
//新增一個(gè)
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ù)頂點(diǎn)索引數(shù)組確定mesh被分成了幾份
//經(jīng)實(shí)驗(yàn):不可行;因?yàn)橥粋€(gè)位置的點(diǎn)在不同的面中是不同的點(diǎn),無(wú)法判斷這兩個(gè)三角形是否是連接起來(lái)的
//故只能按方向?qū)⒛P头殖蓛蓚€(gè)
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)
{
//沒(méi)有切割到物體
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();
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
一篇文章教會(huì)你用Unity制作網(wǎng)格地圖生成組件
網(wǎng)格地圖這個(gè)功能在策略型游戲中應(yīng)用比較廣泛,基本情況下會(huì)將地圖分割成正方形網(wǎng)格或者六邊形網(wǎng)格,這篇文章主要給大家介紹了如何通過(guò)一篇文章學(xué)會(huì)用Unity制作網(wǎng)格地圖生成組件的相關(guān)資料,需要的朋友可以參考下2021-08-08
c# 面試必備線程基礎(chǔ)知識(shí)點(diǎn)
這篇文章主要介紹了c# 面試必備線程基礎(chǔ)知識(shí)點(diǎn),幫助大家更好的鞏固,掌握線程的基礎(chǔ)知識(shí),感興趣的朋友可以了解下2020-11-11
C#實(shí)現(xiàn)汽車租賃系統(tǒng)項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)汽車租賃系統(tǒng)項(xiàng)目,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
基于Silverlight打印的使用詳解,是否為微軟的Bug問(wèn)題
本篇文章對(duì)Silverlight打印的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
C#判斷一個(gè)類是否實(shí)現(xiàn)了某個(gè)接口3種實(shí)現(xiàn)方法
這篇文章主要介紹了C#判斷一個(gè)類是否實(shí)現(xiàn)了某個(gè)接口3種實(shí)現(xiàn)方法,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-06-06
提高C# StringBuilder操作性能優(yōu)化的方法
本篇文章主要介紹使用C# StringBuilder 的項(xiàng)目實(shí)踐,用于減少內(nèi)存分配,提高字符串操作的性能。對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-11-11

