[Unity] キューブ状のメッシュ生成をパスに沿って行う

![スクリーンショット_20230220_005057.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2689374/d5580a24-cb9d-5e57-8fdf-178d71e79d22.png)
ゲーム制作の過程でパスに沿ったオブジェクトを作る必要があったため記事にします。
少し掘り下げてメッシュ生成の基本となる記事も書いたのでよかった見てください。

https://qiita.com/okome184/items/1e3d2a474225c4a23eb1

### パスに沿った頂点の位置
パスに沿ってメッシュを生成する場合、最初に課題となるのが **「頂点をどのようにとるか」** になります。
![スクリーンショット_20230219_220944.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2689374/8a6f6efc-4d99-89ec-cf29-1a9e77819d48.png)
図のように**白い球**がパスの頂点だとして、**灰色の球**の位置に頂点を置くことが理想です。
一番簡単な考え方としてはパスの頂点を中心に左右にオフセットした位置を頂点に置くことです。
```C#
            Vector3 lowerLeft = position + Vector3.left;
            Vector3 lowerRight = position + Vector3.right;
```
ただし、この場合は真っすぐ進む分には問題ありませんが、９０度以上曲がった場合破綻してしまいます。
つまり、回転を考慮してあげなくてはいけません。

２点間の向きは`Quaternion.LookRotation`を使って求めることができます。
```C#
            Vector3 direction = (secondPos - firstPos).normalized;
            Quaternion rot = Quaternion.LookRotation(direction);

            Vector3 lowerLeft = positions[i] + rot * Vector3.left;
            Vector3 lowerRight = positions[i] + rot * Vector3.right;
```
こうすることで**白い球**がどのような位置にあっても**図の灰色の球**のように頂点をとることができました。

```C#:GeneratePathCube.cs
using System.Collections.Generic;
using UnityEngine;

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

    [SerializeField] Transform[] PathPosition;
    [SerializeField] Material Material;
    [SerializeField] float Width = 1f;
    [SerializeField] float Height = 2f;
    [SerializeField] bool IsDrawGizmo = false;

    private Square[] PathSquares()
    {
        int length = PathPosition.Length;
        var positions = new Vector3[length];
        var squares = new Square[length];

        for( int i = 0; i < length; i++)
        {
            positions[i] = PathPosition[i].position;
        }

        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;
            Quaternion rot = Quaternion.LookRotation(direction);

            Vector3 lowerLeft = positions[i] + rot * Vector3.left * Width;
            Vector3 lowerRight = positions[i] + rot * Vector3.right * Width;

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

            squares[i] = square;
        }

        return squares;
    }

    private void OnDrawGizmos()
    {
        if((PathPosition == null) || (PathPosition.Length < 2) || !IsDrawGizmo)
        {
            return;
        }

        var squares = PathSquares();

        for (int i = 0; i < squares.Length; i++)
        {
            Gizmos.DrawSphere(squares[i].lowerLeft, 0.2f);
            Gizmos.DrawSphere(squares[i].lowerRight, 0.2f);
            Gizmos.DrawSphere(squares[i].upperLeft, 0.2f);
            Gizmos.DrawSphere(squares[i].upperRight, 0.2f);
        }
    }
```
### パスに沿った頂点
キューブを作る考え方とあまり変わりませんが、正面と背面以外は**今の頂点**と**ひとつ前の頂点**を結んだ面を作るだけです。
![スクリーンショット_20230219_223838.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2689374/ff10bdd2-25bc-a7fc-a59c-42abd40ee16f.png)
```C#
        for (int i = 0; i < squares.Length; i++)
        {
            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
            {
                int lastLowerLeft = ((i - 1) * 4) + 0;
                int lastLowerRight = ((i - 1) * 4) + 1;
                int lastUpperLeft = ((i - 1) * 4) + 2;
                int lastUpperRight = ((i - 1) * 4) + 3;

                // 左側面
                triangles.AddRange(SquareToTriangles(currrentLowerLeft, lastLowerLeft, currrentUpperLeft, lastUpperLeft));
                // 右側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, currrentLowerRight, lastUpperRight, currrentUpperRight));
                // 上側面
                triangles.AddRange(SquareToTriangles(lastUpperLeft, lastUpperRight, currrentUpperLeft, currrentUpperRight));
                // 下側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, lastLowerLeft, currrentLowerRight, currrentLowerLeft));

                if (i == squares.Length - 1)
                {
                    // 背面
                    triangles.AddRange(SquareToTriangles(currrentLowerRight, currrentLowerLeft, currrentUpperRight, currrentUpperLeft));
                }
            }
        }
```
トライアングルとメッシュ生成の全体像は以下になります。
```C#:GeneratePathCube.cs
    void Start()
    {
        GenerateMesh();
    }

    public void GenerateMesh()
    {
        var squares = PathSquares();
        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);

            uvs.Add(new Vector2(0, 0));
            uvs.Add(new Vector2(1, 0));
            uvs.Add(new Vector2(0, 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
            {
                int lastLowerLeft = ((i - 1) * 4) + 0;
                int lastLowerRight = ((i - 1) * 4) + 1;
                int lastUpperLeft = ((i - 1) * 4) + 2;
                int lastUpperRight = ((i - 1) * 4) + 3;

                // 左側面
                triangles.AddRange(SquareToTriangles(currrentLowerLeft, lastLowerLeft, currrentUpperLeft, lastUpperLeft));
                // 右側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, currrentLowerRight, lastUpperRight, currrentUpperRight));
                // 上側面
                triangles.AddRange(SquareToTriangles(lastUpperLeft, lastUpperRight, currrentUpperLeft, currrentUpperRight));
                // 下側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, lastLowerLeft, currrentLowerRight, currrentLowerLeft));

                if (i == squares.Length - 1)
                {
                    // 背面
                    triangles.AddRange(SquareToTriangles(currrentLowerRight, currrentLowerLeft, currrentUpperRight, currrentUpperLeft));
                }
            }
        }

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

        var obj = new GameObject();
        obj.name = "GenerateMesh";
        obj.transform.parent = transform;
        var meshFilter = obj.AddComponent<MeshFilter>();
        var meshRenderer = obj.AddComponent<MeshRenderer>();
        var meshCollider = obj.AddComponent<MeshCollider>();

        meshFilter.mesh = mesh;
        meshCollider.sharedMesh = mesh;
        meshRenderer.material = Material;
    }

    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;
    }
```