Unityでプロシージャルモデリング (直方体)

Unityでメッシュをスクリプトでプロシージャルに生成する方法について何回かに分けて書いていきます。

* [第1回 Unityでプロシージャルモデリング \(三角形・四角形・正多角形\) \- Qiita](https://qiita.com/aa_debdeb/items/d34bd733e0584494bdbd)
* [第2回 Unityでプロシージャルモデリング \(平面\) \- Qiita](https://qiita.com/aa_debdeb/items/043695f998606876cfb4)
* 第3回 Unityでプロシージャルモデリング \(直方体\)
* [第4回 Unityでプロシージャルモデリング \(円柱\) \- Qiita](https://qiita.com/aa_debdeb/items/c54b14696363873c640d)
* [第5回 Unityでプロシージャルモデリング \(球\) \- Qiita](https://qiita.com/aa_debdeb/items/cc46409b99301a5e34c0)
* [第6回 Unityでプロシージャルモデリング \(トーラス\) \- Qiita](https://qiita.com/aa_debdeb/items/efc188aa3c03a8acc5b9)


今回は直方体を作ってみたいと思います。

![box.PNG](https://qiita-image-store.s3.amazonaws.com/0/61569/5ce6c9c4-6a56-01d2-faef-443ddfcfb3ba.png)


```csharp:Box.cs
using UnityEngine;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class Box : MonoBehaviour
{
    [SerializeField] private Vector3 size = new Vector3(5, 4, 3);
    [SerializeField]private Vector3Int segment = new Vector3Int(5, 4, 3);

    private void Awake()
    {
        Mesh mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;

        Vector3Int extendedSeg = segment + Vector3Int.one;
        Vector3[] vertices = new Vector3[2 * ((extendedSeg.x * extendedSeg.y) + (extendedSeg.y * extendedSeg.z) + (extendedSeg.z * extendedSeg.x))];
        int[] triangles = new int[(segment.x * segment.y + segment.y * segment.z + segment.z * segment.x) * 2 * 2 * 3];

        int vi = 0;
        int ti = 0;
        MakeNearPlane(vertices, triangles, ref vi, ref ti);
        MakeFarPlane(vertices, triangles, ref vi, ref ti);
        MakeRightPlane(vertices, triangles, ref vi, ref ti);
        MakeLeftPlane(vertices, triangles, ref vi, ref ti);
        MakeUpPlane(vertices, triangles, ref vi, ref ti);
        MakeDownPlane(vertices, triangles, ref vi, ref ti);

        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.RecalculateNormals();
    }

    private void MakeNearPlane(Vector3[] vertices, int[] triangles, ref int vi, ref int ti)
    {
        Vector3 halfSize = size * 0.5f;
        Vector3 sizeStep = new Vector3(size.x / segment.x, size.y / segment.y, size.z / segment.z);

        int vj = 0;
        for (int y = 0; y < segment.y + 1; y++)
        {
            for (int x = 0; x < segment.x + 1; x++)
            {
                vertices[vi + vj++] = new Vector3(sizeStep.x * x - halfSize.x, sizeStep.y * y - halfSize.y, -halfSize.z);
            }
        }
        for (int y = 0; y < segment.y; y++)
        {
            for (int x = 0; x < segment.x; x++)
            {
                int v00 = vi + x + y * (segment.x + 1);
                int v10 = vi + (x + 1) + y * (segment.x + 1);
                int v01 = vi + x + (y + 1) * (segment.x + 1);
                int v11 = vi + (x + 1) + (y + 1) * (segment.x + 1);
                ti = MakeQuad(triangles, ti, v00, v10, v01, v11);
            }
        }
        vi += vj;
    }

    private void MakeFarPlane(Vector3[] vertices, int[] triangles, ref int vi, ref int ti)
    {
        Vector3 halfSize = size * 0.5f;
        Vector3 sizeStep = new Vector3(size.x / segment.x, size.y / segment.y, size.z / segment.z);

        int vj = 0;
        for (int y = 0; y < segment.y + 1; y++)
        {
            for (int x = 0; x < segment.x + 1; x++)
            {
                vertices[vi + vj++] = new Vector3(sizeStep.x * x - halfSize.x, sizeStep.y * y - halfSize.y, halfSize.z);
            }
        }
        for (int y = 0; y < segment.y; y++)
        {
            for (int x = 0; x < segment.x; x++)
            {
                int v00 = vi + x + y * (segment.x + 1);
                int v10 = vi + (x + 1) + y * (segment.x + 1);
                int v01 = vi + x + (y + 1) * (segment.x + 1);
                int v11 = vi + (x + 1) + (y + 1) * (segment.x + 1);
                ti = MakeQuad(triangles, ti, v00, v01, v10, v11);
            }
        }
        vi += vj;
    }

    private void MakeRightPlane(Vector3[] vertices, int[] triangles, ref int vi, ref int ti)
    {
        Vector3 halfSize = size * 0.5f;
        Vector3 sizeStep = new Vector3(size.x / segment.x, size.y / segment.y, size.z / segment.z);

        int vj = 0;
        for (int y = 0; y < segment.y + 1; y++)
        {
            for (int z = 0; z < segment.z + 1; z++)
            {
                vertices[vi + vj++] = new Vector3(halfSize.x, sizeStep.y * y - halfSize.y, sizeStep.z * z - halfSize.z);
            }
        }
        for (int y = 0; y < segment.y; y++)
        {
            for (int z = 0; z < segment.z; z++)
            {
                int v00 = vi + z + y * (segment.z + 1);
                int v10 = vi + (z + 1) + y * (segment.z + 1);
                int v01 = vi + z + (y + 1) * (segment.z + 1);
                int v11 = vi + (z + 1) + (y + 1) * (segment.z + 1);
                ti = MakeQuad(triangles, ti, v00, v10, v01, v11);
            }
        }
        vi += vj;
    }

    private void MakeLeftPlane(Vector3[] vertices, int[] triangles, ref int vi, ref int ti)
    {
        Vector3 halfSize = size * 0.5f;
        Vector3 sizeStep = new Vector3(size.x / segment.x, size.y / segment.y, size.z / segment.z);

        int vj = 0;
        for (int y = 0; y < segment.y + 1; y++)
        {
            for (int z = 0; z < segment.z + 1; z++)
            {
                vertices[vi + vj++] = new Vector3(-halfSize.x, sizeStep.y * y - halfSize.y, sizeStep.z * z - halfSize.z);
            }
        }
        for (int y = 0; y < segment.y; y++)
        {
            for (int z = 0; z < segment.z; z++)
            {
                int v00 = vi + z + y * (segment.z + 1);
                int v10 = vi + (z + 1) + y * (segment.z + 1);
                int v01 = vi + z + (y + 1) * (segment.z + 1);
                int v11 = vi + (z + 1) + (y + 1) * (segment.z + 1);
                ti = MakeQuad(triangles, ti, v00, v01, v10, v11);
            }
        }
        vi += vj;
    }

    private void MakeUpPlane(Vector3[] vertices, int[] triangles, ref int vi, ref int ti)
    {
        Vector3 halfSize = size * 0.5f;
        Vector3 sizeStep = new Vector3(size.x / segment.x, size.y / segment.y, size.z / segment.z);

        int vj = 0;

        for (int z = 0; z < segment.z + 1; z++)
        {
            for (int x = 0; x < segment.x + 1; x++)
            {
                vertices[vi + vj++] = new Vector3(sizeStep.x * x - halfSize.x, halfSize.y, sizeStep.z * z - halfSize.z);
            }
        }

        for (int z = 0; z < segment.z; z++)
        {
            for (int x = 0; x < segment.x; x++)
            {
                int v00 = vi + x + z * (segment.x + 1);
                int v10 = vi + (x + 1) + z * (segment.x + 1);
                int v01 = vi + x + (z + 1) * (segment.x + 1);
                int v11 = vi + (x + 1) + (z + 1) * (segment.x + 1);
                ti = MakeQuad(triangles, ti, v00, v10, v01, v11);
            }
        }
        vi += vj;
    }

    private void MakeDownPlane(Vector3[] vertices, int[] triangles, ref int vi, ref int ti)
    {
        Vector3 halfSize = size * 0.5f;
        Vector3 sizeStep = new Vector3(size.x / segment.x, size.y / segment.y, size.z / segment.z);

        int vj = 0;

        for (int z = 0; z < segment.z + 1; z++)
        {
            for (int x = 0; x < segment.x + 1; x++)
            {
                vertices[vi + vj++] = new Vector3(sizeStep.x * x - halfSize.x, -halfSize.y, sizeStep.z * z - halfSize.z);
            }
        }

        for (int z = 0; z < segment.z; z++)
        {
            for (int x = 0; x < segment.x; x++)
            {
                int v00 = vi + x + z * (segment.x + 1);
                int v10 = vi + (x + 1) + z * (segment.x + 1);
                int v01 = vi + x + (z + 1) * (segment.x + 1);
                int v11 = vi + (x + 1) + (z + 1) * (segment.x + 1);
                ti = MakeQuad(triangles, ti, v00, v01, v10, v11);
            }
        }
        vi += vj;
    }

    private int MakeQuad(int[] triangles, int ti, int v00, int v10, int v01, int v11)
    {
        triangles[ti] = v00;
        triangles[ti + 1] = triangles[ti + 5] = v01;
        triangles[ti + 2] = triangles[ti + 4] = v10;
        triangles[ti + 3] = v11;
        return ti + 6;
    }
}

```

生成されたメッシュは以下のようになります。

![box_02.PNG](https://qiita-image-store.s3.amazonaws.com/0/61569/2017e6c7-5364-3e34-065e-dd2a7d395920.png)


ソースコードの分量は多いですが、直方体を構成する6つの平面を以下の各メソッドでそれぞれ生成しているだけで、各メソッドの処理内容はほとんど同じです。平面の生成方法については[以前の記事](https://qiita.com/aa_debdeb/items/043695f998606876cfb4)を参考にしてください。

```csharp
MakeNearPlane(vertices, triangles, ref vi, ref ti);
MakeFarPlane(vertices, triangles, ref vi, ref ti);
MakeRightPlane(vertices, triangles, ref vi, ref ti);
MakeLeftPlane(vertices, triangles, ref vi, ref ti);
MakeUpPlane(vertices, triangles, ref vi, ref ti);
MakeDownPlane(vertices, triangles, ref vi, ref ti);
```

注意しなければならないのは三角形の定義する順番です。例えば`MakeNearPlane`と`MakeFarPlane`はXY平面に対してPlaneを定義するという点では同じですが、NearPlaneを構成する三角形はz軸を+方向に見たときに表側になるようにしなければなりませんが、FarPlaneではz軸を-方向に見たときに表側になるようにしなければなりません。このために、`MakeFarPlane`では`v01`と`v10`を入れ替えて`MakeQuad`に渡しています。

```csharp
private void MakeFarPlane(Vector3[] vertices, int[] triangles, ref int vi, ref int ti)
{
    Vector3 halfSize = size * 0.5f;
    Vector3 sizeStep = new Vector3(size.x / segment.x, size.y / segment.y, size.z / segment.z);

    int vj = 0;
    for (int y = 0; y < segment.y + 1; y++)
    {
        for (int x = 0; x < segment.x + 1; x++)
        {
            vertices[vi + vj++] = new Vector3(sizeStep.x * x - halfSize.x, sizeStep.y * y - halfSize.y, halfSize.z); // <= zの値がMakeNearPlaneと異なる
        }
    }
    for (int y = 0; y < segment.y; y++)
    {
        for (int x = 0; x < segment.x; x++)
        {
            int v00 = vi + x + y * (segment.x + 1);
            int v10 = vi + (x + 1) + y * (segment.x + 1);
            int v01 = vi + x + (y + 1) * (segment.x + 1);
            int v11 = vi + (x + 1) + (y + 1) * (segment.x + 1);
            ti = MakeQuad(triangles, ti, v00, v01, v10, v11); // <= v01とv１０の渡し方がMakeNearPlaneと異なる
        }
    }
    vi += vj;
}
```

頂点数は各平面の頂点の和、三角形は各平面の三角形の和になります。直方体の辺では同じ位置に複数の頂点が存在することになりますが、法線を別々に持つ必要があるので気にする必要はありません。