참고
아래와 같이 빨간 직선과 파란 직선이 3차원 공간에 있을 때,
두 직선의 최단거리와 최단거리를 만드는 초록색 직선을 구해보자.
3차원에 있는 두 직선의 최단거리
두 직선의 최단거리 공식은 다음과 같다.
점 A, B가 만드는 직선과 점 C, D가 만드는 직선이 있을 때,
직선 AB의 점 하나(=A)와 직선 CD의 점 하나(=C)로 직선을 만들고 아래의 공식을 대입하면 거리를 구할 수 있다.
위의 공식 AC에서 A나 C가 아니더라도 각각 직선 AB와 직선 CD에 포함된 모든 점이 가능하다.
유니티에서는 아래와 같이 구현하면 된다.
float getDistanceTwoLine(Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
Vector3 AB = A - B;
Vector3 CD = C - D;
Vector3 AC = A - C;
Vector3 line = Vector3.Cross(AB, CD);
return Mathf.Abs(Vector3.Dot(AC, line)) / line.magnitude;
}
3차원에 있는 두 직선의 최단거리를 만드는 직선
최단거리를 만드는 직선은 두 직선에 대해 모두 수직이어야 한다.
따라서 이 직선의 방향은 직선 AB와 직선 CD의 외적이다.
그런데 여기서 두 직선의 교차점은 어떻게 구할까?
평면과 직선의 접점 좌표를 이용해서 구하면 된다.
외적과 직선 AB를 다시 외적하고 직선 AB를 포함하는 평면을 구해보자.
이 평면은 반드시 아래와 같게 된다. (초록색 직선과 빨간색 직선의 외적이 평면의 노멀 벡터다)
평면과 직선의 접점 좌표를 구하는 공식으로 파란색 직선에 있는 최단거리를 만드는 점을 구할 수 있다.
Vector3 getContactPoint(Vector3 normal, Vector3 planeDot, Vector3 A, Vector3 B)
{
Vector3 nAB = (B - A).normalized;
return A + nAB * Vector3.Dot(normal, planeDot - A) / Vector3.Dot(normal, nAB);
}
위의 공식을 이용하면 마찬가지로 빨간색 직선에 있는 최단거리를 만드는 점도 구할 수 있다.
유니티 C#의 튜플을 이용하여 두 점을 리턴하는 함수를 만들면 아래와 같다.
(Vector3 point1, Vector3 point2) getShortestPath(Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
Vector3 AB = A - B;
Vector3 CD = C - D;
Vector3 line = Vector3.Cross(AB, CD);
Vector3 crossLineAB = Vector3.Cross(line, AB);
Vector3 crossLineCD = Vector3.Cross(line, CD);
return (getContactPoint(crossLineAB, A, C, D), getContactPoint(crossLineCD, C, A, B));
}
실제 최단거리가 같은지 Update 문에서 Vector3.Distance와 getDistanceTwoLine로 구한 값을 비교해보자.
void Update()
{
Vector3 p1, p2, p3, p4;
p1 = point1.position;
p2 = point2.position;
p3 = point3.position;
p4 = point4.position;
(Vector3 linePoint1, Vector3 linePoint2)
= getShortestPath(p1, p2, p3, p4);
cube1.position = linePoint1;
cube2.position = linePoint2;
setLineRenderer(lr1, new List<Vector3>() { p1, p2 }, Color.red);
setLineRenderer(lr2, new List<Vector3>() { p3, p4 }, Color.blue);
setLineRenderer(lr3, new List<Vector3>() { cube1.position, cube2.position }, Color.green);
float distanceCube1 = getDistanceTwoLine(p1, p2, p3, p4);
float distanceCube2 = Vector3.Distance(cube1.position, cube2.position);
Debug.Log(distanceCube1 + " / " + distanceCube2);
}
점을 움직여보면 최단거리가 정상적으로 변경되는 것을 알 수 있다.
그리고 콘솔의 로그를 보면 두 직선의 최단거리가 잘 구해지는 것을 알 수 있다.
만약 선이 유한한 경우에만 직선을 구해야 한다면,
2차원 평면에서 유한한 선의 교점 구하기를 참고하여 위에서 구한 두 점이 각각의 직선에 포함되는지 체크하면 된다.
bool checkDotInLine(Vector3 a, Vector3 b, Vector3 dot)
{
float epsilon = 0.00001f;
float dAB = Vector3.Distance(a, b);
float dADot = Vector3.Distance(a, dot);
float dBDot = Vector3.Distance(b, dot);
return ((dAB + epsilon) >= (dADot + dBDot));
}
테스트는 다음과 같이 설정하였다.
자세한 사항은 글 아래에 있는 unitypackage를 참고하자.
전체 코드는 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LineToLine : MonoBehaviour
{
public Transform point1, point2, point3, point4;
public Transform cube1, cube2;
public LineRenderer lr1, lr2, lr3;
float getDistanceTwoLine(Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
Vector3 AB = A - B;
Vector3 CD = C - D;
Vector3 AC = A - C;
Vector3 line = Vector3.Cross(AB, CD);
return Mathf.Abs(Vector3.Dot(AC, line)) / line.magnitude;
}
Vector3 getContactPoint(Vector3 normal, Vector3 planeDot, Vector3 A, Vector3 B)
{
Vector3 nAB = (B - A).normalized;
return A + nAB * Vector3.Dot(normal, planeDot - A) / Vector3.Dot(normal, nAB);
}
(Vector3 point1, Vector3 point2) getShortestPath(Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
Vector3 AB = A - B;
Vector3 CD = C - D;
Vector3 line = Vector3.Cross(AB, CD);
Vector3 crossLineAB = Vector3.Cross(line, AB);
Vector3 crossLineCD = Vector3.Cross(line, CD);
return (getContactPoint(crossLineAB, A, C, D), getContactPoint(crossLineCD, C, A, B));
}
void setLineRenderer(LineRenderer lr, List<Vector3> list, Color color)
{
lr.startWidth = lr.endWidth = .2f;
lr.material.color = color;
lr.positionCount = list.Count;
for (int i = 0; i < list.Count; i++)
lr.SetPosition(i, list[i]);
}
void Update()
{
Vector3 p1, p2, p3, p4;
p1 = point1.position;
p2 = point2.position;
p3 = point3.position;
p4 = point4.position;
(Vector3 linePoint1, Vector3 linePoint2)
= getShortestPath(p1, p2, p3, p4);
cube1.position = linePoint1;
cube2.position = linePoint2;
setLineRenderer(lr1, new List<Vector3>() { p1, p2 }, Color.red);
setLineRenderer(lr2, new List<Vector3>() { p3, p4 }, Color.blue);
setLineRenderer(lr3, new List<Vector3>() { cube1.position, cube2.position }, Color.green);
float distanceCube1 = getDistanceTwoLine(p1, p2, p3, p4);
float distanceCube2 = Vector3.Distance(cube1.position, cube2.position);
Debug.Log(distanceCube1 + " / " + distanceCube2);
}
}
위의 실행결과는 아래의 unitypackage에서 확인 가능하다.
Unity Plus:
Unity Pro:
Unity 프리미엄 학습:
'개발 > Unity' 카테고리의 다른 글
유니티 - OnGUI, ProfilerRecorder로 런타임 드로우 콜 확인하기 (How to Get Draw Call Count at Runtime) (0) | 2022.12.10 |
---|---|
유니티 - 드로우 콜 횟수 최적화하기 (Optimizing Draw Calls, Batches) (0) | 2022.11.30 |
유니티 - 다각형 좌표의 시계 방향 판단하기 (How to Determine If a Polygon is Clocwise) (0) | 2022.11.25 |
유니티 - 직선을 N등분 하기 (Dividing a Line) (0) | 2022.11.23 |
유니티 C# - 비교 함수를 이용하여 리스트 정렬하기 (List Sorting with Compare) (0) | 2022.11.22 |
댓글