[Unity] スパイラル状（渦巻き状）のメッシュ生成

![無題の動画 ‐ Clipchampで作成 (4).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2689374/ee863d7f-6b3d-26e8-8913-e5d72aa436c0.gif)
下記記事のスクリプトをベースにしてスパイラル状のメッシュ生成を試しました。

https://qiita.com/okome184/items/6da40073f033d6fb6e8c

### 頂点の取り方
考え方としてはパスに沿ったメッシュと同じで、まずはスパイラル状にパスを作ります。
以前記事にした円柱を作成する記事で使った三角関数を使うことで円の頂点の取り方の応用で、頂点を少しずつオフセットすることでスパイラル状の円が出来ます。
``` c#:
    [SerializeField, Range(2, 200)] int SpiralVertices = 40;
    [SerializeField] float Gap = 0.1f;
    private List<Vector3> SpiralPositions()
    {
        List<Vector3> positions = new List<Vector3>();
        Vector3 startPos = transform.position;

        float startDivision = 8;
        float startWidth = 0.2f;

        // 円周の頂点：反時計回り
        for (int j = 0; j < SpiralVertices; j++)
        {
            // 円の角度を分割数で割る
            float angleStep = (Mathf.PI * 2.0f) / (startDivision + (j * 0.1f));
            float angle = j * angleStep;

            // 三角関数：sin(angle) = y, cos(angle) = x
            Vector3 vertic = startPos + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0f) * startWidth;
            positions.Add(vertic);
            startWidth += Gap * angleStep;
        }

        return positions;
    }
```
Gizumoを使って可視化して確認するとこうなります。
![スクリーンショット_20230226_202046.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2689374/009fad5f-c4b6-d097-0956-ff667e54f2f5.png)

### UVを調整する
UVを**上側面**と**下側面**に貼るため、カレント頂点を`UV(0,0)(0,1)`にしてネクスト頂点を`UV(0,1)(1,1)`に指定することでこのようなメッシュになります。
![スクリーンショット_20230226_203201.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2689374/4f72454f-63e6-7dea-23d8-006d46b7fad0.png)

