반응형
총알을 뚫거나, 창문을 만들거나 할 때 Mesh에 구멍을 뚫고 싶을 때가 있다.
절차적 메시로 구멍이 뚫린 다각형을 만들어보자.
구멍이 뚫린 사각형의 삼각분할은 아래의 링크를 참고하자.
다각형의 시계방향 좌표와 구멍의 반시계방향 좌표가 있다면 절차적 메시로도 구멍이 뚫린 메시를 만들 수 있다.
(또는 다각형이 반시계방향이고 구멍이 시계방향 좌표인 경우)
위 링크의 최종 코드 PolygonTriangulationWithHoles.cs에서 아래의 코드를 추가하면 메시를 만들 수 있다.
절차적 메시와 삼각분할로 다각형 만들기에서 메시를 만들 때 사용한 코드를 그대로 구멍이 있는 경우에 옮겼었다.
void createProceduralMesh()
{
mesh.Clear();
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
Destroy(this.GetComponent<MeshCollider>());
this.gameObject.AddComponent<MeshCollider>();
}
void OnValidate()
{
if (numOfTriangle > 0)
{
triangluation(numOfTriangle);
createProceduralMesh();
}
}
순서대로 메시를 만들면 아래와 같이 삼각분할로 메시가 생성된다.
라인 렌더러를 끄면 만들어진 메시를 볼 수 있다.
최종 생성된 메시는 아래와 같다.
렌더러를 제거하고 콜라이더만 남겼을 때의 모습은 아래와 같다.
최종 코드는 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
public class PolygonTriangulationWithHoles : MonoBehaviour
{
public GameObject polygon;
public GameObject[] holes;
LineRenderer pLine;
LineRenderer[] hLines;
List<Vector3> polygonPos = new List<Vector3>(); /* 시계 방향 */
List<List<Vector3>> holePoses = new List<List<Vector3>>(); /* 반시계 방향 */
List<Vector3> mergePolygon = new List<Vector3>();
List<Vector3> mergePolygons = new List<Vector3>();
Dictionary<Vector3, bool> dupCheck = new Dictionary<Vector3, bool>();
#region Polygon Triangulation
public int numOfTriangle;
List<Vector3> dotList = new List<Vector3>();
Mesh mesh;
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
Dictionary<Vector3, int> dic = new Dictionary<Vector3, int>();
LineRenderer[] lineForTriangles;
void createProceduralMesh()
{
mesh.Clear();
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
Destroy(this.GetComponent<MeshCollider>());
this.gameObject.AddComponent<MeshCollider>();
}
float CCWby2D(Vector3 a, Vector3 b, Vector3 c)
{
Vector3 p = b - a;
Vector3 q = c - b;
return Vector3.Cross(p, q).y;
}
void makeTriangle(LineRenderer lr, Vector3 a, Vector3 b, Vector3 c)
{
lr.startWidth = lr.endWidth = 0.2f;
lr.material.color = Color.red;
lr.positionCount = 3;
lr.SetPosition(0, a);
lr.SetPosition(1, b);
lr.SetPosition(2, c);
lr.loop = true;
}
float getAreaOfTriangle(Vector3 dot1, Vector3 dot2, Vector3 dot3)
{
Vector3 a = dot2 - dot1;
Vector3 b = dot3 - dot1;
Vector3 cross = Vector3.Cross(a, b);
return cross.magnitude / 2.0f;
}
bool checkTriangleInPoint(Vector3 dot1, Vector3 dot2, Vector3 dot3, Vector3 checkPoint)
{
if (dot1 == checkPoint) return false;
if (dot2 == checkPoint) return false;
if (dot3 == checkPoint) return false;
float area = getAreaOfTriangle(dot1, dot2, dot3);
float dot12 = getAreaOfTriangle(dot1, dot2, checkPoint);
float dot23 = getAreaOfTriangle(dot2, dot3, checkPoint);
float dot31 = getAreaOfTriangle(dot3, dot1, checkPoint);
return (dot12 + dot23 + dot31) <= area + 0.1f /* 오차 허용 */;
}
bool CrossCheckAll(List<Vector3> list, int index)
{
Vector3 a = list[index];
Vector3 b = list[index + 1];
Vector3 c = list[index + 2];
for (int i = index + 3; i < list.Count; i++)
{
if (checkTriangleInPoint(a, b, c, list[i]) == true) return true;
}
return false;
}
void triangluation(int count)
{
vertices.Clear();
triangles.Clear();
dic.Clear();
dotList.Clear();
foreach (Vector3 v in mergePolygons) // init
dotList.Add(v);
lineForTriangles = this.GetComponentsInChildren<LineRenderer>();
for (int i = 0; i < lineForTriangles.Length; i++) // init
lineForTriangles[i].positionCount = 0;
//int numOfTriangle = dotList.Count - 2;
if (count > dotList.Count - 2) count = dotList.Count - 2;
for (int i = 0; i < count; i++)
{
List<Vector3> copy = new List<Vector3>(dotList);
for (int k = 0; k < copy.Count - 2; k++)
{
bool ccw = (CCWby2D(copy[k], copy[k + 1], copy[k + 2]) > 0);
bool cross = CrossCheckAll(copy, k);
if (ccw == true && cross == false)
{
makeTriangle(lineForTriangles[i], copy[k], copy[k + 1], copy[k + 2]);
for (int c = 0; c < 3; c++)
{
if (dic.ContainsKey(copy[k + c])) continue;
dic[copy[k + c]] = vertices.Count;
vertices.Add(copy[k + c]);
}
for (int c = 0; c < 3; c++)
triangles.Add(dic[copy[k + c]]);
copy.RemoveAt(k + 1);
dotList = new List<Vector3>(copy);
break;
}
}
}
}
#endregion
void initLineRenderer(LineRenderer lr, Color color, float length = 0.1f)
{
lr.startWidth = lr.endWidth = length;
lr.material.color = color;
lr.loop = true;
}
void setLineRenderer(LineRenderer lr, List<Vector3> pos)
{
lr.positionCount = pos.Count;
for (int i = 0; i < pos.Count; i++)
lr.SetPosition(i, pos[i]);
}
Vector3[] getShortestDots(List<Vector3> a, List<Vector3> b)
{
float minValue = float.MaxValue;
Vector3 dot1, dot2;
dot1 = dot2 = Vector3.zero;
foreach (Vector3 v1 in a)
{
foreach (Vector3 v2 in b)
{
if (dupCheck.ContainsKey(v1) == true
|| dupCheck.ContainsKey(v2) == true) continue;
float distance = Vector3.Distance(v1, v2);
if (distance < minValue)
{
minValue = distance;
dot1 = v1;
dot2 = v2;
}
}
}
return new Vector3[] { dot1, dot2 };
}
List<Vector3> sortListbyStartPoint(List<Vector3> list, Vector3 startPos)
{
List<Vector3> sort = new List<Vector3>();
List<Vector3> doubleList = new List<Vector3>();
int count = list.Count;
foreach (Vector3 v in list) doubleList.Add(v);
foreach (Vector3 v in list) doubleList.Add(v);
int start;
for (start = 0; start < list.Count; start++)
if (list[start] == startPos) break;
for (int i = start; i < start + count; i++) sort.Add(doubleList[i]);
return sort;
}
List<Vector3> makePolygonWithHole(List<Vector3> polygonPos, List<Vector3> holePos, Vector3[] shortestDots)
{
List<Vector3> sortPolygon = sortListbyStartPoint(polygonPos, shortestDots[0]);
List<Vector3> sortHole = sortListbyStartPoint(holePos, shortestDots[1]);
List<Vector3> merge = new List<Vector3>();
foreach (Vector3 v in sortPolygon) merge.Add(v);
merge.Add(sortPolygon[0]);
foreach (Vector3 v in sortHole) merge.Add(v);
merge.Add(sortHole[0]);
return merge;
}
void OnValidate()
{
if (numOfTriangle > 0)
{
triangluation(numOfTriangle);
createProceduralMesh();
}
}
void Start()
{
mesh = this.GetComponent<MeshFilter>().mesh;
pLine = polygon.GetComponent<LineRenderer>();
hLines = new LineRenderer[holes.Length];
for(int i = 0; i < holes.Length; i++)
hLines[i] = holes[i].GetComponent<LineRenderer>();
initLineRenderer(pLine, Color.blue);
foreach (Transform tr in polygon.transform)
{
Vector3 v = tr.transform.position;
polygonPos.Add(v);
}
setLineRenderer(pLine, polygonPos);
/* ---------------------- */
for (int i = 0; i < holes.Length; i++)
initLineRenderer(hLines[i], Color.cyan);
int index = 0;
foreach (GameObject hole in holes)
{
List<Vector3> tmp = new List<Vector3>();
foreach (Transform tr in hole.transform)
{
Vector3 v = tr.transform.position;
tmp.Add(v);
}
holePoses.Add(tmp);
setLineRenderer(hLines[index], holePoses[index]);
index++;
}
mergePolygons = polygonPos;
for (int i = 0; i < holes.Length; i++)
{
Vector3[] shortestDots = getShortestDots(mergePolygons, holePoses[i]);
dupCheck[shortestDots[0]] = true;
dupCheck[shortestDots[1]] = true;
mergePolygon = makePolygonWithHole(mergePolygons, holePoses[i], shortestDots);
mergePolygons = mergePolygon;
//{ // 가장 짧은 점 표시하기
// GameObject temp;
// temp = GameObject.CreatePrimitive(PrimitiveType.Sphere);
// temp.transform.position = shortestDots[0];
// temp.transform.localScale = new Vector3(1.1f, 1.1f, 1.1f);
// temp = GameObject.CreatePrimitive(PrimitiveType.Sphere);
// temp.transform.position = shortestDots[1];
// temp.transform.localScale = new Vector3(1.1f, 1.1f, 1.1f);
//}
}
//foreach (Vector3 m in mergePolygons)
//{
// GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
// go.transform.localScale = new Vector3(1.5f, 1.5f, 1.5f);
// go.transform.position = m;
//}
//LineRenderer mergeLine = this.GetComponent<LineRenderer>();
//initLineRenderer(mergeLine, Color.green, 0.5f);
//setLineRenderer(mergeLine, mergePolygons);
}
}
위의 실행결과는 아래의 unitypackage에서 확인 가능하다.
Unity Plus:
Unity Pro:
Unity 프리미엄 학습:
반응형
댓글