メッシュをカットする #1の方法でドラッグアンドドロップで作ったPlaneの表側と裏側でメッシュをカットしてみました。
そのために新しいメッシュの頂点と三角形の配列を2つずつ作ります。頂点の配列には頂点の位置が、三角形の配列には三角形を構成する頂点のインデックスが時計回りに入っています。
まず三角形の配列では3の倍数ごとに処理をしていきました。3の倍数i、i + 1、i + 2によって三角形は下のような状態です。
そこでPlaneによって孤立した点を探して、その点と他の点との中間の点の三角形を作ります。上の画像の場合、i +2、a、bの三角形です。さらに、a、i、bとb、i、i + 1の三角形も作れます。
三角形の定義は面の表から見て時計回りと決まっているので、孤立する点が決まれば計算方法は同じです。後は、これらの三角形がPlaneのどちら側にあるかで、どちらの配列に入れるかを決めるだけです。
孤立した点を求めるには、Plane.SameSideメソッドで2つの頂点が同じ側にあるかを見ていきます。Planeを横切らない三角形の頂点はそのまま全て同じ配列に追加します。
最後に、2つのゲームオブジェクトを作ってそれぞれのメッシュに新しい頂点と三角形を入れ、裏側のゲームオブジェクトにはリジッドボディを付けました。
これでメッシュを2つにカットすることはできましたが、同じ位置にある頂点を三角形ごとに新しい頂点としてリストに追加しているので、カットした後のメッシュの頂点数が増えてしまいます。
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; // 緑の球のプレハブ
[SerializeField] Material[] materials;
// 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;
// 新しいメッシュの三角形リスト
List<int> triangles1 = new List<int>();
List<int> triangles2 = new List<int>();
// 新しいメッシュの頂点リスト
List<Vector3> verticies1 = new List<Vector3>();
List<Vector3> verticies2 = new List<Vector3>();
for (int i = 0; i < mesh.triangles.Length; i++)
{
// 三角形の配列を3の倍数ごとに見る
if(i % 3 == 0)
{
// すべて表側のとき
if (plane.GetSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i]])) && plane.GetSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i + 1]])) && plane.GetSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i + 2]])))
{
verticies1.Add(mesh.vertices[mesh.triangles[i]]);
triangles1.Add(verticies1.Count - 1);
verticies1.Add(mesh.vertices[mesh.triangles[i + 1]]);
triangles1.Add(verticies1.Count - 1);
verticies1.Add(mesh.vertices[mesh.triangles[i + 2]]);
triangles1.Add(verticies1.Count - 1);
}
// すべて裏側のとき
else if (!plane.GetSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i]])) && !plane.GetSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i + 1]])) && !plane.GetSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i + 2]])))
{
verticies2.Add(mesh.vertices[mesh.triangles[i]]);
triangles2.Add(verticies2.Count - 1);
verticies2.Add(mesh.vertices[mesh.triangles[i + 1]]);
triangles2.Add(verticies2.Count - 1);
verticies2.Add(mesh.vertices[mesh.triangles[i + 2]]);
triangles2.Add(verticies2.Count - 1);
}
else
{
Vector3 isolatedVert = default; // 孤立点
Vector3 firstVert = default;
Vector3 secondVert = default;
// i、i+1が同じ側にあるとき
if (plane.SameSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i]]), target.transform.TransformPoint(mesh.vertices[mesh.triangles[i + 1]])))
{
isolatedVert = mesh.vertices[mesh.triangles[i + 2]]; // i + 2が孤立
firstVert = mesh.vertices[mesh.triangles[i]];
secondVert = mesh.vertices[mesh.triangles[i + 1]];
}
// i+1、i+2が同じ側にあるとき
else if (plane.SameSide(target.transform.TransformPoint(mesh.vertices[mesh.triangles[i + 1]]), target.transform.TransformPoint(mesh.vertices[mesh.triangles[i + 2]])))
{
isolatedVert = mesh.vertices[mesh.triangles[i]];
firstVert = mesh.vertices[mesh.triangles[i + 1]];
secondVert = mesh.vertices[mesh.triangles[i + 2]];
}
// i+2、iが同じ側にあるとき
else
{
isolatedVert = mesh.vertices[mesh.triangles[i + 1]];
firstVert = mesh.vertices[mesh.triangles[i + 2]];
secondVert = mesh.vertices[mesh.triangles[i]];
}
// 中間点
Vector3 middleA = (firstVert + isolatedVert) * 0.5f;
Vector3 middleB = (secondVert + isolatedVert) * 0.5f;
// 孤立した点が表側のとき
if (plane.GetSide(target.transform.TransformPoint(isolatedVert)))
{
// 表側
verticies1.Add(middleA);
triangles1.Add(verticies1.Count - 1);
verticies1.Add(middleB);
triangles1.Add(verticies1.Count - 1);
verticies1.Add(isolatedVert);
triangles1.Add(verticies1.Count - 1);
// 裏側
verticies2.Add(middleA);
int index1 = verticies2.Count - 1;
triangles2.Add(index1);
verticies2.Add(firstVert);
triangles2.Add(verticies2.Count - 1);
verticies2.Add(secondVert);
int index2 = verticies2.Count - 1;
triangles2.Add(index2);
verticies2.Add(middleA);
triangles2.Add(index1);
verticies2.Add(secondVert);
triangles2.Add(index2);
verticies2.Add(middleB);
triangles2.Add(verticies2.Count - 1);
}
// 孤立した点が裏側のとき
else
{
// 裏側
verticies2.Add(middleA);
triangles2.Add(verticies2.Count - 1);
verticies2.Add(middleB);
triangles2.Add(verticies2.Count - 1);
verticies2.Add(isolatedVert);
triangles2.Add(verticies2.Count - 1);
// 表側
verticies1.Add(middleA);
int index1 = verticies1.Count - 1;
triangles1.Add(index1);
verticies1.Add(firstVert);
triangles1.Add(verticies1.Count - 1);
verticies1.Add(secondVert);
int index2 = verticies1.Count - 1;
triangles1.Add(index2);
verticies1.Add(middleA);
triangles1.Add(index1);
verticies1.Add(secondVert);
triangles1.Add(index2);
verticies1.Add(middleB);
triangles1.Add(verticies1.Count - 1);
}
}
}
}
// 表側の新しいメッシュ
GameObject obj1 = new GameObject();
obj1.transform.position = target.transform.position;
obj1.transform.rotation = target.transform.rotation;
Mesh mesh1 = obj1.AddComponent<MeshFilter>().mesh;
// 頂点、三角形をメッシュに設定
mesh1.vertices = verticies1.ToArray();
mesh1.triangles = triangles1.ToArray();
mesh1.Optimize();
// マテリアルをつける
MeshRenderer mr1 = obj1.AddComponent<MeshRenderer>();
mr1.material = materials[0];
// 裏側の新しいメッシュ
GameObject obj2 = new GameObject();
Vector3 pos = target.transform.position;
obj2.transform.position = pos;
obj2.transform.rotation = target.transform.rotation;
Mesh mesh2 = obj2.AddComponent<MeshFilter>().mesh;
mesh2.vertices = verticies2.ToArray();
mesh2.triangles = triangles2.ToArray();
mesh2.Optimize();
MeshRenderer mr2 = obj2.AddComponent<MeshRenderer>();
mr2.material = materials[1];
// リジッドボディをつける
obj2.AddComponent<Rigidbody>();
// もとのオブジェクトを削除
Destroy(target.gameObject);
// 次のターゲットを表側のオブジェクトにする
target = obj1;
isDragging = false;
}
}
}
}