# 實作Mesh髮片(滑鼠模擬)
###### tags: `髮片model`
## (一)以滑鼠抓取座標(主程式)
### Step1: 想法
按下滑鼠拖曳畫完線段放開滑鼠,中間每間格1cm抓取一次座標。為了抓取正確座標我們需要兩個變數newPos與oldPos不斷計算新座標與舊座標的距離,取到正確座標後將它加入 List< Vector3> PointPos資料。
---
### Step2: 計算座標方法
設定如下圖的基本網格。圖中紅線為我們拖曳出的滑鼠路徑,我們需要算出上下兩段與紅線座標(1)平行的座標(0)與(2)。
抓完座標將座標記入List< Vector3> PointPos資料

如以下程式碼:
```c#
Vector3 Vec0 = pos1 - pos2; //pos1新座標 pos2舊座標
Vector3 Vec1 = new Vector3(Vec0.y, -Vec0.x, 0.0f);
Vector3 Vec2 = new Vector3(-Vec0.y, Vec0.x, 0.0f);
Vector3 AddPos1 = new Vector3(pos1.x + Vec1.x, pos1.y + Vec1.y, 0.0f);//座標(0)
Vector3 AddPos2 = new Vector3(pos1.x + Vec2.x, pos1.y + Vec2.y, 0.0f);//座標(2)
```
---
### step3: 寬度調整方法
增加髮片寬度,我們沿用上一步計算座標方法並增加調整彈性。
下圖藍色區塊為基本寬度1,加入紫色區塊為寬度2,以此類推往上下兩端延伸。

