【Unity】メッシュをカットする #1

投稿者: | 2021-03-02

ドラッグアンドドロップでメッシュをカットしてみます。そのために今回はドラグアンドドロップしたところを境界にして、メッシュの頂点カラーを変えてみました。

まず、クリックとドロップした場所のスクリーン上のマウス位置と、その中間の位置を取得して、それをワールド空間の位置に変換します。これはCamera.ScreenToWorldPointメソッドを使うと簡単です。

次にこの3点を使ってPlaneを定義します。あとは、メッシュの頂点を1つずつ見ていって、Plane.GetSideメソッドでPlaneの表と裏のどちら側に頂点があるかによって頂点カラーを変えるだけです。

頂点カラーは、シェーダーグラフによってマテリアルの色として見えるようにしています。

また、この3点に球を配置し、毎回タグを使って削除するようにしました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SliceMesh : MonoBehaviour
{
    bool isDragging; // マウスドラッグしているかどうか
    Vector3 posAMp; // クリックしたときのマウスポジション
    Vector3 posA; // ワールド空間でのポジション
    [SerializeField] GameObject target; // ターゲットのオブジェクト
    [SerializeField] GameObject sphere; // 緑の球のプレハブ

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

    // Update is called once per frame
    void Update()
    {
        // クリックしたとき
        if (Input.GetMouseButtonDown(0))
        {
            // 緑の球を消す
            foreach(var b in GameObject.FindGameObjectsWithTag("Ball"))
            {
                Destroy(b);
            }
            
            // マウスクリックした場所を記憶
            posAMp = Input.mousePosition;

            // ワールド空間に変換
            Vector3 pos = posAMp;
            pos.z = 3;
            posA = Camera.main.ScreenToWorldPoint(pos);

            // 緑の球を置く
            GameObject g = Instantiate(sphere, posA, Quaternion.identity);
            g.name = "A";

            isDragging = true;
        }


        if (isDragging)
        {
            // ドロップしたとき
            if (Input.GetMouseButtonUp(0))
            {
                // クリックを放した場所
                Vector3 mp = Input.mousePosition;

                // ワールド空間に変換
                mp.z = 3;
                Vector3 posB = Camera.main.ScreenToWorldPoint(mp);

                // 中間の場所
                Vector3 posC = Vector3.Lerp(posAMp, Input.mousePosition, 0.5f);

                // ワールド空間に変換
                posC.z = 1;
                posC = Camera.main.ScreenToWorldPoint(posC);

                // 緑の球を置く
                GameObject g = Instantiate(sphere, posB, Quaternion.identity);
                g.name = "B";

                g = Instantiate(sphere, posC, Quaternion.identity);
                g.name = "C";

                // 3点でPlaneを作る
                Plane plane = new Plane(posA, posB, posC);

                // ターゲットのメッシュ
                Mesh mesh = target.GetComponent<MeshFilter>().mesh;

                // 頂点カラーの配列を作成
                Color[] vertexColors = new Color[mesh.vertexCount];

                // 頂点がPlaneのどちら側にあるかによって頂点カラーを変える
                for(int i = 0; i < mesh.vertexCount; i++)
                {
                    // 表側にある
                    if(plane.GetSide(target.transform.TransformPoint(mesh.vertices[i])))
                    {
                        vertexColors[i] = Color.red;
                    }
                    // 裏側にある
                    else
                    {
                        vertexColors[i] = Color.blue;
                    }
                }

                // メッシュに頂点カラーを設定
                mesh.colors = vertexColors;

                isDragging = false;
            }
        }
    }
}

Camera.ScreenToWorldPointメソッドの引数にはマウス位置を入れますが、zが0だとうまくいかないので、適当な値を入れます。Planeを定義するときに表から見て時計回りの三角形を作るので、三番目の点のz値が一番小さくなるようにしました。

// マウスクリックした場所を記憶
posAMp = Input.mousePosition;

// ワールド空間に変換
Vector3 pos = posAMp;
pos.z = 3;
posA = Camera.main.ScreenToWorldPoint(pos);

Planeのどちら側にあるかを調べるとき、Plane.GetSideメソッドの引数にメッシュの頂点の座標を入れますが、頂点はローカル座標なので、Transform.TransformPointメソッドでワールド座標に変換します。

// 表側にある
if(plane.GetSide(target.transform.TransformPoint(mesh.vertices[i])))
{
    vertexColors[i] = Color.red;
}
// 裏側にある
else
{
    vertexColors[i] = Color.blue;
}

頂点カラーはいったん新しい配列を作ってからMesh.colorsに代入しました。各頂点カラーは、Mesh.verticesの同じインデックスの頂点に対応しています。

コメントを残す

メールアドレスが公開されることはありません。