---
# System prepended metadata

title: 實作Mesh髮片(滑鼠模擬)
tags: [髮片model]

---

# 實作Mesh髮片(滑鼠模擬)
###### tags: `髮片model`

## (一)以滑鼠抓取座標(主程式)

### Step1: 想法

按下滑鼠拖曳畫完線段放開滑鼠，中間每間格1cm抓取一次座標。為了抓取正確座標我們需要兩個變數newPos與oldPos不斷計算新座標與舊座標的距離，取到正確座標後將它加入 List< Vector3> PointPos資料。

---
### Step2: 計算座標方法

設定如下圖的基本網格。圖中紅線為我們拖曳出的滑鼠路徑，我們需要算出上下兩段與紅線座標(1)平行的座標(0)與(2)。
抓完座標將座標記入List< Vector3> PointPos資料
![](https://ppt.cc/fEmDFx@.jpg)

如以下程式碼:
```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，以此類推往上下兩端延伸。

![](https://ppt.cc/fWsD6x@.jpg)

函式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網格。

![](https://ppt.cc/f0A1hx@.jpg)


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想成一個陣列，每加上一片髮片我們需將陣列擴大。如下圖:

![](https://ppt.cc/fAyRMx@.jpg)


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);

    }

}
```