### 全体のソースコード
``` c#:GenerateSpiralMesh.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GenerateSpiralMesh : MonoBehaviour
{
    class Square
    {
        public Vector3 lowerLeft;
        public Vector3 upperLeft;
        public Vector3 lowerRight;
        public Vector3 upperRight;
    }

    [SerializeField] Material Material;
    [SerializeField] float Width = 1f;
    [SerializeField] float Height = 2f;
    [SerializeField, Range(2, 200)] int SpiralVertices = 40;
    [SerializeField] float Gap = 0.1f;
    [SerializeField] bool IsDrawGizmo = false;

    GameObject currentObject;
    int lastSpiralVertices = 0;
    float lastGap = 0;

    // Start is called before the first frame update
    void Start()
    {
        CreateMeshObject();
    }

    private void Update()
    {
        if((SpiralVertices != lastSpiralVertices) || (lastGap != Gap))
        {
            lastSpiralVertices = SpiralVertices;
            lastGap = Gap;
            UpdateMeshObject();
        }
    }

    private void CreateMeshObject()
    {
        currentObject = new GameObject();
        currentObject.transform.parent = transform;
        currentObject.AddComponent<MeshFilter>();
        currentObject.AddComponent<MeshCollider>();

        var meshRen = currentObject.AddComponent<MeshRenderer>();
        meshRen.material = Material;
    }

    private void UpdateMeshObject()
    {
        var mesh = GenerateMesh24();
        currentObject.GetComponent<MeshFilter>().mesh = mesh;
        currentObject.GetComponent<MeshCollider>().sharedMesh = mesh;
    }

    private List<Vector3> SpiralPositions()
    {
        List<Vector3> positions = new List<Vector3>();
        Vector3 startPos = transform.position;

        float startDivision = 8;
        float startWidth = 0.2f;

        // 円周の頂点：反時計回り
        for (int j = 0; j < SpiralVertices; j++)
        {
            // 円の角度を分割数で割る
            float angleStep = (Mathf.PI * 2.0f) / (startDivision + (j * 0.1f));
            float angle = j * angleStep;

            // 三角関数：sin(angle) = y, cos(angle) = x
            Vector3 vertic = startPos + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0f) * startWidth;
            positions.Add(vertic);
            startWidth += Gap * angleStep;
        }

        return positions;
    }

    private Square[] PathSquares24()
    {
        var positions = SpiralPositions();
        int length = positions.Count;
        var squares = new Square[length * 2 + 2];

        for (int i = 0; i < length; i++)
        {
            Vector3 firstPos, secondPos;

            if (i == 0)
            {
                firstPos = positions[0];
                secondPos = positions[1];
            }
            else
            {
                firstPos = positions[i - 1];
                secondPos = positions[i];
            }

            Vector3 direction = (secondPos - firstPos).normalized;
            Vector3 lowerLeft = positions[i] + new Vector3(-direction.y, direction.x, 0f) * Width;
            Vector3 lowerRight = positions[i] + new Vector3(direction.y, -direction.x, 0f) * Width;

            var square = new Square()
            {
                lowerLeft = lowerLeft,
                lowerRight = lowerRight,
                upperLeft = lowerLeft + Vector3.back * Height,
                upperRight = lowerRight + Vector3.back * Height,
            };

            if (i == 0)
            {
                squares[0] = square;
            }
            else if (i == length - 1)
            {
                squares[squares.Length - 1] = square;
            }

            for (int j = 0; j < 2; j++)
            {
                squares[i * 2 + 1 + j] = square;
            }
        }

        return squares;
    }

    private Mesh GenerateMesh24()
    {
        var squares = PathSquares24();
        var mesh = new Mesh();

        var vertices = new List<Vector3>();
        var triangles = new List<int>();
        var uvs = new List<Vector2>();

        for (int i = 0; i < squares.Length; i++)
        {
            vertices.Add(squares[i].lowerLeft);
            vertices.Add(squares[i].lowerRight);
            vertices.Add(squares[i].upperLeft);
            vertices.Add(squares[i].upperRight);

            if((i == 0) || (i == squares.Length - 1)) 
            {
                uvs.Add(new Vector2(0, 0));
                uvs.Add(new Vector2(0, 0));
                uvs.Add(new Vector2(0, 0));
                uvs.Add(new Vector2(0, 0));
            }
            else
            {
                if ((i-1) % 4 < 2)
                {
                    uvs.Add(new Vector2(0, 0));
                    uvs.Add(new Vector2(0, 0));
                    uvs.Add(new Vector2(1, 0));
                    uvs.Add(new Vector2(1, 0));
                }
                else
                {
                    uvs.Add(new Vector2(0, 1));
                    uvs.Add(new Vector2(0, 1));
                    uvs.Add(new Vector2(1, 1));
                    uvs.Add(new Vector2(1, 1));
                }
            }


            int currrentLowerLeft = (i * 4) + 0;
            int currrentLowerRight = (i * 4) + 1;
            int currrentUpperLeft = (i * 4) + 2;
            int currrentUpperRight = (i * 4) + 3;

            if (i == 0)
            {
                // 正面
                triangles.AddRange(SquareToTriangles(currrentLowerLeft, currrentLowerRight, currrentUpperLeft, currrentUpperRight));
            }
            else if (i == squares.Length - 1)
            {
                // 背面
                triangles.AddRange(SquareToTriangles(currrentLowerRight, currrentLowerLeft, currrentUpperRight, currrentUpperLeft));
            }
            else if(i > 2)
            {
                int lastLowerLeft = ((i - 2) * 4) + 0;
                int lastLowerRight = ((i - 2) * 4) + 1;
                int lastUpperLeft = ((i - 2) * 4) + 2;
                int lastUpperRight = ((i - 2) * 4) + 3;

                if (i % 2 == 1)
                {
                    // 左側面
                    triangles.AddRange(SquareToTriangles(currrentLowerLeft, lastLowerLeft, currrentUpperLeft, lastUpperLeft));
                    // 右側面
                    triangles.AddRange(SquareToTriangles(lastLowerRight, currrentLowerRight, lastUpperRight, currrentUpperRight));
                }
                else
                {
                    // 上側面
                    triangles.AddRange(SquareToTriangles(lastUpperLeft, lastUpperRight, currrentUpperLeft, currrentUpperRight));
                    // 下側面
                    triangles.AddRange(SquareToTriangles(lastLowerRight, lastLowerLeft, currrentLowerRight, currrentLowerLeft));
                }

                
            }
        }

        mesh.SetVertices(vertices);
        mesh.SetTriangles(triangles, 0);
        mesh.SetUVs(0, uvs);
        mesh.RecalculateNormals();

        return mesh;
    }

    private List<int> SquareToTriangles(int lowerLeft, int lowerRight, int upperLeft, int upperRight)
    {
        var triangles = new List<int>();

        triangles.Add(lowerLeft);   // 0
        triangles.Add(upperLeft);   // 2
        triangles.Add(lowerRight);  // 1

        triangles.Add(upperLeft);   // 2
        triangles.Add(upperRight);  // 3
        triangles.Add(lowerRight);  // 1

        return triangles;
    }

    private void OnDrawGizmos()
    {
        if (!IsDrawGizmo)
        {
            return;
        }

        Gizmos.color = Color.red;

        var positions = SpiralPositions();
        foreach(var position in positions)
        {
            Gizmos.DrawSphere(position, 0.1f);
        }
    }
}
```