참고
- 평면과 점 사이의 최단거리를 만드는 평면 위의 점 구하기
- 다각형 좌표의 시계 방향 판단하기 (How to Determine If a Polygon is Clocwise)
세 개의 점이 다음과 같다.
d1 = new Vector3(0, 0, 0);
d2 = new Vector3(1, 0, 1);
d3 = new Vector3(0, 0, 1);
d1 → d2 → d3는 반시계 방향이고, d1 → d3 → d2는 시계 방향이다.
모든 좌표가 y = 0이기 때문에 ccwBy2D가 + 면 시계 방향, - 면 반시계 방향이다.
참고로 이 방향은 점 3개로 Plane을 만들 때의 방향과 동일하다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClockWiseForPlane : MonoBehaviour
{
float ccwBy2D(Vector3 a, Vector3 b, Vector3 c)
{
Vector3 p = b - a;
Vector3 q = c - b;
return Vector3.Cross(p, q).y;
}
void Start()
{
Vector3 d1, d2, d3;
d1 = new Vector3(0, 0, 0);
d2 = new Vector3(1, 0, 1);
d3 = new Vector3(0, 0, 1);
Plane p1 = new Plane(d1, d2, d3);
Plane p2 = new Plane(d1, d3, d2);
Debug.Log("p1 normal : " + p1.normal);
Debug.Log("p2 normal : " + p2.normal);
Debug.Log("counterclockwise : " + ccwBy2D(d1, d2, d3));
Debug.Log("clockwise : " + ccwBy2D(d1, d3, d2));
}
}
하지만 이 경우는 y = 0인 평면에 대해서만 시계 방향이거나 반시계 방향을 정한 경우다.
기준에 따라 달라지는 방향
아래의 초록색 → 파란색 → 빨간색의 공은 반시계 방향이다.
하지만 시점을 변경해서 본 초록색 → 파란색 → 빨간색의 공은 시계 방향이다.
따라서 평면의 기준에 따라 시계 방향인지 반시계 방향인지 판단하는 함수를 만들 필요가 있다.
구현
먼저 점 3개 모두 기준이 되는 평면에 가장 가까운 위치를 찾아야 한다.
평면과 점 사이의 최단거리와 최단거리를 만드는 평면 위의 점을 구하는 함수는 다음과 같다.
float distancePlaneToPoint(Vector3 normal, Vector3 planeDot, Vector3 point)
{
Plane plane = new Plane(normal, planeDot);
return plane.GetDistanceToPoint(point);
}
Vector3 getPositionOnthePlane(Vector3 normal, Vector3 planeDot, Vector3 position)
{
float distance = distancePlaneToPoint(normal, planeDot, position);
return position - normal * distance;
}
위의 함수를 이용하여 해당 평면에 대해 시계 방향을 판단하는 함수를 만들 수 있다.
float ccwByPlane(Vector3 a, Vector3 b, Vector3 c, Vector3 normal, Vector3 planeDot)
{
Vector3 d = getPositionOnthePlane(normal, planeDot, a);
Vector3 e = getPositionOnthePlane(normal, planeDot, b);
Vector3 f = getPositionOnthePlane(normal, planeDot, c);
Vector3 p = e - d;
Vector3 q = f - e;
return Vector3.Dot(Vector3.Cross(p, q), normal);
}
ccwBy2D의 Vector3.Cross(p, q).y은 외적과 normal 벡터가 y축인 평면의 내적의 결과다.
따라서 Plane의 normal과 내적을 해서 양수면 시계 방향, 음수면 반시계 방향이 된다.
위 함수의 정상 동작을 확인하기 위해 Update에서 아래의 코드를 실행한다.
각 점을 평면에 가장 가까운 곳으로 mapping시키고
그 평면에서 시계 방향이면 material이 빨간색으로, 반시계 방향이면 파란색으로 변경되도록 하였다.
for(int i = 0; i < planeObject.Length; i++)
{
Vector3 normal = planeObject[i].transform.up;
Vector3 planeDot = planeObject[i].transform.position;
for(int k = 0; k < 3; k++)
{
Vector3 point = clockCheckObject[k].transform.position;
dotsOnthePlane[i * 3 + k].transform.position = getPositionOnthePlane(normal, planeDot, point);
}
Vector3 d1 = dotsOnthePlane[i * 3 + 0].transform.position;
Vector3 d2 = dotsOnthePlane[i * 3 + 1].transform.position;
Vector3 d3 = dotsOnthePlane[i * 3 + 2].transform.position;
if(ccwByPlane(d1, d2, d3, normal, planeDot) > 0)
{
for(int k = 0; k < 3; k++)
{
Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
rd.material = red;
}
}
else
{
for (int k = 0; k < 3; k++)
{
Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
rd.material = blue;
}
}
}
라인 렌더러까지 적절히 추가하면 아래의 결과를 볼 수 있다.
최종 코드는 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClockWiseForPlane : MonoBehaviour
{
public Material red, blue;
public GameObject[] planeObject;
public GameObject[] clockCheckObject;
LineRenderer lr;
List<GameObject> dotsOnthePlane = new List<GameObject>();
float ccwBy2D(Vector3 a, Vector3 b, Vector3 c)
{
Vector3 p = b - a;
Vector3 q = c - b;
return Vector3.Cross(p, q).y;
}
float ccwByPlane(Vector3 a, Vector3 b, Vector3 c, Vector3 normal, Vector3 planeDot)
{
Vector3 d = getPositionOnthePlane(normal, planeDot, a);
Vector3 e = getPositionOnthePlane(normal, planeDot, b);
Vector3 f = getPositionOnthePlane(normal, planeDot, c);
Vector3 p = e - d;
Vector3 q = f - e;
return Vector3.Dot(Vector3.Cross(p, q), normal);
}
float distancePlaneToPoint(Vector3 normal, Vector3 planeDot, Vector3 point)
{
Plane plane = new Plane(normal, planeDot);
return plane.GetDistanceToPoint(point);
}
Vector3 getPositionOnthePlane(Vector3 normal, Vector3 planeDot, Vector3 position)
{
float distance = distancePlaneToPoint(normal, planeDot, position);
return position - normal * distance;
}
void Start()
{
lr = this.GetComponent<LineRenderer>();
lr.startWidth = lr.endWidth = 0.2f;
lr.material.color = Color.yellow;
lr.positionCount = 3;
for(int i = 0; i < planeObject.Length * 3; i++)
{
GameObject dot = GameObject.CreatePrimitive(PrimitiveType.Sphere);
dotsOnthePlane.Add(dot);
}
}
void Update()
{
Vector3 a, b, c;
a = clockCheckObject[0].transform.position;
b = clockCheckObject[1].transform.position;
c = clockCheckObject[2].transform.position;
lr.SetPosition(0, a);
lr.SetPosition(1, b);
lr.SetPosition(2, c);
for(int i = 0; i < planeObject.Length; i++)
{
Vector3 normal = planeObject[i].transform.up;
Vector3 planeDot = planeObject[i].transform.position;
for(int k = 0; k < 3; k++)
{
Vector3 point = clockCheckObject[k].transform.position;
dotsOnthePlane[i * 3 + k].transform.position = getPositionOnthePlane(normal, planeDot, point);
}
Vector3 d1 = dotsOnthePlane[i * 3 + 0].transform.position;
Vector3 d2 = dotsOnthePlane[i * 3 + 1].transform.position;
Vector3 d3 = dotsOnthePlane[i * 3 + 2].transform.position;
if(ccwByPlane(d1, d2, d3, normal, planeDot) > 0)
{
for(int k = 0; k < 3; k++)
{
Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
rd.material = red;
}
}
else
{
for (int k = 0; k < 3; k++)
{
Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
rd.material = blue;
}
}
}
}
}
위의 실행결과는 아래의 unitypackage에서 확인 가능하다.
Unity Plus:
Unity Pro:
Unity 프리미엄 학습:
'개발 > Unity' 카테고리의 다른 글
유니티 - 직선을 N등분 하기 (Dividing a Line) (0) | 2022.11.23 |
---|---|
유니티 C# - 비교 함수를 이용하여 리스트 정렬하기 (List Sorting with Compare) (0) | 2022.11.22 |
유니티 - 평면과 점 사이의 최단거리를 만드는 평면 위의 점 구하기 (0) | 2022.11.11 |
유니티 - triangles로 다각형의 원래 좌표 복구하기 (Recover the Original Coordinates of a Polygon with Triangles) (0) | 2022.11.08 |
유니티 - CombineInstance로 메시 합치기 (Combine Meshes with CombineInstance) (0) | 2022.11.07 |
댓글