函式PosGenerate()簡說:
首先 for迴圈跑 thickness1記入向下延伸的座標,並記入List PointPos< Vector3>
將中心點座標記入List PointPos< Vector3>
再走 for迴圈跑 thickness2記入往上延伸的座標,並記入List PointPos< Vector3>
如以下程式碼:
```c#
int width = 2;
void PosGenerate(Vector3 pos1, Vector3 pos2)//計算點座標 (1)主線段點(2)右左兩個延伸點座標計算
{
//上下兩個延伸點座標矩陣
thickness1 = new Vector3[width];
thickness2 = new Vector3[width];
//算兩點向量差
Vector3 Vec0 = pos1 - pos2;//兩點移動方向向量
for (int i = 0, j = thickness1.Length; i < thickness1.Length; i++, j--)//widthAdd1
{
Vector3 Vec1 = new Vector3((Vec0.y) * j, (-Vec0.x) * j, Vec0.z * j);
thickness1[i] = new Vector3(pos1.x + Vec1.x, pos1.y + Vec1.y, pos1.z + Vec1.z);
PointPos.Add(thickness1[i]);
}
PointPos.Add(NewPos);
for (int i = 0, j = 1; i < thickness2.Length; i++, j++)//widthAdd
{
Vector3 Vec2 = new Vector3((-Vec0.y) * j, (Vec0.x) * j, (-Vec0.z) * j);
thickness2[i] = new Vector3(pos1.x + Vec2.x, pos1.y + Vec2.y, pos1.z + Vec2.z);
PointPos.Add(thickness2[i]);
}
}
```
## (二)生成mesh(副程式)
首先建立MeshFilter組件,再將mesh網格加入進MeshFilter中。
` GetComponent<MeshFilter>().mesh = mesh = new Mesh();`
mesh網格中有幾項數組(array)需設定,包含vertice、uv、triangle、normal、tangent...等。
基本設定:
```C#
public void meshGenerate(int count,int Getwidth)
{
hairColor = GetComponent<Renderer>().material;
hairColor.color = Color.red;
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
GetComponent<MeshRenderer>().material = hairColor;//加入material
mesh.name = "Hair Grid";//網格名字
```
}
```
---
### step1:設定vertice、uv及tangent
將在第一部分PointPos收集的座標繼承在GetPointPos,並指給vertice以及uv
1. 需先給定陣列大小,陣列大小包含舊的髮片座標加上新的髮片座標的總和
2. 若有之前生成過的髮片,需要再跑迴圈備份一次
3. 跑迴圈將GetPointPos中的座標指給vertice及uv
4. 將vertice、uv、tangent(array名稱自訂無規範)的array分別指給mesh.vertice、mesh.uv及mesh.tangent
```c#
public void meshGenerate(int count,int Getwidth)
{
```
int Voldlen = verticeBox[count];//目前的total vertice個數
Vector3 vertice = new Vector3[GetPointPos.Count + Voldlen];
Vector2 uv = new Vector2[GetPointPos.Count + Voldlen];//texture
Vector4 tangents = new Vector4[GetPointPos.Count + Voldlen];
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
//舊座標備分
for (int i = 0; i < Voldlen; i++)
{
vertice[i] = oldVerticePos[i];
uv[i].x = oldVerticePos[i].x;
uv[i].y = oldVerticePos[i].y;
tangents[i] = tangent;
}
//新座標
for (int i = Voldlen,j = 0; i < GetPointPos.Count + Voldlen; i++,j++)
{
vertice[i] = GetPointPos[j];
uv[i].x = GetPointPos[j].x;//Vector3轉Vector2
uv[i].y = GetPointPos[j].y;
tangents[i] = tangent;
}
mesh.vertices = vertice;
mesh.uv = uv;
mesh.tangents = tangents;
```
}
```
---
### step2:設定triangle
mesh.triangle陣列負責生成三角形,一塊正方形網格是由兩片三角形組成,而我們的髮片是由多塊正方形網格所組合而成的。mesh.triangle資料型態為int,triangle抓座標方式為找到對應的mesh.vertice陣列指標,繼承對應的vertice陣列指標中的座標位置。
下圖中,黑色數字為vertice陣列指標,紅色數字部分為triangle陣列指標,triangle陣列指標需正確找到自己對應的vertice陣列指標,才能完成我們想要的Mesh網格。

1.int point為計算有多少個正方形網格
2.計算triangle陣列需開多大
3.若有之前生成的髮片,需先做備份
4.for迴圈配合SstQuad函式,計算triangle。
```
public void meshGenerate(int count,int Getwidth)
{
```
int point = ((GetPointPos.Count / (3 + (Getwidth - 1) * 2) - 1)) * 2 * Getwidth;//計算網格數
triangles = new int[point * 6 + triangleBox[count]];//計算需要多少三角形點座標
//備份三角形
for (int i = 0; i < triangleBox[count]; i++)
{
triangles[i] = oldTrianglePos[i];
}
int t = triangleBox[count];//初始三角形陣列指標
int k = 0;//累加
for (int vi = verticeBox[count], x = 1; x <= point; x++, vi += k)
{
t = SetQuad(triangles, t, vi, vi + 1, vi + 3 + (2 * (Getwidth - 1)), vi + 4 + (2 * (Getwidth - 1)));
if (x % (Getwidth * 2) != point % (Getwidth * 2)) k = 1; //在同一行時
else k = 2; //累加 (換行時)
}
mesh.triangles = triangles;
```
}
private static int SetQuad(int[] triangles, int i, int v0, int v1, int v2, int v3)
{
triangles[i] = v0;
triangles[i + 1] = v1;
triangles[i + 2] = v2;
triangles[i + 3] = v2;
triangles[i + 4] = v1;
triangles[i + 5] = v3;
return i + 6;
}
```
### step3:生成多段髮片
指導老師建議:
所有的髮片共用一個mesh,將mesh想成一個陣列,每加上一片髮片我們需將陣列擴大。如下圖:

1. 新增兩個變數分別為oldVertice及oldTriangle紀錄mesh.vertice及triangle的長度。
新增兩個List資料為verticeBox及triangleBox紀錄每次mesh.vertice及triangle的長度。
新增兩個List資料為 oldVerticePos及oldTrianglePos 存放mesh.vertice的輩分座標及mesh.triangle的輩分指標位置
2. 在進行輩分以及生成新的值時,須注意起始的陣列指標以及對應的座標
```c#
public void meshGenerate(int count,int Getwidth)
{
if (down == 0)//讓list有值
{
verticeBox.Add(0);
triangleBox.Add(0);
down = 1;
}
```
Voldlen = verticeBox[count];//目前的total vertice個數
vertice = new Vector3[GetPointPos.Count + Voldlen];
uv = new Vector2[GetPointPos.Count + Voldlen];//texture
tangents = new Vector4[GetPointPos.Count + Voldlen];
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
//舊座標備分
for (int i = 0; i < Voldlen; i++)
{
vertice[i] = oldVerticePos[i];
uv[i].x = oldVerticePos[i].x;
uv[i].y = oldVerticePos[i].y;
tangents[i] = tangent;
}
//新的座標
for (int i = Voldlen,j = 0; i < GetPointPos.Count + Voldlen; i++,j++)
{
vertice[i] = GetPointPos[j];
uv[i].x = GetPointPos[j].x;//Vector3轉Vector2
uv[i].y = GetPointPos[j].y;
tangents[i] = tangent;
}
mesh.vertices = vertice;//mesh網格點生成
mesh.uv = uv;
mesh.tangents = tangents;
int point = ((GetPointPos.Count / (3 + (Getwidth - 1) * 2) - 1)) * 2 * Getwidth;//計算網格數
triangles = new int[point * 6 + triangleBox[count]];//計算需要多少三角形點座標
//備份三角形
for (int i = 0; i < triangleBox[count]; i++)
{
triangles[i] = oldTrianglePos[i];
}
int t = triangleBox[count];//初始三角形
int k = 0;//累加
for (int vi = verticeBox[count], x = 1; x <= point; x++, vi += k)
{
t = SetQuad(triangles, t, vi, vi + 1, vi + 3 + (2 * (Getwidth - 1)), vi + 4 + (2 * (Getwidth - 1)));
if (x % (Getwidth * 2) != point % (Getwidth * 2)) k = 1; //在同一行
else k = 2; //對vi的累加 (需換行時)
}
mesh.triangles = triangles;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
oldVertice = vertice.Length;
oldTriangle = triangles.Length;
//收集長度&舊的位置
RecordValue(oldVertice, oldTriangle,mesh.vertices,mesh.triangles);
}
```
3. RecordValue()函式,負責收集長度以及需輩分座標位置。當偵測到滑鼠放開時,先將之前的輩分內容先清空,在加入新的輩分內容進去(AddRange())以及新長度(Add())。
```c#
//紀錄 vertice & triangle長度的矩陣
public List<int> verticeBox = new List<int>();
public List<int> triangleBox = new List<int>();
//輩分座標
public List<Vector3> oldVerticePos = new List<Vector3>();
public List<int> oldTrianglePos = new List<int>();
public void RecordValue(int verticeLength,int triangleLength,Vector3[] verticePos,int[] trianglePos)
{
if (Input.GetMouseButtonUp(0))
{
oldVerticePos.Clear();//需先清空原有的座標
oldTrianglePos.Clear();
verticeBox.Add(verticeLength);
triangleBox.Add(triangleLength);
oldVerticePos.AddRange(verticePos);//重新新增上去
oldTrianglePos.AddRange(trianglePos);
}
}
```